华为云 RDS for MySQL 慢查询治理实战:从慢日志定位到索引重构
一、现象:QPS 不高,CPU 却打满
某订单类业务的 RDS for MySQL 实例在晚高峰出现接口 P99 从 180ms 涨到 1.2s,但监控里 QPS 只有 300 左右,远未触及实例连接上限,CPU 使用率却长期停在 85% 以上。这种「低并发、高 CPU」的组合基本可以排除连接数打满,第一嫌疑就是慢查询在反复扫大表。本文记录一次完整的排查与治理过程,把每个环节的可复用动作都留下来。
二、第一步:把慢日志变成可排序的清单
华为云 RDS 的慢日志按 long_query_time 采集。先在控制台「参数修改」里把 long_query_time 从默认 10 秒临时调到 0.5 秒,并打开 log_queries_not_using_indexes,采集 10 分钟后立刻调回,避免长期写入开销。随后在 DAS(数据管理服务)的慢日志页导出,或者直接对 performance_schema 做聚合:
SELECT LEFT(digest_text, 80) AS sql_head,
count_star AS exec_count,
ROUND(sum_timer_wait/1e12, 2) AS total_sec,
ROUND(avg_timer_wait/1e12, 4) AS avg_sec,
sum_rows_examined / count_star AS rows_per_exec
FROM performance_schema.events_statements_summary_by_digest
WHERE schema_name = 'order_db'
ORDER BY sum_timer_wait DESC
LIMIT 20;
按总耗时排序而不是按单次耗时排序,是这里最关键的一步:一条 0.6 秒的 SQL 每分钟跑 2000 次,比一条偶尔 8 秒的报表 SQL 危害大得多。我们抓到的问题 SQL 是:
SELECT id, user_id, status, amount, created_at FROM t_order WHERE shop_id = ? AND status IN (1,3) AND created_at >= ? ORDER BY created_at DESC LIMIT 20;
单次 0.6 秒,rows_per_exec 约 42 万行,说明它一直在扫全表而不是走索引。
三、第二步:读懂执行计划里的三个字段
EXPLAIN 之后只看三个字段:type、key_len、rows。原表只有主键和 shop_id 单列索引,执行计划显示 type=ref、key=idx_shop、rows 约 42 万,Extra 里带 Using filesort。问题很明确:索引只过滤了 shop_id,status 和 created_at 都没进索引,回表与排序全部落在 Server 层,这也是 CPU 被打满的直接原因。
四、第三步:按最左前缀重构索引
等值条件在前、范围条件在后,是联合索引的基本排序规则。这里 shop_id 与 status 是等值,created_at 是范围且参与排序,所以列顺序应为 (shop_id, status, created_at):
ALTER TABLE t_order ADD INDEX idx_shop_status_ctime (shop_id, status, created_at), ALGORITHM=INPLACE, LOCK=NONE;
建完再 EXPLAIN,type 仍是 ref,但 key_len 从 8 涨到 13,rows 从 42 万降到约 1200,Using filesort 消失——因为 created_at 在索引内部已经有序,可以直接倒序取 20 条。有一个容易忽略的点:status IN (1,3) 本质是范围语义,严格来说最左前缀只保证 shop_id 完全可用;但 MySQL 8.0 对 IN 列表有范围扫描优化,实践中仍能吃满三段。建议用 EXPLAIN ANALYZE 验证真实扫描行数,不要只看预估 rows。如果查询列较少,进一步做成覆盖索引 (shop_id, status, created_at, id, amount) 可以彻底免回表,但会显著增加写放大,订单这种写多的表要谨慎,我们最终没有加。
五、第四步:参数与连接层的收尾
索引改完后 CPU 从 92% 降到 35%,但 P99 仍有偶发抖动,补齐三件事后才真正稳定:一是把 innodb_buffer_pool_size 调到实例内存的 70%,此前只有 40%,索引重建后热数据能常驻内存;二是 slow_query_log 阈值回调到 1 秒但保持长期开启,作为回归监控;三是应用侧把那条 IN 查询的分页 LIMIT 从 20 调到 50,减少翻页穿透导致的重复查询。
六、效果与四条经验
治理后观察一周:慢 SQL 日均从 3.2 万条降到 400 条以内,晚高峰 P99 回到 210ms,CPU 峰值 38%。复盘留下四条可复用的经验。
1. 慢日志要按总耗时排序,单次耗时只是噪音;
2. 先看 rows_per_exec,它比执行计划更早暴露全表扫描;
3. 联合索引用「等值在前、范围在后」,改完必须用 EXPLAIN ANALYZE 验证真实行数;
4. 索引不是唯一解,buffer pool 与采样阈值这些配角往往决定抖动是否彻底消失。
- 点赞
- 收藏
- 关注作者
评论(0)