$1.WHY : 找到MySQL Query执行慢的原因
1.1 EXPLAIN
通过Explain查看SQL Query语句的执行情况,从中找出导致MySQL查询性能差的原因
EXPLAIN + QUERY语句
【字段解释】
<1> id -- 表的读取顺序
- id相同时,按照从上至下的顺序执行
- id不同时,id值越大,则执行优先级越高,执行顺序越靠前
<2> select_type -- 数据读取操作的操作类型
- SIMPLE:简单的select查询,不包含子查询或者UNION操作
- PRIMARY:若查询中包含复杂的子部分,如子查询,则最外层的查询则被标记为PRIMARY,最后执行
- SUBQUERY:在SELECT或者WHERE语句中包含了子查询
- DERIVED:在FROM语句中包含的子查询则会被标记为DERIVED(即衍生表),其结果会被存放在临时表中
- UNION:若第二个SELECT语句出现在UNION后面,则被标记为UNION;若UNION出现在FROM语句中的子查询中,则外层SELECT语句会被标记为DERIVED
- UNION RESULT:从UNION表获取结果的SELECT
<3> table -- 显示当前执行计划是针对哪张表
<4> type -- 访问类型
- system:表中只有一行记录,等价于系统表, 这是const类型的特例,实际生产中基本不会出现
- const:只通过一次索引就能找到,只匹配一条记录,const用于比较primary key或者unique索引。
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一键扫描
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是索引访问,它可能找到多个符合条件的行,因此属于查找和扫描的混合体
- range:只检索给定范围的行,使用一个索引来选择行,key键显示使用了哪个索引。一般出现在where语句中包含between、<、>、in等查询,比全表扫描要好,因为他相当于全表索引的子集,不需要扫描所有
- index:Full Index Scan,index类型遍历全表索引树,而ALL类型要遍历全部表的数据,因此Index类型一般要比ALL更快,因为索引数据量一般小于实际表的数据量
- ALL:扫描全表的行匹配到需要的记录
一般来说,尽量能够优化到ref或者range
<5> possible keys -- 显示可能应用在这张表中的索引,但不一定被实际查询使用
<6> key -- 实际使用的索引
- NULL:表示当前查询没有用到索引(可优化点)
- 查询中如果使用了覆盖索引,则该索引仅出现在key字段中
<7> key_len
表示索引中使用的字节数,可通过该列就按查询中使用的索引长度,在不损失精度前提下,长度越短越好。注意:key_len显示的值为索引字段最大可能长度,并非实际使用长度
<8> ref
显示索引的哪一列被使用了,如果是等值判断的话,该字段也可能是一个常数(const),显示哪些列或常量被用于查找索引列上的值
<9> rows
根据表统计信息即索引选用情况,大致估算出找到所需记录所需要读取的行数
<10> Extra -- 包含不在其他列中显示却又重要的额外信息
- Using filesort:MySQL会对数据进行一个外部的索引排序,而不是按照表中的索引进行排序。这种无法利用索引完成的排序操作成为"文件排序",实际中应该尽量避免,出现了需要及时优化
- Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用了临时表,常见于排序操作 order by 和分组操作 group by ---- 必须避免该情况!!会严重影响MySQL性能
- Using index:表明select操作使用了覆盖索引,避免了全表扫描 ;如果同时出现了Using where,表明索引被用来执行索引键值的查找;如果没有同时出现Using where,表明索引用来读取数据而非执行查找
- Using where:见上
- Using join buffer:使用了表的连接缓存
- impossible where:where子句的值总是false,不能实际获取记录
- select tables optimized away:在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于存储引擎MyISAM优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的计算即完成优化
- distinct:优化distinct操作,在找到第一匹配的元组之后即立即停止找同样值的动作
1.2 SLOW_QUERY_LOG(慢查询日志)
慢查询日志功能开启后,MySQL会自动收集那些执行时间超过设置阈值的QUERY语句,优化人员便能够通过查看日志系统地分析影响MySQL性能的因素。默认MySQL是关闭慢查询日志功能的,因为开启此功能会增加判断和日志收集操作,或多或少会影响MySQL性能
## 开启慢查询日志功能,只对当前服务有效,即MySQL服务器重启后失效
set global slow_query_log=1
## 查看慢查询日志的判断阈值参数long_query_time,默认为10s
show variables like '%long_query_time%';
## 设置阈值为3s,需要重新连接客户端才能生效
set global long_query_time=3;
## 查看当前系统中有多少条慢查询日志记录,可判断系统性能状态
show global status like '%Slow_queries%';
PLUS:慢查询日志官方分析工具 -- mysqldumpslow
1.3 SHOW PROFILE
与慢查询日志一样,MySQL也是默认关闭SHOW PROFILE功能,需要设置参数手动打开
## 开启SHOW PROFILE功能
show variables like "profiling";
set profiling=on;
## 查询记录的所有Query指令及其各环节的执行时间
show profiles;
##对某条查询指令单独进行深度分析,可以查询Query指令的整个生命周期每个环节的运行时间和开销,针对性地进行分析优化
show profile cpu, block io for query 10;
WARNING: 四个主要比较拖慢性能的项,查询结果中如果有任意一个,则需要尽可能优化
- converting HEAP to MyISAM:查询结果太大,内存不够,需要写到磁盘上
- Creating tmp table:创建了临时表,即会将数据拷贝到临时表,用完再删除
- Copying to tmp table on disk:把内存中的临时表复制到磁盘上,非常危险
- locked
1.4 GENERAL_LOG
该功能一般只在测试环境中启用,会收集全局的查询日志,即每一条查询语句都会被记录。实际开发生产环境中一般不要启用
set global general_log=1; ## 开启全局日志功能
set global log_output='TABLE'; ## 设置日志输出为表的格式
select * from mysql.general_log; ## 查询日志记录
$2.HOW:如何优化
2.1 表的Join
<1> 多表Join情况
两表情况:驱动表一方全部保存,因此相当于在被驱动表中查询数据
- 左连接 LEFT JOIN -- 右表外键建索引
- 右连接 RIGHT JOIN -- 左表外键建索引
多表情况:优先用小表驱动大表
- 保证Join语句中被驱动表的Join条件字段已经建立索引
- 当无法保证被驱动表join条件字段被索引情况下,如果内存资源充足,可以启用更大的JoinBuffer
2.2 避免索引失效
- 尽量保证全值匹配,即索引字段和select字段相同且顺序一致
- 最佳左前缀法则:如果索引多列,则查询要从索引的最左列开始,且中间不跳过索引中的列
## 建立联合索引 a_b_c
## 不走索引:WHERE b AND c 、 WHERE c
## 走部分索引:WHERE a AND c、WHERE a AND b
## 走全部索引:WHERE a AND b AND c
- 不在索引列上做任何操作(计算、函数、类型转换、特别注意!注意!注意!不要出现隐式转换),会导致索引失效而全表扫描
## 假设目标行 name = 'july'
select * from info where name='july'; ## 走索引
select * from info where left(name,4)='july'; ## 不走索引
- 一旦出现非等值字段条件判断,则该字段后的索引列皆失效
select * from info where a=10 and b=100 and c=1000; ## 全索引 a_b_c
select * from info where a=10 and b>100 and c=1000; ## 部分索引 a_b
## 非等值条件包括:in < > != like 等
## 注意:当like 'aaa%' 通配符在右时,仍然能够走全索引
select * from info where a=10 and b like '100%' and c=1000; ## 全索引 a_b_c
select * from info where a=10 and b like '%100' and c=1000; ## 部分索引 a
- 尽量使用覆盖索引,即查询列为索引列的子集,减少select * 的使用
- MySQL在使用不等于(!=或者<>)时无法使用索引,会导致全表扫描
select * from info where a=100; ## 走索引
select * from info where a!=100; ## 不走索引,全表扫描
- 查询条件为 is NULL 和 is not NULL情况时也无法使用索引
select * from info where a is null; ## 不走索引
select * from info where a is not null; ## 不走索引
- like以通配符开头('%abc...')时索引也会失效,变为全表扫描;但通配符结尾依然会走索引,但该字段后的索引依然失效
select name, age from info where name like "%aaa"; ## 索引失效
select name, age from info where name like "aaa%"; ## 索引有效
## 当业务要求必须使用左通配符时,可使用覆盖索引的方法来避免索引失效
## 在上面例子中即建立联合索引 name_age
- 字符串不加单引号会导致索引失效 -- 原因:隐式转换
## id为varchar类型
select * from info where id='2000';
select * from info where id=2000; ## 会有隐式类型转换
- 尽量少用or,用它来连接查询条件可能会导致索引失效
- group by基本上都需要进行排序,当group by的字段顺序和索引顺序不一致的时候,就会导致临时表的产生,即同时出现 Using temporary 和 Using filesort,因此一定要极力避免
## 索引为 A_B_C
select * from info where A=10 group by C, B; ## 走索引A,产生临时表
2.3 索引优化小结
- 对于单值索引,尽量选择针对当前查询过滤性更好的索引字段
- 在选择联合索引时,当前查询中过滤性最好的字段在索引字段顺序中位置越靠前越好
- 在选择联合索引时,尽可能选择可以包含当前查询的where子句中更多字段的索引,即如果可能的话,尽量达到索引覆盖,这样不仅能够避免索引失效,也能够避免回表等影响查询性能等操作
- 尽可能通过分析统计信息和调整查询语句的写法来达到适应选择的索引