筛选索引对“删特定状态”效果好,因其仅索引满足谓词(如where status = 'archived')的子集,体积小、统计准、更新少,能直接定位目标行;需include主键列并定期更新统计信息。

为什么普通索引对“删特定状态”效果差?
比如表里有上千万行,其中 status = 'archived' 的只占 0.3%,但你建了个全表索引 IX_status,SQL Server 还是可能走全索引扫描——因为统计信息显示这个值太稀疏,优化器觉得不如直接扫主键。更糟的是,如果 status 上还有大量 NULL 或其他无效值,全表索引实际有效数据占比低,维护开销大、查询路径不精准。
用筛选索引(Filtered Index)精准覆盖目标行
筛选索引只索引满足谓词的子集,对 DELETE WHERE status = 'archived' 这类操作效果极佳。它体积小、统计准、更新少,且能直接定位目标行,避免回表或额外过滤。
- 必须用
WHERE status = 'archived'作为筛选谓词,不能写成WHERE status IN ('archived', 'deleted')—— 多值谓词无法被筛选索引精确匹配 - 推荐带上
INCLUDE主键列(如id),让索引覆盖 DELETE 的定位需求,避免额外查找:CREATE NONCLUSTERED INDEX IX_status_archived ON dbo.orders (status) INCLUDE (id) WHERE status = 'archived';
- 如果 DELETE 同时带时间范围(如
AND created_at ),筛选索引需包含该列并调整谓词:<pre class="brush:php;toolbar:false;">WHERE status = 'archived' AND created_at ,否则仍会回表过滤</pre>
DELETE 语句本身怎么配合筛选索引?
光有索引不够,语句写法必须严格匹配筛选谓词,否则优化器根本不会选它。
- WHERE 条件必须是筛选索引谓词的“逻辑等价子集”,例如索引是
WHERE status = 'archived',那么DELETE FROM orders WHERE status = 'archived'能命中;但WHERE status LIKE 'archived'或WHERE UPPER(status) = 'ARCHIVED'就失效 - 避免隐式转换:确保字段类型和字面量一致,
status是VARCHAR就别传N'archived'(除非列是NVARCHAR),否则索引失效 - 执行前务必用
EXPLAIN(SQL Server 中是SET STATISTICS XML ON或看执行计划)确认是否用了你的筛选索引,重点看Index Seek是否发生在IX_status_archived上
批量删 + 筛选索引,才是生产级组合
即使有了筛选索引,一次性删几十万行仍会锁表、压日志、拖慢其他业务。筛选索引只是加速“找”,不是解决“删”的并发问题。
- 用
DELETE TOP (5000)配合循环,每次只删一批:WHILE @@ROWCOUNT > 0 DELETE TOP (5000) FROM orders WHERE status = 'archived';
- 每批单独提交事务,别包进一个大事务——否则日志暴涨、锁持有时间翻倍
- 注意:
TOP在无序场景下可能重复删或漏删,若表有主键/唯一排序列(如id),建议加ORDER BY id并用游标方式控制进度
筛选索引真正起效的地方,是让每一小批 DELETE TOP 都能快速定位到下一批要删的 5000 行,而不是反复扫描全量索引或主键。容易忽略的是:筛选索引本身也要定期更新统计信息(UPDATE STATISTICS),尤其当归档数据比例发生显著变化后,否则优化器可能误判选择性而弃用它。










