存储过程执行慢八成因索引未被正确使用,而非逻辑问题;需检查执行计划中type、key、rows、extra等字段,确认是否走索引、是否覆盖、有无隐式转换或函数导致失效。

存储过程执行慢,八成不是逻辑问题,而是执行计划没走对索引、或者根本没用上索引——光加索引不等于有效,得看查询是否真正“覆盖”了所需字段,也得看执行计划里 key 和 Extra 字段说了什么。
为什么加了索引,存储过程还是全表扫描?
常见现象是:明明给 WHERE 条件列建了索引,EXPLAIN 却显示 type=ALL 或 key=NULL。这不是索引失效就是查询写法拦住了优化器。
- 在
WHERE子句中对字段用函数,比如WHERE YEAR(create_time) = 2024,哪怕create_time有索引也会跳过 - 隐式类型转换,比如参数传的是字符串
'123',但字段是INT,MySQL 会转成CAST('123' AS SIGNED),导致索引失效 -
OR连接多个条件时,只要其中一边不能走索引(比如没索引字段),整个条件可能退化为全表扫描;改用UNION ALL拆开更可控 - 复合索引顺序错位,比如建了
INDEX idx_status_date (status, create_date),但查询只用了WHERE create_date > '2024-01-01',那这个索引完全用不上
怎么判断是不是索引覆盖(Covering Index)生效了?
索引覆盖的核心是:查询所有字段(包括 SELECT、WHERE、ORDER BY、GROUP BY 涉及的列)都包含在同一个索引里,这样引擎不用回表查聚簇索引,直接从二级索引拿完数据。
- 看
EXPLAIN的Extra字段:出现Using index表示命中覆盖,Using where; Using index是理想状态;若出现Using filesort或Using temporary,说明排序或分组没被索引支持 - 例如查询
SELECT id, status FROM orders WHERE status = 'shipped' ORDER BY create_date,要覆盖就得建INDEX idx_status_date (status, create_date, id)——注意id放最后,因为它是主键,用于避免回表 - 不要盲目把所有字段塞进索引:宽度越大,索引体积越大、维护成本越高,写多读少的场景反而拖慢整体性能
执行计划里哪些字段必须盯紧?
别只扫一眼 type,rows 和 Extra 才暴露真实代价。尤其在存储过程中嵌套调用时,这些值容易被掩盖。
-
rows值远大于实际返回行数?说明统计信息过期,运行ANALYZE TABLE orders更新一下 -
key显示用了索引,但key_len比预期小(比如定义是VARCHAR(255)却只用了 768 字节),可能是前缀索引或字符集导致截断,得检查SHOW INDEX FROM orders -
Extra出现Using index condition是好信号(ICP,索引条件下推),说明 MySQL 把部分WHERE下推到存储引擎层过滤;但若同时有Using where,说明还有剩余条件在 Server 层做,仍需关注 - 在 SQL Server 中,还要查
sys.dm_exec_query_stats看plan_generation_num,大于 1 就说明该存储过程被反复重编译,再好的索引也白搭
存储过程里怎么让执行计划稳定不飘?
参数一变,计划就换,尤其是首次调用用了边缘值,后续都跟着跑歪——这不是 bug,是 SQL Server 的参数嗅探机制在起作用。
- 别一上来就加
OPTION (RECOMPILE),它会让每次执行都丢计划、CPU 翻倍;优先用OPTIMIZE FOR (@status = 'Active'),告诉优化器按典型值生成计划 - 如果分支太多(比如
IF @mode = 'A' ... ELSE IF @mode = 'B'),拆成多个独立存储过程,比堆在一个里更容易复用计划 - 确认所有调用上下文
SET选项一致:一个地方SET ARITHABORT ON,另一个没设,SQL Server 就当两个不同过程,各自编译 - 避免在过程里拼
@sql NVARCHAR(MAX)再EXEC(@sql),这等于主动放弃计划缓存;改用sp_executesql,带参数定义,才能复用
索引和执行计划不是设完就一劳永逸的事。表数据量翻倍、统计信息三个月没更新、应用层悄悄改了传参类型……都可能让昨天还飞快的过程今天卡住。最危险的,是看到 Using index 就以为万事大吉,却没核对 rows 是否合理、key_len 是否完整利用。










