数据库慢查询的分层排查:从慢日志到执行计划的一次复盘
引言
上周一次接口超时报警,把我们带回一个老问题:数据库变慢了。日志里只有超时,监控只显示连接数上涨,真正原因藏在几层之下。这次复盘不讲单一技巧,而是讲分层排查:先确认慢在哪一层,再收敛范围,最后才改动。
第一层:区分数据库慢,还是应用在等
接口耗时高,不等于数据库执行慢。先对比三组时间:应用端总耗时、数据库侧执行耗时、排队等待连接的时间。总耗时远大于执行耗时,问题可能在连接池、网络或线程等待;执行耗时本身高,再往数据库内部查。这次属于后者,慢日志里同一查询反复出现,平均耗时从几十毫秒涨到两秒以上。
第二层:用慢日志排序,而不是凭感觉猜
慢日志的价值在于可以排序。按出现次数乘以平均耗时排序,优先处理总耗时占比最高的那几条,而不是单次最慢的那一条。单次最慢可能是冷缓存或偶发大结果集,高频慢才是稳定成本。我们把目标查询抽出来,在只读环境单独重放,确认它在没有业务流量时仍然慢,先排除锁等待和突发流量的干扰。
第三层:看执行计划,别只看 SQL 文本
SQL 写得短,不代表执行得快。执行计划重点看四点:是否走了预期索引,扫描行数与返回行数的差距,是否出现临时表或文件排序,连接顺序是否合理。这次查询走了索引,但扫描行数是返回行数的几十倍,原因是范围条件放在索引列之后,索引只能用到前半部分。把等值条件前置并重新评估范围条件后,扫描量明显下降。索引存在却未被选中时,也要先确认统计信息是否过期,再看选择性是否真的支持这条索引。
第四层:看锁与并发,别只看单条查询
单条查询空闲时很快、高峰期很慢,往往与锁和并发有关。长事务持有锁,会阻塞后面的更新;更新又会阻塞读,读超时反过来拖住连接池。排查时要同时看运行中的事务、等待事件和锁等待链。我们的做法是缩短事务边界,把只读查询移出写事务,把大批量更新拆成小批次。一次提交几万行看似省事,实际上锁持有时间长、回滚代价大,拆小后整体反而更稳。
第五层:看数据增长与返回量
查询合理、索引合理,但数据量一直在涨,慢就是迟早的事。要看表数据量、索引大小、分区情况,以及查询是否真的需要返回那么多列。只取需要的列、限制返回行数、按时间分区裁剪,都是不改业务语义的降扫描方法。我们最后把历史数据按月归档,热表只保留近期数据,查询默认带时间范围,避免全表扫描旧数据。
检查清单与结语
复盘后形成一张清单:确认耗时发生在应用、连接还是执行;用慢日志按总耗时排序;看执行计划的扫描量与排序;检查长事务与锁等待链;评估数据增长与返回量。每一步留证据,包括执行计划前后对比和改动前后的耗时分布。慢查询排查不是一次用上所有手段,而是按层收敛,改动越小、证据越清楚,线上风险就越低。
- 点赞
- 收藏
- 关注作者
评论(0)