最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
怎样解决SQL存储过程在多线程并发下的数据争抢问题?
时间:2026-07-11 09:45:03 编辑:袖梨 来源:一聚教程网
优先使用 UPDATE ... WHERE 带条件校验,因其是原子操作,可避免超卖;仅当业务逻辑无法单条 SQL 实现时才加锁,且需规范使用事务、锁名和超时设置。
优先用 UPDATE ... WHERE 带条件校验,而不是先查再更新——这是最轻量、最可靠、也最容易被忽略的解法。
为什么 UPDATE ... WHERE 比 SELECT + UPDATE 更安全
“先查库存再扣减”看似合理,但两个线程几乎同时执行时,会都看到 stock = 10,都判断“够扣”,然后双双执行 UPDATE,结果 stock 变成 8,超卖 2 单。这个时间窗口无法靠 Sleep 或重试堵住。
UPDATE products SET stock = stock - 1 WHERE id = 1001 AND stock >= 1 是原子操作:条件不满足就一整条语句不生效,根本不会进入“扣减逻辑”。执行后只需检查影响行数:
- MySQL 用
ROW_COUNT() - PostgreSQL 用
pg_affected_rows() - SQL Server 用
@@ROWCOUNT
返回 0 就说明失败(已被别人抢走或已售罄),无需锁、不依赖隔离级别、也不需要额外字段。
什么时候必须加锁?怎么加才不卡死
当业务逻辑无法塞进单条 UPDATE 的 WHERE 条件里(比如要跨多张表校验、调外部接口后再决定是否扣减),才考虑锁。但锁极易滥用:
- SQL Server 的
sp_getapplock必须包裹在BEGIN TRANSACTION内,且事务结束前不能退出存储过程,否则锁残留 - 锁名必须带业务上下文,例如
'order_pay_' + CAST(@order_id AS VARCHAR(20)),硬编码'mylock'会让所有请求串行 -
@LockTimeout必须设具体毫秒值(如 5000),别用默认 -1(无限等待) - 必须检查返回值:
IF @result 表示失败(-1=超时,-2=死锁牺牲品),不能只看 <code>@@ERROR
MySQL 怎么模拟应用级锁
MySQL 没有 sp_getapplock 这类事务安全的应用锁,GET_LOCK() 是唯一跨会话方案,但它本身不是事务安全的:
- 锁名必须唯一稳定,推荐格式:
'db_shop_voucher_use_' + CAST(@voucher_id AS CHAR),纯数字或固定字符串极易冲突 -
RELEASE_LOCK()必须出现在两个地方:正常流程末尾 +EXIT HANDLER异常处理器里,否则一次崩溃就可能让锁永远挂住 -
IS_USED_LOCK()只能用于诊断,不能用来轮询等待——它自己也会加锁,高并发下反而成瓶颈
视图、临时表、自增 ID 在并发下特别容易翻车
这些不是“只读就安全”的东西:
- 视图查询会真实访问基表,若含无索引
JOIN或WHERE字段,容易触发大量 S 锁,和正在UPDATE的事务互相等待,直接死锁 - 多线程用
SELECT MAX(oid)算新 ID 插入,必然错乱——A 和 B 线程同时读到 oid=100,都插 101,主键冲突或数据错位 - 正确做法是建一张
max_oid表,用UPDATE max_oid SET current_val = current_val + N原子获取一批连续 ID,再插入
真正难的不是写锁,而是判断哪一步真需要锁——90% 的并发问题,其实一条带条件的 UPDATE 就能解决,剩下 10% 才轮到锁和事务设计。但多数人一上来就加锁,反而把系统拖慢。