Administrator
发布于 2022-06-07 / 7858 阅读
90

MySQL 慢查询治理的自动化实践

慢查询告警响了,但我不知道是哪条 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=123WHERE 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);

建议生成逻辑其实不复杂:EXPLAINtypeALLindexrows 很大、且 Extra 出现 Using filesort 时,就把 WHERE 等值列 + ORDER BY 列拼成联合索引建议。上线这条索引后,该 SQL 从 2.1 秒降到 18 毫秒。

一个要紧的边界

我们规定:索引建议只发工单,不自动执行。曾有一次脚本建议给一个 3000 万行的表加联合索引,如果在白天高峰期加,会用掉 40 分钟锁表。所以建议必须经过 DBA 在变更窗口手动执行。自动化的是"发现和分析",决策权留在人手里。

下篇预告

这篇先把《MySQL 慢查询治理的自动化实践》里的坑列了,下一篇写我们当时是怎么在线上工程里真正落地的——包括那次让领导拍桌的故障复盘。

参考