← 返回文章列表
数据库

MySQL 并发更新:先想清楚事务边界

两次请求同时修改剩余数量时,普通的先读后写可能丢失更新。把检查与写入放到正确的位置。

01为什么先读后写容易出错

假设剩余数量为一,两次请求先后读到了相同的旧值,各自认为可以继续扣减。业务检查虽然执行了两遍,却没有与最终写入形成可靠的并发约束。

问题在于读取结果可能已经过时。实现时要先确定哪些检查与修改必须一起成功,再选择条件更新或事务内的锁定读取。

02简单扣减可以使用条件更新

对于扣一次这样的操作,可以把剩余数量条件直接放进更新语句。实际执行时,下面的问号应由数据库驱动绑定参数。

应用必须检查受影响行数:一行表示更新成功,零行表示没有符合条件的记录,不能仍然向用户返回成功。还需要在接口中验证用户权限和输入。

SQL
UPDATE credits
SET remaining = remaining - 1
WHERE tenant_id = ?
  AND id = ?
  AND remaining >= 1;

-- 应用检查 affected rows,再决定返回结果。

03多步修改要在同一个事务里完成

如果扣减数量时还要写入使用明细,应把相关修改放在同一个事务里。需要先读取并据此作出判断时,可以在事务中使用 SELECT ... FOR UPDATE,读取和后续写入使用同一连接。

事务结束要明确提交或回滚,并保持访问记录的顺序尽量一致。网络重试还可能导致重复请求,这类问题需要额外的请求标识和唯一约束;仅有事务并不等于已经处理了重复提交。

进一步阅读

下列文档用于延伸阅读,文中示例是围绕具体问题编写的简化示例。