mysql事务并发下如何扣减库存_mysql业务实现示例

来源:这里教程网 时间:2026-02-28 20:48:38 作者:

SELECT ... FOR UPDATE
防止超卖

在并发扣减库存场景下,不加锁直接

UPDATE
极易导致超卖。核心做法是先查再锁再改:用
SELECT ... FOR UPDATE
在事务内锁定目标行,确保同一商品不会被多个事务同时修改。

注意必须满足两个前提:表引擎为

InnoDB
;查询条件命中索引(如主键或唯一索引),否则会升级为表锁,严重拖慢性能。

不要写
SELECT * FROM stock WHERE sku = 'A001'
后再判断,这中间存在竞态窗口
务必把
SELECT ... FOR UPDATE
和后续
UPDATE
放在同一个事务中(
BEGIN
→ 查询 → 更新 →
COMMIT
如果查询不到记录(
sku
不存在),
FOR UPDATE
不会报错,但后续
UPDATE
将影响 0 行,需主动检查
ROW_COUNT()

扣减逻辑必须带库存校验条件

仅靠行锁不能替代业务校验。即使锁住了某行,也得确认当前库存是否足够——否则可能扣成负数。正确方式是在

UPDATE
语句里直接写条件判断。

UPDATE stock 
SET quantity = quantity - 1 
WHERE sku = 'A001' AND quantity >= 1;

执行后检查

ROW_COUNT()
返回值:

返回
1
:扣减成功
返回
0
:库存不足或记录不存在,事务应主动回滚

避免先

SELECT quantity
UPDATE
,那又回到竞态问题。

事务隔离级别建议用
READ COMMITTED

MySQL 默认是

REPEATABLE READ
,它会使用间隙锁(Gap Lock)防止幻读,但在库存类场景中容易引发不必要的锁等待甚至死锁。而
READ COMMITTED
只锁实际命中的行,无间隙锁,更轻量。

设置方式(连接级):

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
不需要全局改,应用在扣减库存的数据库连接池初始化时设置即可 确认业务能接受“同一事务中两次读可能不一致”,这对下单这种短事务通常无影响 如果已用
SELECT ... FOR UPDATE
READ COMMITTED
下锁行为更可预测,不易误锁相邻范围

别忽略连接超时和死锁重试机制

高并发下,

FOR UPDATE
等待锁、网络延迟、事务过长都可能导致超时或死锁。MySQL 报错典型有:

Lock wait timeout exceeded
innodb_lock_wait_timeout
触发)
Deadlock found when trying to get lock

应用层必须捕获这些错误并实现简单重试(比如最多 3 次),而不是直接抛给用户。伪代码逻辑:

for retry in range(3):
    try:
        BEGIN
        SELECT quantity FROM stock WHERE sku = ? FOR UPDATE;
        UPDATE stock SET quantity = quantity - 1 WHERE sku = ? AND quantity >= 1;
        COMMIT
        break
    except (DeadlockError, LockTimeoutError):
        time.sleep(0.05 * (2 ** retry))  # 指数退避
        continue

重试前强制断开并重建连接,避免复用处于异常状态的事务上下文。

相关推荐