sp_getapplock必须在begin transaction内调用,锁名需带业务上下文如'order_pay_'+cast(@order_id as varchar(20)),@locktimeout设具体毫秒值(如5000),并严格检查返回值(

UPDATE + WHERE 条件校验为什么比先查后更新更可靠
“先SELECT再UPDATE”看似自然,但中间存在不可控的时间窗口——两个事务几乎同时查到相同库存,都判断“够扣”,结果双双执行UPDATE,超卖就发生了。原子写法直接堵死这个窗口。
-
UPDATE products SET stock = stock - 1 WHERE id = 1001 AND stock >= 1执行后必须检查影响行数:ROW_COUNT()(MySQL)、pg_affected_rows()(PostgreSQL)或@@ROWCOUNT(SQL Server)为 0,说明条件不满足(已售罄或被别人扣了) - 适用于逻辑简单、判断条件能直接写进
WHERE的场景,比如“余额大于 X”“状态为 pending” - 避免在浮点字段上做精确值比对(精度误差会导致条件失效)
- 如果业务需要记录修改次数或防止重复提交,再补一个
version字段做乐观锁
SQL Server 里 sp_getapplock 怎么调用才不残留锁
sp_getapplock 是轻量应用锁,但默认绑定事务——没 COMMIT 或 ROLLBACK,锁就一直挂着,连接池复用后新请求会被静默阻塞。
- 锁操作必须包裹在
BEGIN TRANSACTION内,且事务结束前不能退出存储过程 -
@Resource参数要带业务上下文,例如'order_pay_' + CAST(@order_id AS VARCHAR(20)),硬编码'mylock'会锁住所有请求 -
@LockTimeout必须设具体毫秒值,比如5000;别用默认-1(无限等待) - 必须检查返回值:
IF @result 就表示失败(<code>-1=超时,-2=死锁牺牲品),不能只看@@ERROR
MySQL 中模拟行级应用锁只能靠 GET_LOCK() 吗
MySQL 没有内置应用锁函数,GET_LOCK() 是唯一跨会话的命名锁方案,但它不是事务安全的:连接断开、异常退出、或忘记调用 RELEASE_LOCK(),锁就永远留在那里。
- 锁名要唯一稳定,推荐格式:
'db_shop_voucher_use_' + CAST(@voucher_id AS CHAR),纯数字或固定字符串极易冲突 -
RELEASE_LOCK()必须出现在两个地方:正常流程末尾 +EXIT HANDLER异常处理器里,否则一次崩溃就可能让整条业务线卡住 -
IS_USED_LOCK()只能用于诊断,不能用来轮询等待——它本身会加锁,高并发下反而成瓶颈
SQL Server 存储过程中怎么用 ROWVERSION 判断冲突
ROWVERSION 实现乐观锁并发控制,仅检测修改不自动重试;比手写 version int 更可靠,因其由引擎强制更新、不可跳过、单事务内只变一次、无需索引即可高效等值校验。
- 必须把原始
ROWVERSION值作为参数传入存储过程,不能在过程中重新SELECT获取 -
UPDATE语句必须同时包含主键条件和ROWVERSION条件,例如:UPDATE products SET name = @name, price = @price WHERE id = @id AND rowversion = @orig_rowversion - 执行后必须检查
@@ROWCOUNT:为 0 表示更新失败(已被他人修改),此时应抛出特定错误,而不是静默忽略 - 不要在
WHERE中用rowversion 或其他非等值判断——<code>ROWVERSION不保证单调递增可比,只适合相等校验
真正难的不是选哪种机制,而是把条件写进 WHERE 的那一刻是否覆盖了全部业务约束,以及影响行数检查是否落在每一条关键 UPDATE 后面。漏掉一次,就等于给并发留了一道缝。











