MySQL 笔记:慢查询日志的开启与定位流程

· Mysql笔记 MySQL SQL优化 慢查询 · 1 阅读 · 约 6 分钟

前言

慢查询日志是成本最低的优化入口:只记录超过阈值的语句,不影响正常查询,出了问题翻日志就行。这篇记一遍开启方式和定位流程。

开启与参数

配置文件里写死(需要重启):

[mysqld]
slow_query_log      = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time     = 1
log_output          = FILE

线上临时打开,不用重启:

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW GLOBAL STATUS LIKE 'Slow_queries';

几个容易踩的点:

  • long_query_time 默认是 10 秒,几乎所有业务都嫌太宽,一般设 1 秒甚至 0.5 秒;它支持小数
  • 修改 long_query_time 只对新建立的连接生效,已经存在的长连接(比如应用连接池、常驻的从库 SQL 线程)用的还是旧阈值,改完记得重连
  • log_queries_not_using_indexes 会把所有没走索引的语句都记下来,小表全扫其实很快,会把日志刷屏,建议配合 min_examined_row_limit 设一个最小扫描行数
  • 从库默认不记录复制过来的语句,要排查主从延迟来源得开 log_slow_slave_statements

定位流程

我一般是五步走:

  1. 挑一个业务高峰时段打开日志,别只抓空闲时间
  2. 聚合排序,先找出总耗时长的那几条,而不是单条最慢的
  3. 拿典型语句出来 EXPLAIN,看 type、key、rows、Extra
  4. 改索引或改写 SQL,在小范围验证
  5. 复测:同一时间段对比 Slow_queries 计数和业务接口耗时

聚合用自带的 mysqldumpslow:

# 按总耗时排序取前 10
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 按执行次数排序,次数多的语句往往比偶尔一条超时的更值得改
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 只看某个库
mysqldumpslow -g "db=blog_php" /var/log/mysql/slow.log

-s 后面跟排序维度,可选值我抄一份在这:al 平均锁时间、at 平均查询时间(默认)、ar 平均返回行数、c 执行次数、l 总锁时间、r 总返回行数、t 总查询时间;-t 取前 N 条,-h 匹配多台实例的 slow log,-a 关闭数字抽象方便看真实参数。想要更详细的指纹聚合可以上 Percona Toolkit 的 pt-query-digest,第三方工具,需要单独装。

抓现场的两个习惯

一是先切日志再分析。FLUSH LOGS; 之后 MySQL 会重开慢查询日志文件,把旧文件挪走单独处理,避免边写边读只解析到半截。二是记住时间窗。改 SQL 之前把 SHOW GLOBAL STATUS LIKE 'Slow_queries'; 的当前值抄下来,改完在同样的业务时段再对比增量,比单看某一条语句的执行时间可靠得多——单条计时很容易被缓冲池冷热、并发高低干扰,得出"改了但没变快"的错误结论。

记到表里的用法

SET GLOBAL log_output = 'TABLE';
SET GLOBAL slow_query_log = ON;

SELECT start_time, query_time, lock_time, rows_sent, rows_examined, db,
       CONVERT(sql_text USING utf8mb4) AS sql_text
FROM mysql.slow_log
ORDER BY start_time DESC
LIMIT 10;

mysql.slow_log 是 CSV 引擎,查起来不如 MyISAM 表灵活,堆积久了要用 RENAME TABLE 换表再清空旧表。日常我更喜欢 FILE 方式加 mysqldumpslow,需要 SQL 聚合分析时才切到 TABLE。

不看日志也能定位的补充手段

5.7 的 performance_schema 默认开着语句摘要表,重启后仍能看历史统计:

SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR,
       SUM_TIMER_WAIT / 1000000000000 AS total_sec,
       AVG_TIMER_WAIT / 1000000000000 AS avg_sec,
       SUM_ROWS_EXAMINED
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME IS NOT NULL
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

时间单位是皮秒,除以 10 的 12 次方换算成秒。它跟慢查询日志互补:日志只留超过阈值的,摘要表把所有语句都统计进来。

小结

  • 阈值先设小,long_query_time 改完要重连才生效
  • 先看聚合排序的 Top 列表,不要一上来逐条读原始日志
  • 单条超时和总量大的语句是两类问题,后者通常收益更高
  • 慢查询日志解决"哪条慢",EXPLAIN 解决"为什么慢",缺一不可