防止双重执行。 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 的子查询余额不足时为零行。