索引未生效需先查执行计划中是否有Index Seek或Index Scan;若仅见Clustered Index Scan或Table Scan,则索引大概率被跳过,主因常为参数嗅探或谓词写法不当。

SQL Server 存储过程中索引没生效?先查执行计划里有没有 Index Seek 或 Index Scan
存储过程跑得慢,不等于索引没建——更常见的是 SQL Server 没选上你期望的索引。根本原因通常是参数化导致的“参数嗅探”或谓词写法让优化器无法利用索引。
实操建议:
- 在 SSMS 中右键执行存储过程 → “显示实际执行计划”,重点看每个
SELECT节点下方是否出现Index Seek(理想)或Index Scan(次优),如果只有Clustered Index Scan或Table Scan,说明索引大概率被跳过了 - 检查 WHERE 条件字段是否在索引最左列,比如索引是
(status, created_date),但查询只用了created_date > '2024-01-01',那这个索引基本不会被用到 - 避免在索引字段上做函数操作:
WHERE YEAR(order_date) = 2024会强制全表扫描;改成WHERE order_date >= '2024-01-01' AND order_date - 注意变量类型与字段类型是否一致:
DECLARE @id INT去查id CHAR(10)字段,隐式转换会让索引失效
哪些索引可以安全删?看 sys.dm_db_index_usage_stats 里的 user_seeks 和 last_user_seek
一个索引如果长期没被用于查找(user_seeks = 0),又没被用于排序或连接(user_scans = 0, user_lookups = 0),还持续被维护(user_updates > 0),那就是纯拖累。
实操建议:
- 运行查询时务必加上时间过滤,因为该 DMV 在服务重启后清空:
WHERE last_user_seek - 重点关注
index_id > 1的非聚集索引(index_id = 1是聚集索引,不能删) - 排除唯一约束、主键、外键依赖的索引:它们可能不常被 SEEK,但删了会破坏数据完整性
- 别只看单个数据库——用
USE [db_name]切换后查,或在查询中用DB_ID()过滤
存储过程重编译能绕过参数嗅探,但别滥用 WITH RECOMPILE
加 WITH RECOMPILE 确实能让每次执行都生成新执行计划,避开因首次调用参数“太偏”导致后续都走错路的问题。但它代价明确:CPU 升高、缓存无复用。
实操建议:
- 优先用
OPTION (RECOMPILE)加在具体慢语句末尾,而不是整个存储过程加WITH RECOMPILE——粒度更细,影响更小 - 对参数值分布极不均匀的场景才考虑:比如
@status VARCHAR(10) = 'pending'(占 95% 数据)和'cancelled'(占 0.1%)共用一个计划,必然失衡 - 测试时对比加前加后的
execution_count和total_logical_reads(从sys.dm_exec_query_stats查),别只看单次耗时
MySQL 或 PostgreSQL 存储过程里索引优化逻辑不同,别套用 SQL Server 经验
MySQL 的存储过程本身不参与查询优化——它只是包装了一组语句,真正决定索引是否生效的是每条 SELECT/UPDATE 内部的写法;PostgreSQL 的 PL/pgSQL 更接近,但执行计划缓存机制和参数绑定行为也不同。
实操建议:
- MySQL 下直接查
EXPLAIN FORMAT=TRADITIONAL对应的语句,别在存储过程里加EXPLAIN——它不支持 - PostgreSQL 中用
EXPLAIN (ANALYZE, BUFFERS)执行存储过程调用语句,看实际是否命中Index Scan,注意prepared_statement缓存可能掩盖问题 - 跨数据库迁移存储过程时,
LIKE '%abc'这种写法在所有引擎里都会让索引失效,但JSON_CONTAINS()(MySQL)或@>(PostgreSQL)是否走索引,得单独验证
索引不是建了就完事,也不是删了就快——关键在匹配查询模式。最容易被忽略的是:统计信息过期、填充因子设置不合理、以及把“看起来能用”的索引当成“实际被用”的索引。查执行计划永远比猜快。

















