根本原因是执行计划无法复用,导致每次执行都重编译;常见触发点包括未限定schema、执行者非所有者、临时表未限定名及条件分支中对象名不完整。

直接结论:编译锁竞争不是锁太多,而是每次执行都在被迫重编译——根本原因是执行计划无法复用。
为什么 EXEC usp_xxx 会卡住一堆 SPID 在 LCK_M_X [[COMPILE]]
这不是死锁,是“编译排队”。SQL Server 发现缓存里没有可用的执行计划,必须加独占编译锁、解析、优化、生成新计划。常见触发点:
- 调用时没写 schema,比如用
EXEC usp_pay_order而不是EXEC dbo.usp_pay_order - 执行者不是存储过程所有者(如 appuser 执行 dbo.usp_xxx,且没显式写
dbo.) - 过程内用了
SELECT INTO #tmp或未限定名的临时表引用,导致延迟解析 - 过程里写了
IF @flag = 1 SELECT * FROM Orders这类条件分支,且分支中对象名未完全限定
强制复用执行计划的四个实操动作
不靠运气,靠命名规范和显式控制:
- 所有对象引用必须带 schema:把
SELECT * FROM Orders改成SELECT * FROM dbo.Orders,连临时表创建也要写CREATE TABLE #tmp (id INT)(注意:#tmp 本身不能加 schema,但后续 JOIN 必须用两段式) - 调用存储过程时,始终带上所有者:
EXEC dbo.usp_pay_order @order_id = 123,别省略dbo. - 禁用隐式事务:
SET IMPLICIT_TRANSACTIONS OFF开头加一句,避免意外延长事务生命周期,干扰编译上下文 - 对高频过程,用
sp_recompile主动标记过期(仅限已知结构变更后),而不是等运行时崩了再修;别依赖自动清除
怎么确认问题真被解决了
别只看是否“不卡”,要查缓存行为:
- 运行
SELECT object_name(object_id), plan_generation_num, execution_count FROM sys.dm_exec_query_stats s CROSS APPLY sys.dm_exec_sql_text(s.sql_handle) t WHERE t.text LIKE '%usp_pay_order%',如果plan_generation_num稳定为 1,说明计划真正复用了 - 查
sys.dm_exec_cached_plans中对应plan_handle的 XML,搜索Reason="SchemaChanged"或Reason="DeferredCompile"—— 这两个词消失,才是关键信号 - 监控
sys.dm_exec_requests,waitresource字段不再出现OBJECT: dbid:object_id [[COMPILE]]
最容易被忽略的是:即使你改完了所有代码,连接池里还可能缓着旧连接,它们第一次执行仍会触发编译。上线后观察前 5 分钟的 waitstats,比看日志更真实。











