存储过程不选择索引,真正起作用的是其内部sql语句执行时优化器对基表所用的索引;需确保where条件sargable、复合索引字段顺序合理、高频查询覆盖、select列通过include避免key lookup,并兼顾join和order by字段索引设计。

存储过程本身不“选择”索引——真正起作用的是它内部每一条 SELECT、UPDATE 或 DELETE 语句实际执行时,优化器对基表所用的索引。所谓“为存储过程选索引”,本质是为它的核心 SQL 语句匹配最有效的索引结构。
WHERE 条件字段必须覆盖,且顺序要对
优化器能否走索引,第一关看 WHERE 是否满足 SARGable(可搜索参数)条件。函数包装、类型转换、LIKE '%abc' 都会让索引失效。
- ✅ 正确写法:
WHERE OrderDate >= '2024-01-01' AND UserID = 123 - ❌ 错误写法:
WHERE YEAR(OrderDate) = 2024或WHERE UPPER(Email) = 'A@B.COM' - 复合索引字段顺序必须匹配查询中等值条件优先、范围条件靠后的原则。例如查
WHERE Status = 'paid' AND CreateTime > '2024-06-01',索引应建为(Status, CreateTime),而不是反过来 - 如果存储过程中存在多个不同
WHERE组合(如 A=1、A=1 AND B=2、B=2),优先保障高频+高选择性组合,低频分支可考虑拆成独立语句或加OPTION (RECOMPILE)
SELECT 列要进索引,否则容易 Key Lookup
当 SELECT 返回的列不在索引键中,SQL Server 会回表查数据行(Key Lookup),IO 成倍增加。尤其在循环调用或大数据集上,性能断崖式下跌。
- 观察执行计划:出现黄色警告图标 + “Key Lookup” 操作,就是信号
- 解决方式不是堆字段进索引键,而是用
INCLUDE把非过滤/排序字段塞进去。例如:CREATE NONCLUSTERED INDEX IX_UserID_Incl ON Orders(UserID) INCLUDE (OrderNo, Amount, Status) -
INCLUDE列不参与排序和查找逻辑,只作“附赠数据”,体积小、维护成本低 - 避免把
TEXT、XML、大VARCHAR(MAX)放进INCLUDE,可能触发页拆分或内存压力
JOIN 和 ORDER BY 字段也要纳入索引设计
存储过程里常有 JOIN 多表或 ORDER BY 排序逻辑,这两类操作同样依赖索引支撑,但容易被忽略。
-
JOIN条件字段(通常是外键)必须有索引,否则驱动表小也救不了嵌套循环的灾难性扫描 -
ORDER BY若无法利用索引排序,就会触发Sort运算符——内存或 TempDB 排序开销巨大。理想情况是索引顺序与ORDER BY完全一致,且无反向(DESC)混用 - 例如:
SELECT * FROM Orders o JOIN Users u ON o.UserID = u.ID WHERE o.Status = 'shipped' ORDER BY o.CreateTime DESC,最佳索引是(Status, CreateTime) INCLUDE (UserID),同时确保Users(ID)有主键或唯一索引 - 如果
ORDER BY含多列(如ORDER BY a, b),索引必须以相同顺序包含它们,且不能中间跳过
强制索引提示(INDEX / FORCE INDEX)只是临时止血,不是长期方案
在存储过程里硬写 WITH (INDEX(...)) 或 FORCE INDEX,往往说明底层索引或统计信息已失控。它能绕过优化器决策,但风险极高。
- SQL Server 中
WITH (INDEX(ix_name))必须紧贴表名后、AS别名前,位置错一个字符就报错或静默失效 - MySQL 的
FORCE INDEX必须在FROM子句表名后、WHERE前,写在后面直接语法错误 - 视图内无法生效:存储过程查视图时加提示,SQL Server 报错,MySQL 忽略——提示根本传不到基表
- 最大隐患是“假成功”:索引还在,但数据分布变了(比如某状态值从 3% 变成 40%),强制走索引反而比全表扫描还慢,且毫无预警
真正该做的,是定期更新统计信息、检查碎片、用 sys.dm_db_missing_index_details 或 performance_schema 找缺失索引,而不是在存储过程里埋一堆脆弱的提示。











