首頁 > 資料庫 > mysql教程 > 一个用户SQL慢查询分析,原因及优化_MySQL

一个用户SQL慢查询分析,原因及优化_MySQL

WBOY
發布: 2016-06-01 13:13:27
原創
894 人瀏覽過

问题描述

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">一个用户反映先上一个SQL语句执行时间慢得无法接受。SQL语句看上去很简单(本文描述中修改了表名和字段名):SELECT count(*)FROMaJOINb ONa.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` </code>
登入後複製

分析

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">从explain的结果看,查询用了b作为驱动表。上一篇文章我们介绍到,MySQL选择jion顺序是分别分析各种join顺序的代价后,选择最小代价的方法。这个join只涉及到两个表,自然也与optimizer_search_depth无关。于是我们的问题就是,我们预期的那个join顺序的为什么没有被选中?MySQL Tips: MySQL提供straight_join语法,强制设定连接顺序。explain SELECT count(*)FROMastraight_joinb ONa.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` </code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">MySQL Tips: explain结果中,join的查询代价可以用依次连乘rows估算。</code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">join顺序对了,简单的分析查询代价:普通join是1038165*1, straight_join是 63*1038165. 貌似MySQL没有错。但一定哪里不对!</code>
登入後複製

发现异常

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">回到我们最初的设想。我们预计表a作为驱动表,是因为认为表b能够用上IX_S索引,而实际上staight_join的时候确实用上了,但这个结果与我们预期的又不同。我们知道,索引的过滤性是决定了一个索引在查询中是否会被选中的重要因素,那么是不是b.S的过滤性不好呢?</code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">MySQL Tips: show index from tbname返回结果中Cardinality的值可以表明一个索引的过滤性。</code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">show index的结果太多,也可以从information_schema表中取。mysql> select * from information_schema.STATISTICS where table_name='b' and index_name='IX_S'/G*************************** 1. row ***************************TABLE_CATALOG: def TABLE_SCHEMA: test	TABLE_NAME: b	NON_UNIQUE: 1 INDEX_SCHEMA: test	INDEX_NAME: IX_S SEQ_IN_INDEX: 1COLUMN_NAME: S	 COLLATION: ACARDINALITY: 1038165	SUB_PART: NULL		 PACKED: NULL	NULLABLE: YES	INDEX_TYPE: BTREE		COMMENT: INDEX_COMMENT: 可以这个索引的CARDINALITY: 1038165,已经很大了。那这个表的估算行是多少呢。show table status like 'b'/G*************************** 1. row ***************************			Name: b			Engine: InnoDB		Version: 10	Row_format: Compact			Rows: 1038165 Avg_row_length: 114	 Data_length: 119160832Max_data_length: 0	Index_length: 109953024		Data_free: 5242880 Auto_increment: NULL	 Create_time: 2014-05-23 00:24:25	 Update_time: NULL	Check_time: NULL		Collation: utf8_general_ci		 Checksum: NULL Create_options: 		Comment: 1 row in set (0.00 sec)从Rows: 1038165看出,IX_S这个索引的区分度被认为非常好,已经近似于唯一索引。MySQL Tips: 在show table status结果中看到的Rows用于表示表的当前行数。对于MyISAM表这是一个精确值,但对InnoDB这是个估算值。虽然是估算值,但优化器是以此为指导的,也就是说,上面的某个explain里面的数据完全不符合期望:staight_join结果中第二行的rows。</code>
登入後複製

目前为止

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">我们发现整个错误的逻辑是这样的:以a为驱动表的执行计划,由于索引b.S的rows估计为1038165导致优化器认为代价大于以b为驱动表。而实际上这个索引的区分度为1.(当然对explan结果比较熟悉的同学会发现,第二行的type字段和Extra字段一起诡异了)也就是说,straight_join得到的每一行去b中查询的时候,都走了全表扫描。在MySQL里面出现这种情况的最常见的是类型转换。比如一个字符串字段,虽然包含的是全数字,但查询的时候传入的不是字符串格式。在这个case里面,两个都是字符串。因此,就是字符集相关了。回到两个表结构,发现S字段的声明差别在于 COLLATE utf8_bin -- 这个就是本case的根本原因了:a表得到的S值是utf8_bin,优化器认为类型不同,无法直接用上索引b.IX_S过滤。至于为什么还会用上索引,这个是因为覆盖索引带来“误解”。</code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">MySQL Tips:若查询的所有结果能够从某个索引完全得到,则会优先用遍历索引替代遍历数据。</code>
登入後複製
<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">作为验证,mysql> explain SELECT *FROMastraight_JOINb ONbinary a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` </code>
登入後複製

+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
| 1 | SIMPLE | a | range | IX_L | IX_L | 4 | NULL | 63 | Using where | 
| 1 | SIMPLE | b | ALL | IX_S | NULL | NULL | NULL | 1038165 | Range checked for each record (index map: 0x4) | 
+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
由于结果是select *, 无法使用覆盖索引,因此第二行的key就显示为NULL. (笔者泪:要是早出这个结果查起来可方便多了)。

优化

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">当然最直接的想法就是修改两个表的S字段的定义,改成相同即可。这个方法可以避免修改业务代码,但DDL代价略大。这里提供两种在SQL语句方面的优化。1、select count(*) fromb join (select s fromaWHERE a.`L` > '2014-03-30 00:55:00' AND a.`L`  '2014-03-30 00:55:00' AND a.`L` </code>
登入後複製

小结

<code style="font-family: inherit; font-size: 14px; padding: 0px; color: inherit; background-color: transparent; white-space: pre-wrap; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border: 0px;">做join的字段尽量设计为类型完全相同。</code>
登入後複製
相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板