01为什么先读后写容易出错
假设剩余数量为一,两次请求先后读到了相同的旧值,各自认为可以继续扣减。业务检查虽然执行了两遍,却没有与最终写入形成可靠的并发约束。
问题在于读取结果可能已经过时。实现时要先确定哪些检查与修改必须一起成功,再选择条件更新或事务内的锁定读取。
02简单扣减可以使用条件更新
对于扣一次这样的操作,可以把剩余数量条件直接放进更新语句。实际执行时,下面的问号应由数据库驱动绑定参数。
应用必须检查受影响行数:一行表示更新成功,零行表示没有符合条件的记录,不能仍然向用户返回成功。还需要在接口中验证用户权限和输入。
SQL
UPDATE credits
SET remaining = remaining - 1
WHERE tenant_id = ?
AND id = ?
AND remaining >= 1;
-- 应用检查 affected rows,再决定返回结果。03多步修改要在同一个事务里完成
如果扣减数量时还要写入使用明细,应把相关修改放在同一个事务里。需要先读取并据此作出判断时,可以在事务中使用 SELECT ... FOR UPDATE,读取和后续写入使用同一连接。
事务结束要明确提交或回滚,并保持访问记录的顺序尽量一致。网络重试还可能导致重复请求,这类问题需要额外的请求标识和唯一约束;仅有事务并不等于已经处理了重复提交。
进一步阅读
下列文档用于延伸阅读,文中示例是围绕具体问题编写的简化示例。