我在“用户”表上有一个字段,用于保存用户的帐户余额。用户可以使用我的服务执行很多操作,这将导致他们的余额快速变化。
我正在尝试使用mysql的可序列化隔离级别来确保多个用户操作不会错误地更新该值。 (动作A和动作B同时希望从余额中扣除1美元。)但是,我遇到了很多死锁错误。
我如何正确地做到这一点而又不陷入所有僵局,又保持余额字段为最新状态?
简单模式:用户有一个ID和一个余额。
我正在使用教义,所以我正在做以下事情:
$con->beginTransaction();
$tx = $con->transaction;
$tx->setIsolation('SERIALIZABLE');
$user = UserTable::getInstance()->find($userId);
$user->setBalance($user->getBalance() + $change);
$user->save();
$con->commit();
最佳答案
首先尝试在事务上使用可序列化隔离级别是一个好主意。这意味着您至少至少了解什么是转换,并且隔离级别是最大的问题之一。
请注意,可序列化并不是真正的可序列化。 this previous answer上的更多内容,您将有时间阅读它:-)。
但是最重要的部分是,您应该考虑到由于可序列化失败而对事务进行自动回滚是正常现象,并且正确的做法是构建应用程序以使事务可能失败并应重播。
一个简单的解决方案,对于会计问题,我喜欢这个简单的解决方案,因为我们可以预测所有事实,没有任何意外,因此,一种解决方案是执行表锁定。这不是一个精致而优雅的解决方案,没有行级锁,只有简单的大表锁(并且总是以相同的顺序)。之后,您可以作为单个播放器进行操作,然后释放锁。在表的行上没有多用户并发,没有下一排魔术锁失败(请参阅上一链接)。这肯定会减慢您的写入操作的速度,但是如果每个人都以相同的顺序执行表锁,则只会遇到锁超时问题,不会出现死锁,也不会出现“无法序列化的自动回滚”的情况。
编辑
从您的代码示例中,我不确定您可以在开始之后设置事务隔离级别。您应该在MySQL上激活查询日志并查看已完成什么,然后检查CMS运行的其他事务是否仍处于可序列化级别。
关于mysql - 使用mysql更新帐户余额,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8828245/