防止双重执行。 PHP 金融应用
Preventing double execution. PHP financial app
如果我有一个脚本可以减少复式簿记系统中的用户余额,并且恶意用户决定在完全相同的时间在两台不同的机器(或同一台机器)上以他的帐户执行此脚本,整个事情会 运行 两次,对吗?所以这是我过度简化的假设场景。
$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,在账本中留下负值?
你会如何防止这样的事情发生?维护此数据库中的数据完整性至关重要 table。
你说的是 race conditions,它们 非常 非常重要,需要在财务代码中消除。
任何遵循 get/test/set 模式的东西都会有很大的问题。你不能那样做。
而是采用 set/test/fail 模式。尝试使用原子 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
,您可以在其中将条件应用于 return 的子查询余额不足时为零行。
如果我有一个脚本可以减少复式簿记系统中的用户余额,并且恶意用户决定在完全相同的时间在两台不同的机器(或同一台机器)上以他的帐户执行此脚本,整个事情会 运行 两次,对吗?所以这是我过度简化的假设场景。
$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,在账本中留下负值?
你会如何防止这样的事情发生?维护此数据库中的数据完整性至关重要 table。
你说的是 race conditions,它们 非常 非常重要,需要在财务代码中消除。
任何遵循 get/test/set 模式的东西都会有很大的问题。你不能那样做。
而是采用 set/test/fail 模式。尝试使用原子 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
,您可以在其中将条件应用于 return 的子查询余额不足时为零行。