DELETE慢主因是锁阻塞、未走索引或执行计划退化;需查sys.dm_tran_locks定位持锁会话,用SET STATISTICS XML ON分析执行计划,检查等待类型及参数嗅探问题。

DELETE 慢,大概率不是语句本身写错了,而是被锁、没走索引、或者执行计划退化了。先别改SQL,直接查阻塞和执行计划。
查有没有事务在挡路
SQL Server 的 DELETE 会等锁,而锁往往来自另一个还没提交的事务。光看 sp_who2 或 sys.dm_exec_requests 不够,它不显示谁持有了锁。
- 用
sys.dm_tran_locks查当前所有锁:重点关注resource_type = 'OBJECT'或'KEY'、request_mode IN ('X', 'U', 'S')、request_status = 'WAIT'的行 - 连查
sys.dm_exec_sessions和sys.dm_exec_requests,把blocking_session_id非 0 的会话揪出来,看它的last_request_end_time和status - 特别注意
status = 'sleeping'但open_transaction_count > 0的会话——这是典型的“开了事务没提交”,可能卡了几小时
确认是否走了索引
没走索引的 DELETE 就是全表扫描 + 全表加锁,哪怕只删一行,也会锁住整个表。用 SET STATISTICS XML ON 后执行 DELETE,看执行计划里 Index Seek 还是 Clustered Index Scan。
- WHERE 条件字段没建索引?立刻补,优先建覆盖索引(比如
CREATE INDEX ix_user_del ON orders(user_id) INCLUDE (status)) - 用了函数或类型转换?比如
WHERE CAST(order_no AS VARCHAR) = '123',会导致索引失效 - 复合索引顺序错位?索引是
(a, b, c),却用WHERE b = 1 AND c = 2,优化器很可能弃用索引 - 统计信息过期?运行
UPDATE STATISTICS orders WITH FULLSCAN再试一次
看等待类型是不是瓶颈
如果 DELETE 长时间没反应,不是卡在锁上,就可能是 I/O、日志或内存问题。查 sys.dm_os_wait_stats 里最近增长最快的等待类型:
-
WRITELOG高 → 日志写入慢,检查磁盘延迟、日志文件是否自动增长频繁、是否和数据文件混在同一个物理盘 -
LCK_M_X持续高 → 锁竞争严重,不只是你这句 DELETE 的问题,得查上游事务模式 -
PAGEIOLATCH_*高 → 数据页读取慢,可能是缓存不足或存储性能差,结合sys.dm_os_memory_clerks看 buffer pool 使用情况 -
ASYNC_NETWORK_IO高 → 客户端取结果太慢(虽然 DELETE 不返回结果集,但如果触发了触发器或有 OUTPUT 子句就可能)
警惕参数嗅探导致执行计划错乱
DELETE 在存储过程中执行时,如果用了参数(比如 @user_id),SQL Server 可能基于第一次调用的值生成执行计划。当某次传入一个低选择性值(如 @user_id = 0),优化器选了全表扫描,后续所有调用都复用这个烂计划。
- 临时验证:加
OPTION (RECOMPILE)强制重编译,看速度是否恢复 - 长期方案:在存储过程里对关键参数加
OPTION (OPTIMIZE FOR (@user_id = 123)),或改用局部变量绕过嗅探(DECLARE @u INT = @user_id; WHERE user_id = @u) - 别依赖
WITH RECOMPILE—— 它只影响存储过程级别,不保证每次 DELETE 都重编译
真正难搞的 DELETE 慢,往往藏在“看不见”的地方:一个三天前开启却没提交的事务、统计信息半年没更新、日志文件自动增长设成了 1MB 每次、甚至客户端连接字符串里没加 ApplicationIntent=ReadOnly 导致误走到主库。排查时别只盯着那条 DELETE 语句本身。

















