前言
慢查询日志是成本最低的优化入口:只记录超过阈值的语句,不影响正常查询,出了问题翻日志就行。这篇记一遍开启方式和定位流程。
开启与参数
配置文件里写死(需要重启):
[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
定位流程
我一般是五步走:
- 挑一个业务高峰时段打开日志,别只抓空闲时间
- 聚合排序,先找出总耗时长的那几条,而不是单条最慢的
- 拿典型语句出来
EXPLAIN,看type、key、rows、Extra - 改索引或改写 SQL,在小范围验证
- 复测:同一时间段对比
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解决"为什么慢",缺一不可