
线上系统经常出现一种诡异现象:系统刚上线性能尚可,随着业务数据量上涨,数据库CPU持续走高,部分接口响应越来越慢,严重时直接引发大量超时,甚至拖垮整个业务库⚠️。
很多故障并不是服务器硬件不足,而是慢SQL日积月累带来的恶果。一条未优化的SQL,在十万、百万级数据下,足以把数据库资源耗尽。
不少团队只懂得事后加索引临时救急,缺少完整的治理流程,同类故障反复复现。本篇文章完整覆盖故障应急、定位手段、调优技巧、代码改造、上线前置检查,提供可直接复制的SQL与Java代码,建立完整慢查询防护体系。
一、慢SQL带来的线上真实危害💥

数据库CPU持续打满,正常读写请求排队阻塞
业务接口大面积超时,前端请求报错、页面加载缓慢
锁等待、死锁风险上升,出现数据更新卡死
主库压力过大,主从延迟持续扩大,读从库数据不一致
严重场景直接数据库雪崩,整体业务不可用
重点提醒:慢查询危害会随数据量放大,小数据环境测试完全正常,上线跑一段时间后才集中爆发。本地单元测试很难发现这类隐患。
二、线上突发慢查询:第一步先应急止损✅
线上故障优先保障业务可用,不要上来就调SQL、改索引,先止损再排查根因。
1.查看数据库当前运行会话
-- 查看正在执行的SQL,Time字段代表执行耗时(秒) SHOW FULL PROCESSLIST;
找到执行时间很长的慢SQLID,执行kill杀掉会话,临时释放数据库压力。
KILL 会话ID;
2.业务侧临时应急手段
对慢查询对应接口增加限流,避免大量请求压垮数据库
热点查询临时上Redis缓存,绕开数据库查询
非核心功能临时降级,优先保障核心业务流程
三、如何精准捕获慢SQL🔍
方式1:开启MySQL慢查询日志
-- 查看慢查询配置 SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time'; -- 动态开启慢查询日志,执行超过1秒即记录 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;
生产环境建议长期开启慢查询日志,长期收集耗时异常SQL,做到问题早发现。
方式2:应用层Druid监控采集慢SQL(Java项目常用)
Druid连接池可以配置SQL监控,设置阈值自动记录耗时过高SQL,不需要依赖数据库日志。
spring: datasource: druid: filter: stat: slow-sql-millis: 1000 log-slow-sql: true
方式3:链路追踪工具定位
SkyWalking、Pinpoint这类链路组件,可以直接看到每一条数据库SQL执行耗时,快速定位是哪个业务接口产生慢查询。
四、EXPLAIN执行计划分析,定位SQL瓶颈📊

拿到慢SQL之后,不要盲目加索引,使用explain分析执行计划,判断索引是否生效。
EXPLAIN SELECT id,order_no,amount,status FROM orders WHERE user_id=10086 AND create_time > '2026‑01‑01';
重点关注几个核心字段:
type:访问类型,性能从优到劣 system > const > eq_ref > ref > range > index > ALL;尽量避免ALL全表扫描
key:实际最终使用的索引,如果为NULL代表索引失效
rows:预估扫描行数,数值越大性能越差
Extra:额外信息,出现Using filesort、Using temporary代表出现文件排序、临时表,性能较差
五、高频索引失效场景,很多项目踩坑💡

索引字段上做函数运算、类型转换,例如where date(create_time)=xxx
隐式类型转换:字符串字段传入数字,索引失效
复合索引不遵守最左匹配原则
like查询以%开头,无法命中索引
or条件一侧字段没有索引,导致整体索引失效
数据分布差异过大,MySQL优化器放弃索引,选择全表扫描
六、典型慢SQL案例优化实战
案例1:大偏移量分页问题
错误写法,offset很大,数据库扫描大量无用数据:
SELECT * FROM orders WHERE status=1 ORDER BY id DESC LIMIT 100000,20;
优化方案,主键子查询过滤,减少扫描行数:
SELECT o.* FROM orders o INNER JOIN (SELECT id FROM orders WHERE status=1 ORDER BY id DESC LIMIT 100000,20) t ON o.id = t.id;
案例2:索引字段使用函数,索引失效
错误写法:
SELECT * FROM orders WHERE DATE(create_time)='2026‑08‑01';
改写为范围查询,保留索引生效:
SELECT * FROM orders WHERE create_time >='2026‑08‑01 00:00:00' AND create_time < '2026‑08‑02 00:00:00';
七、Java业务层规避慢查询的编码实践
很多慢查询根源来自业务代码编写习惯,下面给出生产中可直接参考的编码规范。
/**
* 慢查询防护工具示例:禁止一次性大批量全量查询
*/
public class DbQueryUtil {
/**
* 分页安全查询,防止不加分页条件导致全表扫描
*/
public static void checkPageParam(Integer pageSize){
// 限制单次最大查询条数,避免一次性查出几十万行
if(pageSize != null && pageSize > 2000){
throw new RuntimeException("单次查询数量不能超过2000,请调整分页参数");
}
}
}开发编码几条硬性规范:
禁止不带任何条件直接查询全表数据,业务查询必须有限制条件、分页限制
select不要写*,只查询业务真正需要字段,减少回表开销、网络传输
批量查询in集合不能过大,in里面数值建议控制在1000以内
大表尽量避免join多张表,复杂逻辑放到业务代码处理
不要在循环里面执行数据库查询,优先批量查询
八、优化之后,必须做验证📋
修改索引、改写SQL之后,不能直接上线,需要做验证:
使用explain确认执行计划,确认type、key符合预期,不再全表扫描
模拟线上数据量级,真实测试SQL执行耗时
评估新增索引带来的写入开销,索引不是越多越好,索引会降低insert/update性能
小流量灰度上线,观察数据库监控指标,CPU、慢查询数量是否下降
九、构建事前防控,避免慢查询上线🔥
治理慢查询最高效的方式,不是故障之后救火,而是开发阶段就拦截风险SQL上线。
代码评审重点Review复杂SQL、大表查询逻辑
测试环境导入模拟大数据量,复现慢查询,小数据测不出性能问题
接入SQL审核工具,DDL、SQL变更上线前自动检测风险
线上持续监控慢查询日志,定期巡检,提前发现潜在隐患
定期梳理大表,单表数据量持续上涨,提前评估读写分离、分库分表方案
十、总结📌

慢查询治理不是简单加索引,而是一套完整闭环流程:故障应急止损 → 捕获慢SQL → explain分析定位根因 → SQL与索引优化 → 业务代码规避风险 → 上线验证 + 事前巡检防控。
很多团队只做优化,忽略事前防控,导致同类故障反复出现。数据量会持续增长,今天跑得很快的SQL,未来随着业务增长,就有可能变成慢SQL。建立常态化监控与检查机制,才可以长期保障数据库稳定运行。
我们提供数据库性能诊断、慢SQL整体治理、索引优化、大表改造、读写分离方案落地、线上故障排查等软件技术服务,帮助业务系统解决数据库性能瓶颈。
在线
电话
微信
需求
TOP