处理CodeIgniter中的并发请求

时间:2015-06-03 00:26:36

标签: php mysql codeigniter session

假设我使用CodeIgniter / PHP和MySQL创建了一个在线银行系统,并从我的银行账户中提取以下资金:

["value1", "value2", "value3"]

首先,我们检查用户是否可以执行撤销,然后从帐户中扣除,然后我们添加到钱包。

问题在于我可以在几乎完全相同的时间发出多个请求(这基本上是使用function withdraw($user_id, $amount) { $amount = (int)$amount; // make sure we have enough in the bank account $balance = $this->db->where('user_id', $user_id) ->get('bank_account')->balance; if ($balance < $amount) { return false; } // take the money out of the bank $this->db->where('user_id', $user_id) ->set('balance', 'balance-'.$amount, false) ->update('bank_account'); // put the money in the wallet $this->db->where('user_id', $user_id) ->set('balance', 'balance+'.$amount, false) ->update('wallet'); return true; } )。每个请求都有自己的线程,所有线程都同时运行。因此,每个人都会检查我的银行是否有足够的金额(我这样做),然后每个人都执行提款。结果,如果我开始100的余额,并且我发出两个curl请求导致同时撤回100,那么我最终在我的钱包里有200个,在我的银行里有100个帐户,这是不可能的。

解决此类TOCTOU漏洞的正确“CodeIgniter”方法是什么?

2 个答案:

答案 0 :(得分:3)

我将bank_accountwallet表的存储引擎设置为具有事务支持的InnoDB,然后在SELECT语句中包含FOR UPDATE子句以锁定交易期间的bank_account表。

代码将类似于以下内容。

function withdraw($user_id, $amount) {
    $amount = (int)$amount;

    $this->db->trans_start();

    $query = $this->db->query("SELECT * FROM bank_account WHERE user_id = $user_id AND balance >= $amount FOR UPDATE");

    if($query->num_rows() === 0) {
        $this->db->trans_complete();
        return false;
    }

    $this->db->where('user_id', $user_id)
             ->set('balance', 'balance-'.$amount, false)
             ->update('bank_account');

    $this->db->where('user_id', $user_id)
             ->set('balance', 'balance+'.$amount, false)
             ->update('wallet');

    $this->db->trans_complete();

    return true;
}

答案 1 :(得分:0)

我在寻找的是table locking

为了使这个函数安全,我需要在函数的开头锁定表,然后在结束时释放它们:

function withdraw($user_id, $amount) {
    $amount = (int)$amount;

    // lock the needed tables
    $this->db->query('lock tables bank_account write, wallet write');

    // make sure we have enough in the bank account
    $balance = $this->db->where('user_id', $user_id)
                        ->get('bank_account')->balance;
    if ($balance < $amount) {
        // release the locks
        $this->db->query('unlock tables');
        return false;
    }

    // take the money out of the bank
    $this->db->where('user_id', $user_id)
             ->set('balance', 'balance-'.$amount, false)
             ->update('bank_account');

    // put the money in the wallet
    $this->db->where('user_id', $user_id)
             ->set('balance', 'balance+'.$amount, false)
             ->update('wallet');

    // release the locks
    $this->db->query('unlock tables');

    return true;
}

这使得任何其他MySQL连接写入所述表的任何尝试都会挂起,直到锁被释放为止。