一条RDS慢查询拖住了发布窗口
周五晚上 19:42,我在发布订单查询服务。应用实例一共 12 个,每个 2 vCPU、4 GB 内存,健康检查正常,但发布后的请求 P99 从 120 毫秒涨到了 8.6 秒。新版本只是增加了一个按商家和状态筛选的条件,代码评审时我并没有把它当成高风险改动。
告警来自 CES 的 rds006 指标,连接数只有 86,CPU 使用率 54%,磁盘 I/O 也没有明显尖峰。值班同事转给我一条错误日志:
2025-03-14 19:43:07.182 ERROR request timeout
query: SELECT id, order_no, buyer_id, status, amount, created_at
FROM t_order WHERE merchant_id = 824731 AND status = 3
ORDER BY created_at DESC LIMIT 50
trace_id: 7c0d9b8e2a1f
这里的 timeout 是应用自己设置的 3 秒,并不是 MySQL 返回的错误。RDS 控制台显示实例规格为 2 vCPU、8 GB,存储 100 GB,引擎版本为 MySQL 8.0.32,地域是华东-上海一。我先怀疑发布时连接池被放大,打开应用日志后却看到慢请求集中在同一组参数上,便转去看慢查询日志。
发布脚本里只有一处改动:原来的查询按 created_at 倒序取 50 条,现在额外传入了 merchant_id=824731 和 status=3。这两个值不是写死的,而是由商家后台的筛选控件提交。代码里还把查询包在 Spring Boot 2.7.18 的 MyBatis 映射中,JDBC 驱动版本是 mysql-connector-j 8.0.33,单次请求没有分页,也没有主动读取全部结果集。
慢日志里没有想象中简单
RDS 慢查询日志开启后,slow_query_log 参数显示为 ON,long_query_time 是 1 秒。我通过控制台导出了 19:30 到 20:00 的日志,文件约 12.4 MB。日志中这条 SQL 的 Query_time 为 47.319 秒,Lock_time 是 0.004 秒,Rows_sent 为 50,扫描行数却是 18,642,731。
更值得注意的是,SQL 前面还有一条持续 31 秒的事务没有提交:
SET timestamp=1741853587;
BEGIN;
UPDATE t_order SET status = 3 WHERE id IN (...);
COMMIT;
# Time: 2025-03-14T19:33:08.117Z
我第一次看到这行时,判断成统计信息过期,准备直接跑 ANALYZE TABLE t_order。执行计划却给出了另一种信息:possible_keys: NULL,没有可用索引。查看表结构后,我发现 merchant_id 字段虽然有普通索引,但 status 和 created_at 不在索引里,排序只能依赖临时表和文件排序。
这不是一个简单的“缺索引”问题。订单表当时有 2,800 万行,merchant_id = 824731 对应的数据约 4.6 万行。MySQL 对候选结果做状态过滤,再按创建时间排序,成本远高于我原先的估计。
我还试过在只读副本上执行 ANALYZE TABLE t_order,命令运行了 2 分 06 秒,期间 Rows_examined 没有发生可解释的下降。SHOW INDEX FROM t_order 里 Cardinality 虽然显示为 4,662,013,但优化器依旧选择了全表扫描,后来确认统计信息并不是决定因素。这个失败尝试让我把注意力放到了列顺序上。
我又把条件拆成两条 SQL 分别测试。只带 merchant_id 时耗时 910 毫秒,扫描 5,201,443 行;加上 status=3 后耗时变成 46.3 秒,扫描行数增加到 18,642,731。原因是现有索引先找到商家数据,再逐行判断状态,最后还要把所有符合条件的记录放进临时表排序。
我没有先改线上索引
线上表数据量接近 3 TB,虽然 RDS 控制台显示可用空间 41 GB,但直接创建索引需要额外临时空间,执行时间也会影响业务。我先用只读副本验证,副本规格与主实例一致,存储 100 GB,复制延迟在 30 到 80 毫秒之间。
我先记录了原始执行计划:
EXPLAIN ANALYZE
SELECT id, order_no, buyer_id, status, amount, created_at
FROM t_order
WHERE merchant_id = 824731 AND status = 3
ORDER BY created_at DESC LIMIT 50;
-- actual time=46318.772..46318.901 rows=50 loops=1
-- Filter: (status = 3)
-- Rows: 46620
-- Sort: 46620 rows
-- Temporary table
这次验证耗时 46.5 秒,我不敢在主库上重复跑。临时表和排序占掉了大部分时间,锁等待反而不是主因。早先看到的事务持续了 31 秒,我一度以为是锁链的源头,后来用 performance_schema.data_lock_waits 检查,没有查到正在等待的锁;那条事务只是一次批量更新,和当前慢查询没有直接锁关系。
我还在副本上试过一次把索引列顺序写成 (status, merchant_id, created_at DESC)。执行计划确实不再出现临时文件排序,但测试数据里 status=3 占比达到 63%,扫描行数仍有 12,884,209,耗时 31.7 秒。这个顺序对当前商家查询不够好,我放弃了这个候选,没有拿它去改主库。
候选索引是 (merchant_id, status, created_at DESC),但线上 MySQL 8.0.32 对降序索引的支持要先确认兼容性。我没有只加一个字段,而是把排序和过滤顺序一起纳入索引,并在只读副本执行:
ALTER TABLE t_order
ADD INDEX idx_merchant_status_created (merchant_id, status, created_at DESC),
ALGORITHM=INPLACE, LOCK=NONE;
副本执行耗时 6 分 48 秒,期间磁盘 I/O 最高达到 218 MB/s,复制延迟升到 2.4 秒,但没有出现主库阻塞。执行完成后再次使用 EXPLAIN ANALYZE,耗时降到 37 毫秒,返回 50 行,Rows_examined 从 18,642,731 降到了 4,662。
我把同样的变更放在业务低峰执行,主库耗时 7 分 12 秒,峰值 CPU 约 76%。这次变更没有使用云厂商的在线 DDL 任务,也没有把“LOCK=NONE”理解成绝对无影响,实际仍要观察复制延迟和活跃事务。
应用侧的 3 秒超时暂时没有调大。调大超时只会让请求堆积,我把连接池最大连接数从 80 调整为 60,并记录每个实例的请求超时数量。发布后 10 分钟,P99 降到 380 毫秒,P95 为 210 毫秒,RDS CPU 从 54% 降到 31%,CES 没有再产生 rds006 告警。
这里我还调整了应用侧连接池的等待时间,从 2 秒改成 500 毫秒,避免数据库变慢时连接对象长期占住。这个参数不是凭感觉改的:我看了 HikariCP 的 pending 指标,发布后连接池等待请求最高达到 47 个,等待时间超过 1 秒的线程有 19 个。把最大连接数降到 60、等待时间缩到 500 毫秒后,连接创建失败从每分钟 26 次降到 0 次,但数据库本身的慢 SQL 仍然存在,所以我没有把这次应用调整当成根因修复。
费用和回滚记录
这次排查没有新增 RDS 实例,只使用了一个 2 vCPU、8 GB、100 GB 存储的只读副本,副本按 0.16 元/小时计费,连续运行 11 小时,成本约 1.76 元。主实例的存储空间和计算资源没有变化,索引增加约 3.2 GB,实际控制台账单里存储费用比前一天多了不到 1 元。
回滚脚本我提前写在发布单里:
DROP INDEX idx_merchant_status_created ON t_order;
脚本没有在生产执行,因为删除索引同样需要扫描表结构,可能造成 I/O 波动。发布单里还记录了索引创建耗时、复制延迟和回滚窗口。
线上变更完成后,SHOW CREATE TABLE t_order 确认索引已经生效,索引大小显示为 3.18 GB。连续观察 30 分钟,performance_schema.events_statements_summary_by_digest 里这条查询的平均耗时为 44 毫秒,最大耗时 129 毫秒,超时数量为 0。期间我只跑了 3 次同样的验证,没有再执行 EXPLAIN ANALYZE,因为真实请求已经能说明效果,继续压测副本只会增加不必要的 I/O。
现在还没解决的部分
这条查询已经恢复,但 t_order 的批量更新仍然会产生长事务,后续还要拆分批次并增加事务超时监控。当前我只把 merchant_id + status 的查询路径加上索引,其他商家筛选组合还没有逐一验证。
下一步我准备在测试环境模拟 3,000 万行数据,分别测试状态为空、状态分布不均和深分页场景,再决定是否补充覆盖索引。现在这次线上变更能确认有效,但它不能替代完整的查询治理。
- 点赞
- 收藏
- 关注作者
评论(0)