set lock_timeout是sql server中唯一可在t-sql层控制锁等待超时的语句级机制,需在存储过程开头显式设置,仅对锁等待生效,错误号为1222,须配合try...catch与限次重试逻辑使用。

SET LOCK_TIMEOUT 是唯一可控的语句级超时机制
SQL Server 存储过程中没有“全局执行超时”开关,CommandTimeout 是客户端行为,对存储过程内部无效。真正能在 T-SQL 层直接干预锁等待的,只有 SET LOCK_TIMEOUT。它不加速查询,但能防止一条语句在锁上无限等待拖垮整个流程。
必须在存储过程开头显式设置,例如:SET LOCK_TIMEOUT 5000(单位毫秒)。设为 0 表示冲突立即失败;设为 -1(默认)等于无限等待;设为正整数才启用主动超时。
- 只对锁等待生效:如
SELECT等待 KEY LOCK、UPDATE等待 PAGE LOCK - 对 CPU 密集型操作(大排序、递归 CTE)、I/O 瓶颈、网络中断完全无效
- 错误号固定为
1222,不是死锁的1205,CATCH块里要按号区分处理
配合 TRY...CATCH 实现轻量重试,但必须限次
单纯 SET LOCK_TIMEOUT 抛出 1222 错误后就退出,业务可能直接失败。加一层重试逻辑更实用,但极易踩坑:
- 必须用局部变量计数,不能依赖全局或会话变量(并发下会串)
- 最多重试
3次,否则高并发下可能雪崩式放大阻塞 - 每次重试前加
WAITFOR DELAY '00:00:00.1',让出调度权,避免忙等 - 重试逻辑必须包裹在
TRY...CATCH内,且CATCH中只捕获1222,别吞掉其他错误
示例片段:BEGIN TRY SET LOCK_TIMEOUT 3000; UPDATE ... END TRY BEGIN CATCH IF ERROR_NUMBER() = 1222 BEGIN SET @retry = @retry + 1; IF @retry
别把 LOCK_TIMEOUT 当性能优化手段
SET LOCK_TIMEOUT 解决的是“挂起”问题,不是“慢”问题。如果存储过程频繁触发 1222,说明底层存在真实瓶颈:
- 缺少关键索引导致扫描锁表,或事务隔离级别过高(如
REPEATABLE READ)延长锁持有时间 - 参数嗅探使执行计划劣化,本该走索引却走全表扫描,锁住大量无关行
- 事务跨多个语句未及时提交,比如在应用层开启事务后,先查再改再等用户输入,锁一直不放
- 上游调用方没取完结果集,连接未关闭,导致锁滞留(常见于未读完
SELECT全部结果的应用)
这类问题靠调大 LOCK_TIMEOUT 只会让故障更隐蔽、更难定位。
远程调用和作业场景下 LOCK_TIMEOUT 仍有效,但需注意作用域
SET LOCK_TIMEOUT 是会话级设置,在存储过程内生效,所以无论是 ADO.NET、JDBC、SQL Server Agent Job 还是 SSIS 调用,只要连接未断,它都起作用。但要注意:
- SQL Server Agent Job 步骤中若勾选“使用代理账户”,则执行上下文切换,
SET语句需在作业步骤的 T-SQL 脚本开头重新写 - SSIS 执行 SQL 任务时,每个任务是独立连接,
SET必须写在该任务的 SQL 语句最前面 - 跨数据库链接(Linked Server)查询中,
LOCK_TIMEOUT只控制本地会话,远端服务器需单独配置
真正容易被忽略的是:它不继承、不跨批处理。哪怕同一连接里先执行 SET LOCK_TIMEOUT 2000,再执行 EXEC sp_executesql,后者内部仍用默认 -1,除非在动态 SQL 字符串里也显式写一遍。











