sp_getapplock 必须在显式事务内调用,否则锁会残留;需用 begin transaction 包裹,并确保所有路径执行 commit 或 rollback,获取锁后须立即检查返回值。

sp_getapplock 必须在事务内调用,否则锁会残留
SQL Server 的 sp_getapplock 不是独立生效的“开关”,它默认绑定到当前事务生命周期。如果没显式开启事务,或事务中途退出(比如错误未捕获、RETURN 提前跳出),锁就不会释放,连接归还池后,下个复用该连接的请求会被静默阻塞——现象是存储过程卡住、超时,但日志里查不到死锁。
正确写法必须包裹在 BEGIN TRANSACTION 内,并确保所有出口路径都有 COMMIT 或 ROLLBACK:
- 开头加
BEGIN TRANSACTION,不能只靠隐式事务 - 锁获取后立刻检查返回值:
IF @result 就要 <code>ROLLBACK并退出,不能只看@@ERROR -
RETURN前必须有事务结束语句;异常处理块(TRY...CATCH)里也要有ROLLBACK
资源名必须带业务唯一标识,不能硬编码
用固定字符串如 'mylock' 或 'order_process' 调用 sp_getapplock,等于给整个业务线串行化——QPS 直接归零。它的设计目标从来不是“全局互斥”,而是“单笔业务单次执行”。
资源名应由稳定、不可变的业务键构造,例如:
- 订单类:
'order_' + CAST(@order_id AS VARCHAR(20)) - 优惠券核销:
'voucher_use_' + CAST(@voucher_id AS VARCHAR(20)) - 定时任务:
'job_run_' + @job_name(需确保@job_name全局唯一)
避免用时间戳、随机数或会变的字段(如用户 session_id),否则起不到互斥作用。
必须设 LockTimeout,且要检查返回值 -1 和 -2
sp_getapplock 默认 @LockTimeout = -1,即无限等待。线上环境绝不能接受——一个慢查询或长事务卡住锁,后续所有同资源请求全得排队,雪崩风险极高。
建议设为 5000(5 秒),并严格检查返回值:
-
@result = 0:成功获取锁 -
@result = -1:超时,应记录日志并返回(如SELECT 0 AS success) -
@result = -2:当前事务被选为死锁牺牲品,需重试或放弃
只检查 @@ERROR 是无效的——锁超时不会触发 SQL Server 错误,@@ERROR 仍为 0。
SELECT FOR UPDATE 不是替代方案,别混用
有人试图用 SELECT ... FOR UPDATE 模拟应用锁,这在 SQL Server 里根本不存在(那是 MySQL/PostgreSQL 的语法)。强行在 SQL Server 里写会报错 Incorrect syntax near 'FOR'。
更危险的是,在不支持的引擎上套用思路:比如误以为加了 WITH (UPDLOCK, HOLDLOCK) 就等价于 sp_getapplock。其实前者锁的是查询结果集的行,后者锁的是任意命名资源——用途完全不同。前者用于“读-改-写”链路,后者用于跨表、跨库甚至跨存储过程的协调。
真正容易被忽略的点是:应用锁失败时,业务逻辑必须能优雅降级,比如返回“任务已在处理中”,而不是抛异常中断整个调用链。










