华为云 RDS for MySQL 慢查询治理实战:从慢日志定位到索引重构

举报
yd_253000176 发表于 2026/10/26 09:09:29 2026/10/26
【摘要】 以一次晚高峰 CPU 打满的真实排查为例,走通华为云 RDS for MySQL 慢日志抓取、Top SQL 聚合排序、执行计划解读到联合索引重构的完整链路,附可直接复用的排障 SQL、ALTER 语句与四条调参经验。

一、现象: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 与采样阈值这些配角往往决定抖动是否彻底消失。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。