SQL慢查询优化:从执行计划到索引实战
一条查询从 8 秒优化到 2 毫秒,不需要改业务逻辑,只需要一个合适的索引——这是后端开发里性价比最高的优化。但很多人一遇到慢查询就“加索引”,加了没效果,或者越加越慢。SQL 优化的第一步不是动手,而是先看懂数据库到底是怎么执行你的查询的。
查询为什么会慢
数据库执行一条 SQL,先解析、再优化、最后执行。没有索引时,引擎只能全表扫描——一行一行读,直到找完所有匹配行,表越大越慢。索引本质上是一份“排好序的列的副本”,引擎可以像查字典一样直接跳到目标位置。MySQL 的 InnoDB 用 B+ 树组织索引,叶子节点有序且相连,天然支持范围查询。但索引不是越多越好:每个索引都要占用磁盘空间,每次增删改都要同步维护,索引过多反而拖慢写入。判断该不该建索引,先看执行计划。
五步优化实战
先看执行计划:在慢查询前加 EXPLAIN,重点看三个字段——type(访问类型,出现 ALL 就是全表扫描)、rows(预估扫描行数)、key(实际用到的索引)。扫描行数从百万降到几十,优化就成功了一半。
优化 WHERE 选择性:高选择性条件(能过滤掉 90% 以上数据)才适合建索引;低选择性字段(如性别)建索引可能适得其反。
组合索引遵守最左前缀:联合索引 (a,b,c) 能命中 a、a,b、a,b,c 三种查询,但跳过 a 直接用 b 就失效;把最常用的等值条件放在最左。
避免索引失效的写法:对索引列使用函数、隐式类型转换、前导通配符(LIKE ‘%xx’)都会让索引失效。
只取需要的列:SELECT * 会读所有列,无法使用覆盖索引(索引覆盖全部查询列时连表都不用回)。大表分页用游标(WHERE id 大于上次最大值)替代 OFFSET,避免深翻页。
一个真实案例
一张 50 万行的订单表,查询“status 等于 pending 且按创建时间排序”需要 8 秒。EXPLAIN 显示 type=ALL、rows=500000。建立组合索引 (status, created_at) 后,type 变成 ref,rows 降到 2000,耗时降到 20 毫秒;再把 SELECT * 改成只取 4 个需要的列,走覆盖索引,进一步降到 2 毫秒。同一个需求,8 秒到 2 毫秒,没有改一行业务代码。
常见误区
- 到处建索引:写入频繁的表索引过多,增删改反而变慢。
- 只看单条查询:一个索引可能让这条快、那条慢,要综合评估。
- 忽略执行计划:凭感觉优化,改完不知道有没有效。
- 索引失效不自知:函数包裹、隐式转换让索引白建。
行动建议
本周做一次慢查询体检:找出一条超过 1 秒的查询,按五步走——先 EXPLAIN,再决定建什么索引,建完复查执行计划确认 rows 下降;把“EXPLAIN 三看”(type、rows、key)写进团队的 SQL 评审清单,让优化从个人经验变成团队规范。