假设我们的 accounts 表有一个名为 balance 的列,每个事务都记录在 transactions 表中。当然,我们应该在进行任何交易以销售任何产品之前验证有足够的资金。因此,出于性能目的,我们应该检查用户的 balance 列并扣除成功销售的金额并更新他的 balance
如果用户异步购买了 2 件产品,那可能会导致欺诈。我已经编写了一个脚本,该脚本将从一个帐户中扣除资金并将其克隆到另一个文件中。我同时执行了两个脚本,结果令人惊讶。

扣除.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
  • 任何替代解决方案来克服这个问题?我明白,如果记录每笔交易并计算最终余额,那么精确但很密集。
  • 最佳答案

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

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

    如果您正在做任何真实的事情,您应该记录所有单独的交易。为了加快速度,您可以每晚汇总交易并更新帐户余额,然后创建一个包含如下查询的 View 以获取当前余额:
    create or replace view current_accounts as
    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
    

    绑定(bind)时,请确保在您取款时扣除的金额为负数,如果您将钱存入帐户,请确保扣除的金额为正数。您将需要一个关于 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,而transactions 表中的主键应称为transaction_id。抵制将任何名称命名为“id”的约定,因为它最终会使您感到困惑。

    关于php - 防止信用系统中的欺诈,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35340981/

    10-13 04:36