Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/72.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php CodeIgniter中并发请求的处理_Php_Mysql_Codeigniter_Session - Fatal编程技术网

Php CodeIgniter中并发请求的处理

Php CodeIgniter中并发请求的处理,php,mysql,codeigniter,session,Php,Mysql,Codeigniter,Session,假设我使用CodeIgniter/PHP和MySQL创建了一个网上银行系统,并使用以下方法从我的银行帐户中取款: 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)

假设我使用CodeIgniter/PHP和MySQL创建了一个网上银行系统,并使用以下方法从我的银行帐户中取款:

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;
}
函数提取($user\u id,$amount){
$amount=(int)$amount;
//确保我们的银行账户上有足够的存款
$balance=$this->db->where('user\u id',$user\u id)
->获取(‘银行账户’)->余额;
如果($余额<$金额){
返回false;
}
//把钱从银行里拿出来
$this->db->where('user\u id',$user\u id)
->设置('余额','余额-'。$amount,false)
->更新(“银行账户”);
//把钱放进钱包里
$this->db->where('user\u id',$user\u id)
->设置('余额','余额+'。$amount,false)
->更新(“钱包”);
返回true;
}
首先,我们检查用户是否可以执行取款,然后从帐户中减去,然后添加到钱包中

问题是我几乎可以在同一时间发送多个请求(使用
curl
,这基本上是微不足道的)。每个请求都有自己的线程,这些线程都是并发运行的。因此,每个人都会检查我的银行是否有足够的存款(我有),然后每个人都会进行取款。因此,如果我开始的余额为100,并且我发出两个
curl
请求,导致同时提取100,那么我的钱包中会有200,银行账户中会有-100,这是不可能的


什么是正确的“代码点火器”解决此类TOCTOU漏洞的方法?

我将设置具有事务支持的
银行账户
钱包
表的存储引擎,然后在SELECT语句中包含一个子句,在事务期间锁定
银行账户

然后,代码将如下所示

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;
}

我要找的是

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

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;
}
函数提取($user\u id,$amount){
$amount=(int)$amount;
//锁定所需的表
$this->db->query('lock tables bank_account write,wallet write');
//确保我们的银行账户上有足够的存款
$balance=$this->db->where('user\u id',$user\u id)
->获取(‘银行账户’)->余额;
如果($余额<$金额){
//松开锁
$this->db->query('unlock tables');
返回false;
}
//把钱从银行里拿出来
$this->db->where('user\u id',$user\u id)
->设置('余额','余额-'。$amount,false)
->更新(“银行账户”);
//把钱放进钱包里
$this->db->where('user\u id',$user\u id)
->设置('余额','余额+'。$amount,false)
->更新(“钱包”);
//松开锁
$this->db->query('unlock tables');
返回true;
}

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

根据文档(),“它们要求您跟踪查询,并根据查询的成功或失败来确定是提交还是回滚”。所以,除非我弄错了,否则这不会有任何效果,因为所有命令都不会失败。你误读了那句话,跳过了下一句。。。“这对于嵌套查询来说尤其麻烦。相比之下,我们已经实现了一个智能交易系统,它可以自动为您完成所有这一切(如果您愿意,您也可以手动管理您的交易,但实际上没有任何好处)。”我想我还是不明白。我不关心查询失败和回滚,而是关心检查的非原子性,然后再撤消。我在前面的评论中链接到的CI文档没有提到原子性或锁。事务是否保证原子性/句柄锁定等?它怎么知道要锁定哪些表呢?根据InnoDB的文档,事务是原子的和隔离的,所以我想我应该继续测试它:我将
银行账户和
钱包都转换成InnoDB,并用
$this->db->trans\u start()替换我的
锁定表
和my
使用
$this->db->trans_complete()解锁表
。我现在再次能够双重撤销,因此可以确认db没有将检查和撤销视为原子操作。@Mala,我在上面提供的SELECT语句中的FOR UPDATE是至关重要的。我上面的代码应该对bank_account表执行锁定。我应该在我的原始答案中突出显示更新,以便更清楚。答案已更新。您的测试中是否包含FOR UPDATE子句?这是您的答案还是问题