防止双重执行。PHP金融应用程序

防止双重执行。PHP金融应用程序,php,security,data-integrity,Php,Security,Data Integrity,如果我有一个脚本在复式簿记系统中减少了用户余额,而一个恶意用户决定在两台不同的机器(或同一台机器)上同时在他的帐户上执行该脚本,那么整个过程将运行两次,对吗?这是我过度简化的假设情景 $balance=$user->ledger->getBalance();//返回5000 $amount=3000; 如果($金额分类账->减少($金额); } echo$user->ledger->getBalance();//echo的2000 如果脚本一个接一个地运行,第二次执行将失败,因为帐户中只剩下2

如果我有一个脚本在复式簿记系统中减少了用户余额,而一个恶意用户决定在两台不同的机器(或同一台机器)上同时在他的帐户上执行该脚本,那么整个过程将运行两次,对吗?这是我过度简化的假设情景

$balance=$user->ledger->getBalance();//返回5000
$amount=3000;
如果($金额分类账->减少($金额);
}
echo$user->ledger->getBalance();//echo的2000
如果脚本一个接一个地运行,第二次执行将失败,因为帐户中只剩下2000个,它试图减少3000个

如果脚本在完全相同的时间同时运行,两个余额不是都是5000,并且两个脚本执行都扣除3000,在分类账中留下负值吗

您将如何防止这种情况发生?维护此数据库表中的数据完整性至关重要。

您所说的,在财务代码中消除这些数据非常重要

任何遵循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()
,以确保原始账户的余额为零或正。如果没有,则通过
回滚
放弃交易。不应用任何更改


有很多方法可以构造那些
INSERT
语句,使其不可能出现负余额,例如
INSERT INTO x SELECT…FROM y
,您可以对子查询应用条件,在余额不足的情况下返回零行。

锁定行、检查行、对行执行数学运算、解锁行数据库那么勒索?就这么简单?如果是的话,那就太好了。我担心这会需要其他我不确定的完整性检查。在某种程度上,这只是证实并消除了我对这段代码的焦虑。我不相信我自己的研究假设这就是保证记录安全所需要的一切!有一种交易锁定,一种nd各种各样的锁定类型,但你知道。很好的综合回答:)我脑子里有这个想法,至少是交易的粗略近似值,但诚实地说,我需要一些确认我的决定。我喜欢插入x选择框的
。。。从y
方法也一样,但我确实可以访问事务,所以这将是一条路@simonw16如果您不能合理地将必要的逻辑包含到单个查询中,那么您应该真正使用一个正式的事务,并在事务期间锁定必要的行。@Sammitch通常的锁定注意事项适用于此处。如果你的代码在一个不方便的地方死掉了,并且没有释放锁,那么你就可以制造一个巨大的混乱。在加载之前,总是在负载下测试这个代码。@ TADMAN-IRC,如果连接在连接的中间丢失,则是隐式回滚。您的代码将不得不以一种特别令人不快的方式死掉,并保持连接打开以导致这种情况。即便如此,我还是要说,与无意中复制或破坏货币相比,这是一个更好的结果。@simonw16 Oof。所以你基本上只是得到了一个表,它代表了一个信用和借记交易的分类账?我的建议是在SQL事务期间锁定整个分类账表和任何关联的余额行。但可能更明智的做法是咨询金融系统设计的特定资源,无论是一本书、一个已建立的开源项目还是一个人。