慢查询告警响了,但我不知道是哪条 SQL
有天早上 Prometheus 报了一条 mysql_slow_queries 突增,我登录数据库一查 SHOW PROCESSLIST,满屏都是 Sending data 的查询,根本分不清谁是元凶。那时候我们的慢查询治理还是"出问题了才去看日志",属于事后救火。后来我搭了一套自动化慢查询治理的链路,把"发现—分析—建议"串了起来。
第一步:把慢日志采集起来
先确认 MySQL 的慢日志参数开着,阈值设为 1 秒(我们核心库 QPS 高,500ms 以上就该关注了):
-- 动态开启,生产建议写进 my.cnf 持久化
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1.0;
SET GLOBAL log_queries_not_using_indexes = 'ON';
关键是别只留本地文件。我们用 pt-query-digest 把慢日志解析成结构化数据,再塞进 Elasticsearch,方便按指纹聚合:
# 按 SQL 指纹聚合,输出 Top N 慢查询
$ pt-query-digest /var/lib/mysql/slow.log \
--limit 20 \
--filter '$event->{db} =~ m/order_db/' \
> digest.txt
指纹(fingerprint)会把 WHERE id=123 和 WHERE id=456 归成同一条模板,这样我们能看到"这条 SQL 模式一共出现了 8 万次,平均 2.1 秒",而不是被 8 万个具体值淹没。
第二步:自动告警,按指纹而不是按次
把 digest 结果推到 Kafka,消费端写进 ES,再用 Prometheus 暴露指标。告警规则我特意按指纹聚合,而不是"出现一次就报":
groups:
- name: slow-sql
rules:
- alert: SlowSqlSpike
expr: sum by (fingerprint) (slow_sql_count_total[5m]) > 500
for: 10m
labels: {severity: warning}
annotations:
summary: "慢 SQL 指纹 {{ $labels.fingerprint }} 5 分钟超 500 次"
之前我们按"单次慢查询"告警,一天能收到 200 多条,全被忽略。改成按指纹聚合后,每天有效告警降到 3~5 条,每一条都值得看。
第三步:自动生成索引建议
光告警不够,我接了个脚本,对 Top 慢 SQL 自动跑 EXPLAIN,根据结果生成索引建议:
-- 典型的一条慢 SQL
SELECT * FROM order_item
WHERE user_id = 88123 AND status = 'PAID'
ORDER BY create_time DESC
LIMIT 20;
-- EXPLAIN 显示 type=ALL,rows=1,280,000,全表扫
-- 脚本据此建议:
ALTER TABLE order_item
ADD INDEX idx_user_status_time (user_id, status, create_time);
建议生成逻辑其实不复杂:EXPLAIN 里 type 是 ALL 或 index、rows 很大、且 Extra 出现 Using filesort 时,就把 WHERE 等值列 + ORDER BY 列拼成联合索引建议。上线这条索引后,该 SQL 从 2.1 秒降到 18 毫秒。
一个要紧的边界
我们规定:索引建议只发工单,不自动执行。曾有一次脚本建议给一个 3000 万行的表加联合索引,如果在白天高峰期加,会用掉 40 分钟锁表。所以建议必须经过 DBA 在变更窗口手动执行。自动化的是"发现和分析",决策权留在人手里。
下篇预告
这篇先把《MySQL 慢查询治理的自动化实践》里的坑列了,下一篇写我们当时是怎么在线上工程里真正落地的——包括那次让领导拍桌的故障复盘。