如果我有一个脚本会减少重复输入簿记系统中的用户余额,并且恶意用户决定同时在两台不同的计算机(或同一台计算机)上以其帐户执行此脚本,整个事情会运行两次,对不对?这样就简化了我的假设假设。
$balance = $user->ledger->getBalance(); // returns 5000
$amount = 3000;
if ($amount <= $balance) {
$user->ledger->decrease($amount);
}
echo $user->ledger->getBalance(); // echo's 2000
如果脚本一个接一个地运行,则第二次执行将失败,因为帐户中只剩下2000个,并且试图减少3000个。
如果脚本在完全相同的时间同时运行,那么两个余额都不会是5000,并且两个脚本执行都减去3000,从而在分类帐中留下负值吗?
您如何防止此类情况发生?维护此数据库表中的数据完整性至关重要。
答案 0 :(得分:1)
您所谈论的是race conditions,在财务代码中消除它们非常重要。
遵循“获取/测试/设置”模式的所有内容都会遇到巨大的问题。你不能那样做。
改为采用设置/测试/失败模式。尝试使用原子SQL语句(例如单个操作或事务块)进行推论。如果这样会使天平负移回原处。
例如,这是不好:
balance = query("SELECT balance FROM accounts WHERE account_id=?")
balance -= amount
balance = query("UPDATE accounts SET balance=?")
在获取和写入之间可能发生任何事情。
相反,您可以在此查询成功或失败但不能中断的地方执行此操作:
query("UPDATE accounts SET balance=balance-? WHERE account_id=? AND balance>?")
除非有足够的余额,否则该查询将不会运行。结果将得到零行修改。
您还可以通过尝试插入所需的会计交易行,然后检查SUM()
以确保原始帐户的余额为零或为正,从而以双分类账簿方式进行记录。如果不是,请使用ROLLBACK
放弃交易。不会应用任何更改。
有很多方法来构造那些INSERT
语句以使负余额不可能发生,例如INSERT INTO x SELECT ... FROM y
,您可以将条件应用于子查询以在出现以下情况时返回零行:余额不足。