MySql--慢查询
摘要
-
MySql知识点介绍: 慢查询
-
本文基于
mysql-8.0.30,https://dev.mysql.com/doc/refman/8.0/en/
常见面试题
慢查询
-
什么是慢查询
慢查询日志,顾名思义,就是查询花费大量时间的日志,是指mysql记录所有执行超过long_query_time参数设定的时间阈值的SQL语句的日志。该日志能为SQL语句的优化带来很好的帮助。默认情况下,慢查询日志是关闭的,要使用慢查询日志功能,首先要开启慢查询日志功能。 -
开启慢查询
1 | mysql> show VARIABLES like 'slow_query_log'; |
-
慢查询日志格式
1 | “Time: 2021-04-05T07:50:53.243703Z”:查询执行时间 |
-
慢查询分析
mysqldumpslow
1 | mysqldumpslow -s r -t 10 /usr/local/soft/mysql8/datas/mysql/ip-10-250-0-214-slow.log |
SET GLOBAL slow_query_log=1只对当前运行实例生效,重启后丢失,线上应写进 MySql单节点、主从、双主的构建方法。SET GLOBAL long_query_time只影响之后新建的连接,当前会话还要再SET SESSION long_query_time=...。默认 10 秒太宽,生产常见是 1~2 秒;log_queries_not_using_indexes容易把日志打爆,一般保持关闭,用下面的performance_schema来抓全表扫描。
慢SQL如何排查
排查就五步:把慢SQL找出来 → 看数字判断慢在哪 → EXPLAIN 看怎么走 → 对症改 → 回归验证。不要一上来就加索引,先分清是 SQL 本身慢、锁等待、还是机器 IO/CPU 已经打满。
1. 把慢SQL找出来
正在发生的、已经发生的、没开慢日志的,三条路。
-
正在卡住:看当前线程,做法见 MySql--线程管理
1 | SHOW FULL PROCESSLIST; |
Time 很大且 State 长期停在 Sending data / Creating sort index / Waiting for ... lock,这条就是现场。Waiting for ... lock 先去查锁,不要先改 SQL,锁相关见 MySql--锁。
-
已经发生过:慢查询日志 +
mysqldumpslow(见上文)。优先看:-s t总耗时最高:对系统伤害最大-s c次数最多:单次不慢、积少成多-s r扫描/返回行数最多:典型没走好索引
-
没开慢日志,MySQL 8.0 默认开了
performance_schema,直接问摘要表:
1 | SELECT SCHEMA_NAME, |
装了 sys 库更省事:
1 | SELECT * FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 10; |
2. 先看数字,判断慢在哪
慢日志里三个数字最有用,先定性再动手:
| 现象 | 慢在哪 | 下一步 |
|---|---|---|
Lock_time 接近 Query_time |
在等锁,SQL 本身不一定差 | MySql--锁、MySql--线程管理 |
Rows_examined 远大于 Rows_sent |
扫了很多行才筛出几行,索引/条件有问题 | EXPLAIN,再看 MySql索引 |
Rows_examined 不大,但 Query_time 很大 |
单行很重,或结果集太大、磁盘 IO、网络 | 少查列、分页、看 Buffer Pool |
| 次数极多、单次耗时一般 | 热点 SQL 被循环调用 | 先改调用次数,再改 SQL |
经验值:Rows_examined / Rows_sent 越大越冤。返回 10 行却扫了 100 万行,优先查索引,不要先加机器。
3. EXPLAIN 看优化器怎么走
字段含义见 MySql执行计划。排查时先盯这几列:
-
type:从好到差大致是system>const>eq_ref>ref>range>index>ALL。ALL就是全表扫描,单表数据量大时基本就是慢 SQL。 -
key为NULL:没用上索引。对照possible_keys,有候选却没用,多半是条件写法和索引不匹配。 -
rows:优化器估的扫描行数,越大越慢。这是估算,和慢日志里真实的Rows_examined对一下。 -
Extra:Using filesort/Using temporary:排序或分组没走索引Using where:存储引擎取出后再用 where 过滤,往往扫描偏多Using index:覆盖索引,通常是好事Using index condition:索引条件下推,比回表后再过滤好
MySQL 8.0.18 起可以用 EXPLAIN ANALYZE,跑一遍真实执行,给出每一步实际耗时和实际行数,比普通 EXPLAIN 更接近现场:
1 | EXPLAIN ANALYZE SELECT * FROM employees WHERE name = 'LiLei' AND age = 22; |
改 SQL 或加索引之后,用 SHOW WARNINGS 看优化器重写后的语句,避免自己以为改对了、优化器其实没按你想的走。
4. 对症改
-
SQL / 索引问题(最常见)
- where / join / order by / group by 列上没有合适索引,或违反最左前缀。联合索引、覆盖索引、前缀索引见 MySql索引。
- 索引列包了函数、做了运算、隐式类型转换(
'123'和123、字符集不一致),索引失效,表现就是possible_keys有值但key为NULL,或type=ALL。常见场景和改法见 MySql索引。 SELECT *导致无法覆盖索引,必须回表;只查需要的列。- 深分页
LIMIT 100000, 20:先按索引把主键翻出来再回表,不要直接大 offset。 - 统计信息不准,优化器选错索引:
ANALYZE TABLE t;更新索引基数。
-
锁等待
Lock_time高、State带 lock、SHOW ENGINE INNODB STATUS里有LOCK WAIT。先找长事务、没提交的for update,再考虑是不是没走索引把行锁升级成了表锁,见 MySql--锁。
-
执行环境
- Buffer Pool 命中率低,热数据全在磁盘上,见 MySql--InnoDB Buffer Pool。
- 单次返回结果集过大,时间花在
Sending data和网络上,加分页、少选列。 - 服务器 CPU / 磁盘已经打满,所有 SQL 一起变慢:这不是单条 SQL 的问题,先看机器和连接数。
5. 回归验证
-
同一条 SQL 再
EXPLAIN/EXPLAIN ANALYZE,确认type、key、rows变好了。 -
用生产可比的数据量看耗时,不要只在几十行的测试表上宣布优化成功。
-
观察慢日志和
events_statements_summary_by_digest里这条 digest 的COUNT_STAR、AVG_TIMER_WAIT是否下降。 -
加索引后用
SHOW INDEX看Cardinality,并盯一下写入是否变慢——索引不是免费的,原则见 MySql索引。
慢SQL问题如何排查?
- 先分清三种慢:
SQL 执行慢(扫描行数多)、等锁慢(Lock_time高)、环境慢(IO/CPU/网络/结果集)。搞混了就会给一条在等锁的 SQL 乱加索引。 - 找 SQL:现场用
SHOW PROCESSLIST,历史用慢查询日志 +mysqldumpslow,没开慢日志用performance_schema.events_statements_summary_by_digest或sys.statement_analysis。 - 定性:
Rows_examined >> Rows_sent查索引;Lock_time接近总耗时查锁;次数多单次不慢查调用方。 - 分析:
EXPLAIN看type/key/rows/Extra,8.0 用EXPLAIN ANALYZE看真实耗时。type=ALL、key=NULL、Using filesort、Using temporary是最常见的红灯。 - 处理:条件与索引对齐(最左前缀、避免函数和隐式转换)、能覆盖就覆盖、深分页改写、
ANALYZE TABLE刷新统计信息;等锁就收短事务;环境问题看 Buffer Pool 和结果集大小。 - 验证:同样数据量复测,digest 统计下降才算完。线上务必把慢日志写进配置并定期扫,不要只靠临时
SET GLOBAL。