华为云 RDS for MySQL 慢查询治理:从慢日志到索引重建的四步法
上周一个订单查询接口的 P99 从 120ms 涨到 1.8s,监控上 CPU 只有 35%,连接数正常,没有任何告警。查了两天才发现根因是一条三个月前上线的 SQL 走了错误的索引。这篇把我们在华为云 RDS for MySQL 上做慢查询治理的完整流程整理出来:从打开慢日志,到把慢日志变成可执行的索引变更,一共四步。
一、先把慢日志变成可用数据
RDS 默认的 long_query_time 是 10 秒,这个阈值对线上接口毫无意义——1.8 秒的慢查询一条都不会被记录。第一步是把它压到 0.5 秒,并且只在参数组里改,不要用会话级 SET GLOBAL,否则实例重启或主备切换后配置就丢了。
这里有三个容易踩的点。第一,log_queries_not_using_indexes 不要在生产上长时间打开:小表全表扫描也会被记录,慢日志几小时就能涨到几个 GB,反而影响 IO。第二,阈值不要设得比业务 SLO 还低,0.5 秒是"人已经能感觉到卡"的分界线,再低就淹没在噪音里。第三,慢日志要开在只读实例或者主实例上要分清:只读实例的慢查询反映的是报表类负载,和主库的在线交易不是一回事,混在一起看会得出错误结论。
二、按 SQL 指纹聚合,不要看单条
慢日志是按"每次执行"记录的,一条高频 SQL 哪怕每次只要 0.6 秒,一天执行 20 万次,它占用的总时间是单条 5 秒但一天只跑 10 次的那条 SQL 的上千倍。所以拿到慢日志的第一件事是按指纹聚合:把常量替换成占位符,按"总耗时"和"平均耗时"两个维度分别排序。
我们在 RDS 控制台的慢 SQL 页签里能直接看到聚合后的结果,但要注意它默认按平均耗时排序。真正该修的是"总耗时最高"的那几条——它们才是压垮 CPU 和 IO 的主力。反过来,平均耗时特别高但总耗时很低的,往往是离线脚本或后台任务,可以排到后面。
三、EXPLAIN 之后,索引设计的三个判断
拿到 Top 5 的 SQL,逐条 EXPLAIN。华为云 RDS 支持在 DAS(数据管理服务)里直接对慢 SQL 做执行计划分析,省去了连跳板机开终端的步骤。看执行计划时我关注四件事:type 是不是 ALL 或 index(全表/全索引扫描)、key 是不是 NULL、rows 估算的数量级、Extra 里有没有 Using filesort 或 Using temporary。
索引设计上,我们固化了三条规则:
最左前缀——把等值条件放前面,范围条件放后面,排序字段接在等值条件之后。一条 (a, b, c) 的联合索引能覆盖 a、a+b、a+b+c 三种查询,但覆盖不了 b 或 b+c。
覆盖索引——如果查询只需要返回索引里已有的列,就不必回表,把 SELECT 里那几个列一起放进索引,往往比新建一个索引收益更大。代价是索引变大、写入变慢,所以只对读多写少的热点查询这么做。
区分度优先——单列索引的区分度低于 10% 基本没有意义,性别、状态、是否删除这类低基数字段单独建索引纯属浪费。如果一定要用它,就放在联合索引的第二位,让它服务于过滤而不是定位。
四、变更索引要走在线 DDL
找到问题索引之后,最危险的动作就是直接 ALTER TABLE。业务表几十 GB 的时候,一条加索引语句既可能锁表几十秒,也可能在从库上造成长时间的主备延迟。我们的做法是:先确认表大小和当前 QPS,再用 DAS 的变更能力或者 gh-ost 这类工具做在线变更,把 DDL 拆成"建影子表—同步增量—原子切换"三步,期间业务读写不受影响。
另外提醒一句,索引不是越多越好。我们有个表被历史上十几个需求加了 9 个索引,写入 TPS 只有同规格实例的三分之一,因为每次 INSERT 都要维护 9 棵 B+ 树。治理的收尾动作应该是"加一个、删一个":把 EXPLAIN 里 key 指向空的那几个索引列出来,确认没有其他 SQL 依赖后删掉。
最后是复盘清单:慢查询治理不是一次性动作。我们把 long_query_time、慢日志采集、聚合报表做成固定流程,每周看一次 Top 10,每个季度清一次无用索引。真正让接口变慢的很少是数据库"性能不够",而是某次需求变更悄悄改掉了执行计划,而监控只看 CPU 和 QPS 看不出来。
- 点赞
- 收藏
- 关注作者
评论(0)