SQL 查询变慢,本质可归为七类根因:全表扫描、索引失效、深分页、慢 JOIN、锁等待/长事务、N+1 查询、配置与统计信息偏差。优化方法论也对应分层——先改 SQL 与索引(成本最低、见效最快),再调整事务与架构,最后才是硬件与参数。据腾讯云开发者社区与 CloudBase 文档的总结,超过六成的慢查询源于”索引未命中或回表开销过大”,因此定位瓶颈的第一步永远是 EXPLAIN,而非盲目加索引或升级机器。
一、七大类慢查询根因
据磐维数据库慢 SQL 排查、腾讯云开发者社区”常见慢 SQL 原因与解决方案对照表”、CSDN 调优文章,慢查询高频成因如下:
| 根因 | 典型表现 | 触发场景 |
|---|---|---|
| 全表扫描 | 执行计划 type=ALL、大表逐行扫 |
过滤列无索引 |
| 索引失效 | key=NULL 但应有索引 |
函数/运算、隐式转换、LIKE '%x'、违反最左前缀 |
| 深分页 | LIMIT 100000,10 越翻越慢 |
OFFSET 过大,前置数据被扫描丢弃 |
| 慢 JOIN | 驱动表选错、关联列无索引 | 大表当前驱、关联列类型不一致 |
| 锁等待/长事务 | 会话等待事件为锁、事务 idle in transaction | 一个事务内含多步甚至 RPC 调用 |
| N+1 查询 | 循环查关联表 | 查列表后逐条查明细 |
| 统计/配置偏差 | 估算行数与实际差距大 | 统计信息过期、buffer_pool 不足 |
据头部云厂商 2026 年《数据库性能白皮书》(kd.cloud 引述),超过 60% 的慢查询源于索引未命中或回表开销过大——这说明多数慢查询是逻辑设计问题,不是物理资源问题。
二、根因一:全表扫描与索引缺失
CockroachDB 官方文档用一组对比数据说明问题:在 125 万行的 users 表上按 name 查询,无二级索引时 EXPLAIN 显示 FULL SCAN、耗时约 981ms;给 name 建索引后,spans: /'Cheyenne Smith' - /'Cheyenne Smith' 直接跳到目标值,耗时降到 4ms,提升约 240 倍。
修复:为高频过滤列建索引;若查询只取部分列,把所需列一并放入索引做成覆盖索引,避免回表。CloudBase 文档指出,复合索引中列序很重要——等值列在前、排序列在后,例如 (user_id, created_at DESC) 同时服务过滤与排序。
三、根因二:索引失效
索引失效的写法高度固定(综合 CSDN、腾讯云、kd.cloud):
WHERE列套函数:DATE(create_time)='...'→ 改成范围比较。- 隐式类型转换:
phone为VARCHAR却传入数字(如13800000000),数据库会做CAST触发全表扫描;应写成phone = '13800000000'。 - 前导模糊:
LIKE '%x'→ 改成右匹配LIKE 'x%'。 - 违反最左前缀:联合索引
(a,b,c)只查b→ 补最左列或调整索引顺序。 OR连接无索引列、IN超大集合(建议控制在 1000 以内)。
这些写法的共同点是破坏了 B+ 树的有序性,使优化器无法遍历索引(详见 13137、13138 篇)。
四、根因三:深分页
MySQL 用 LIMIT n OFFSET m 翻页,当 OFFSET 很大时,数据库要先扫描并丢弃前 m 行。腾讯云文章指出:LIMIT 100000, 10 实际会扫过前 10 万行;若优化器放弃索引改全扫,更雪上加霜。
两种优化路线:
-- 游标分页:用上一页末的主键续查,只扫目标行
SELECT id, title FROM todos WHERE id > 10000 ORDER BY id LIMIT 20;
-- 延迟关联:先取主键,再回表取详情
SELECT * FROM orders
WHERE id >= (SELECT id FROM orders ORDER BY id LIMIT 100000, 1)
ORDER BY id LIMIT 10;
CSDN 调优文章强调,延迟关联”先取主键、再关联”能大幅减少扫描行数;覆盖索引可进一步避免回表。
五、根因四:慢 JOIN 与 N+1
多表关联慢,常因驱动表选错或关联列无索引(见 13129 篇)。N+1 则是应用层循环查库——查订单列表后逐条查用户,应改为一次 WHERE id IN (...) 或 JOIN。腾讯云开发者社区把”N+1 查询”与”大分页”并列为应用层两大典型坑。
优化顺序(据 CSDN 调优):
- 先用
WHERE减少数据量。 - 再优化 JOIN 顺序(小表驱动大表、关联列建索引)。
- 最后优化单行操作,避免
SELECT *。
六、根因五:锁等待与长事务
长事务是隐蔽的性能杀手。CSDN 的电商下单案例给出鲜明对比:一个事务串行执行”锁库存→建订单→写详情→更积分”四步不提交,行锁持有几百毫秒到 1 秒,并发下触发锁等待与死锁,MVCC 版本链堆积让普通查询变慢;拆成多个短事务、把积分更新异步化后,单事务压到几十毫秒,锁快速释放。
磐维数据库排查清单给出方向:缩短事务、禁止事务内空闲等待、配置合理超时、定期清理挂起事务。腾讯云开发者社区补一句:长事务持有行锁会让其他请求排队,应把非数据库操作(如 RPC 调用)移出事务。
七、分层优化实战步骤
把下列顺序作为调优标准流程(综合 CloudBase、GreatSQL、腾讯云、磐维):
- 捕获慢 SQL:开启慢查询日志(
long_query_time)、用pt-query-digest汇总排序,优先优化耗时 TOP 的语句。 - 看执行计划:
EXPLAIN查全扫/重排序/昂贵嵌套循环;EXPLAIN ANALYZE核对估算与实际。 - 加/调索引:高频过滤、排序、关联列建索引;优先覆盖索引减少回表。
- 刷新统计:
ANALYZE TABLE(MySQL)或ANALYZE(PG),让优化器拿到正确数据分布。 - 改写 SQL:深��页改游标/延迟关联,N+1 改批量/JOIN,杜绝
SELECT *。 - 治事务与架构:拆大事务、读写分离、超千万行考虑分表、热点数据上缓存。
- 验证效果:重跑
EXPLAIN与真实耗时,确认扫描行数与资源占用下降,避免”虚假优化”。
八、长效预防
磐维与腾讯云都强调”防复发”机制:上线前用 Druid/SonarQube 做 SQL 审核,禁止大表无索引查询与超大分页上线;常态化监控慢查询与锁等待;定期更新统计信息、清理冗余索引、回收表碎片;配置 innodb_buffer_pool_size、连接池上限与超时参数。
常见问题(FAQ)
慢查询一定是缺索引吗?
不是。深分页、长事务、N+1、统计过期、连接池失衡都可能致慢,需 EXPLAIN 定位。
索引建了还慢怎么办?
看是否失效(函数/转换/最左前缀)或回表过多;用覆盖索引消除回表,必要时 ANALYZE 刷新统计。
深分页除了游标还能怎么改?
可用延迟关联先取主键再回表;覆盖索引也能减少回表,但 OFFSET 过大时游标分页最稳。