防止信用体系中的欺诈行为

时间:2016-02-11 13:42:47

标签: php mysql sql pdo

假设我们的accounts表有一个名为balance的列。每个事务都记录在transactions表中。当然,在进行任何交易以销售任何产品之前,我们应该验证是否有足够的资金。因此,出于效果目的,我们应该检查用户的balance列,并在成功销售时扣除金额并更新其balance

如果用户异步购买2件可能导致欺诈的产品,该怎么办?我写了一个脚本,它将从一个帐户中扣除资金并将其克隆到另一个文件中。我同时执行了两个脚本,结果令人惊讶。

Deduct.php

<?php
//database connection...
$amount = 11;
$deducted = 0;
$blocked = 0;

for($i = 0; $i < 5000; $i++){
    $sql = $dbh->prepare('SELECT balance FROM accounts WHERE id = ?');
    $sql->execute(array(1));
    while($u = $sql->fetch()){
        $balance = $u['balance'];
        $deduct = $balance - $amount;
        if($deduct >= 0){
            $sql2 = $dbh->prepare('UPDATE accounts SET balance = ? WHERE id = ?');
            $sql2->execute(array($deduct,1));
            echo $balance . ' -> ' . $deduct . "\n";
            $deducted += $amount;
        } else {
            $blocked++;
        }
    }
}

echo 'Deducted: '.$deducted. "\n";
echo 'Blocked: '.$blocked;

在运行脚本之前我的balance是1000000,我已经执行了两个具有不同$amount值的脚本进程。结果如下:

As you can see both scripts deducted a total of 125000 and my balance is 879778.00 which is a proof of fraud

  • 任何替代解决方案可以克服这个问题?据我所知,如果记录每笔交易并计算最终余额是精确的,但是是密集的。

3 个答案:

答案 0 :(得分:2)

为什么要在一个查询中获取余额然后在另一个查询中设置?只需使用:

UPDATE accounts
    SET balance = balance - ?
    WHERE id = ?;

在数据库中进行算术运算。

答案 1 :(得分:2)

如果这是学校的家庭作业问题,而您只想让脚本工作,请执行以下操作:

$sql2 = $dbh->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?');
$sql2->execute(array($amount,1));

如果您正在做任何真实的事情,您应该记录所有单独的交易。为了加快速度,您可以每晚汇总交易并更新帐户余额,然后创建一个包含此类查询的视图以获取当前余额:

create or replace view current_accounts
select a.id as account_id
, max(a.balance) + ifnull(sum(t.amount), 0) as current_balance
from accounts a
left join transactions t on t.account_id = a.id and t.transaction_at > a.updated_at
group by a.id

插入每笔交易时,如果您要求余额永远不会消极:

insert into transactions (transaction_at, amount, account_id)
select now(), ?, v.account_id
from current_accounts v
where v.account_id = ?
and v.current_balance + ? >= 0

绑定时,如果您要取款,请确保扣除的金额为负数,如果您将资金存入帐户,请确保扣除金额为正数。您需要有一个关于transactions.transaction_date和accounts.updated_at的索引才能获得速度优势。

每晚更新应如下所示:

drop table accounts_old if exists;
create table accounts_new as
select t.account_id as id
, sum(t.amount) as balance
, max(t.transaction_at) as updated_at
from transactions t
group by t.account_id;
rename table accounts to accounts_old;
rename table accounts_new to accounts;

此外,accounts表中的主键应该被称为account_id,并且您应该在事务表中有一个名为transaction_id的主键。拒绝约定以命名任何内容&#34; id&#34;因为它最终会让你迷惑。

答案 2 :(得分:0)

您正在寻找的是一个允许事务的数据库:换句话说,事务中的所有内容都可以正常运行,或者它已经回滚,就像它从未发生过一样。这可以使用MySQL InnoDB表(但不能使用MyISAM表,您可能需要转换它们)。

根据您尝试的操作,您可以使用各种锁定选项 - 例如&#34;当我在这一行上工作时,其他所有想要读或写的人都要等到我完成了#34;或者&#34;每个人都必须等待写作,但能够阅读&#34;等等。

参见例如这是一个例子:http://coders-view.blogspot.com/2012/03/how-to-use-mysql-transactions-with-php.html

您可能还想检查约束(例如,避免超过帐户包含的借记);在MySQL中,您可以为此目的使用触发器:Can a MySQL trigger simulate a CHECK constraint?