MySQL慢查询排查
慢查询标准
如何判断某个SQL属于慢查询?
慢查询并不是凭借感觉判断的,而是有明确指标的,MySQL默认执行超过10s的SQL叫慢查询,但实际生产一般会调到1s以下,甚至是300ms,当某条SQL执行时间超过阈值,则会被记录到日志当中,这个日志就是慢查询日志。
如何开启慢查询日志
-
在配置文件中修改
-- 开启日志
slow_query_log = on
-- 记录日志的log文件
slow_query_log_file = 路径
-- 最长查询的秒数
long_query_time = 0.5
-- 表示记录没有使用索引的查询
logqueriesnotusingindexes -
直接SQL动态开启
-- 启动慢查询
set global slow_query_log=1;
-- 修改慢查询时间,单位:s
set global long_query_time=0.5;
long_query_time 不会精确到小数,只能写成 0.5,但 MySQL 实际会记录超过 0.5 秒的。慢日志会记录每条 SQL 的执行时间、扫描行数、锁等待时间
查询流程
- 看慢查询日志,确认SQL语句
- 使用真实参数还原SQL
- explain查看执行计划
- 看索引结构、字段类型
- 优化SQL或加索引
- explain验证
- 压力测试验证
假设有一条慢查询SQL
SELECT * FROM table WHERE name like '%UGE%';
执行时间为1s,大于慢查询时间0.5s
1. 查看慢查询日志
此时打开慢查询日志进行日志分析:
## Time: 150530 15:30:58 -- 该查询发生在2015530 15:30:58
## User@Host: root[root] @ localhost [127.0.0.1] --是谁,在什么主机上发生的查询
## Query_time: 1.134065 Lock_time: 0.000000 Rows_sent: 8 Rows_examined: 4000000 Query_time: --查询总共用了多少时间,Lock_time: 在查询时锁定表的时间,Rows_sent: 返回多少rows数据,Rows_examined: 表扫描了400W行数据才得到的结果;
当然,如果慢SQL很多,就需要借助一些分析工具,比如MySQL中的mysqldumpslow
mysqldumpslow s c t 10 /var/run/mysqld/mysqldslow.log # 取出使用最多的10条慢查询
mysqldumpslow s t t 3 /var/run/mysqld/mysqldslow.log # 取出查询时间最慢的3条慢查询
mysqldumpslow s t t 10 g "left join" /database/mysql/slowlog #得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow s r t 10 g 'left join' /var/run/mysqld/mysqldslowlog # 按照扫描行数最多的
2. 使用真是的数据还原SQL
3. explain查看执行计划
explain 有很多字段,但主要还是看这几个:type、key、rows、extra
3.1. type
查询方式
type字段表示查询方式,查询质量由好到坏依次是
NULL > CONST/SYSTEM > EQ_REF > REF > RANGE > INDEX > ALL
- NULL:不用访问表或索引就能直接得到结果
- CONST/SYSTEM:单表中最多只有一条匹配行,所以这个匹配行中的其他列中的值可以被优化器在当前查询中当做常量来处理
- EQ_REF:使用唯一索引的前缀扫描,对于每个索引键值,只有唯一的一条匹配记录
- REF:使用非唯一性索引或者唯一索引的前缀扫描,返回匹配某个单独值的记录行
- RANGE:索引范围扫描,常见于<、<=、between等操作符
- INDEX:索引全扫描,遍历整个索引查找匹配行
- ALL:全表扫描
3.2. key
实际选择的索引。如果为NUL表示未使用
3.3. rows
MySQL估算会扫描的行数,数值越小越好
3.4. extra
展示有关本次查询的附加信息 ,其中,如果出现 Using filesort(文件排序)或 Using temporary(使用临时表),通常意味着性能瓶颈。文件排序意味着你 order by 的字段没有索引或者索引没命中
4. 看索引结构、字段类型
5. 进行索引优化
优化索引的方法
- 前缀索引优化
- 覆盖索引优化
- 主键索引最好自增
- 防止索引失效
5.1. 前缀索引优化
使用某个字段中字符串的前几个字符建立索引,这是为了减小索引字段的大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的自读那作为索引时,使用前缀索引可以帮助我们减小索引项的大小。但也有局限性:
- order by无法使用前缀索引
- 无法把前缀索引用作覆盖索引
5.2. 覆盖索引优化
索引包含所有查询字段 ,避免回表(如 SELECT name FROM users WHERE age = ?,索引 (age, name) 可直接返回数据),这样就不需要查询出包含整行记录的所有信息,能够显著减少磁盘 I/O(尤其是 SELECT 少量字段时)。
5.3. 主键索引最好自增
基于B+Tree的结构,使用自增主键 时,每次插入新数据就会按顺序添加到当前索引节点的未知,不需要移动已有数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。
如果使用的时非自增主键 ,那么每次插入新数据时,就有可能需要移动其他数据来满足新数据的插入,甚至需要从一个页面复制数据到另一个页面,也就是页分裂 。页分裂可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
5.4. 索引设置为NOT NULL
原因:
- 索引列存在NULL会导致优化器在做索引选择的时候会更加复杂,更加难以优化,因为可为NULL的列会使索引、索引统计和值比较更加复杂
- NULL是没有意义的值,但是他会占用物理空间,带来存储空间的问题。
5.5. 防止索引失效
会导致索引失效的情况:
- 对索引使用左或左右模糊匹配 ,也就是
like %xx或like %xx%。因为索引B+树是按照**[索引值]**有序排列存储的,只能根据前缀比较 - **对索引使用函数。**因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然没办法走索引。
- **对索引进行表达式计算。**索引保存的是索引字段的原始值,而不是经过表达式计算之后的值,所以会进行全表扫描,将所有数据对应自字段的值取出计算后比较。
- 对索引隐式类型转换 如:索引字段是字符串类型 ,但查询使输入的参数是整型 ,就会走全表扫描 ,但是如果索引字段是整型 ,但查询使用输入的是字符串 就不会 。MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。 所以当输入整型查询字符串字段时,MySQL会自动将存储的数据 中对应字段的字符串逐个转换成整型再比较,这里就相当于是使用函数。但是第二种情况会自动将输入的参数转换成整型,就不需要对MySQL的数据进行转换了,故不会走全表扫描。
- 联合索引非最左匹配 对应内容在上文联合索引 中的联合索引范围查找有提到过。
- where 子句中的 orwhere 子句中,如果 or 前的条件列是索引列,但 or 后的条件列不是,那么索引会失效。因为or里面有一个条件不是索引都会以这个条件单独查找一边,也就导致了全表扫描。
之后就是查询explain执行计划,查看索引优化是否生效,确定后,再进行压力测试做最后的确认。