php如何避免在循环中使用sql语句

一、背景

众所周知的,在循环语句中执行数据库操作,对数据库来说负担很大。
举个例子:

for(int I=0;I<500;I++){查询语句}

      这样你的这个查询语句会执行500次,稍极端一点,忽略数据库内部执行语句的时间,服务器到数据库服务器之间网络不畅,光是服务器发送查询请求到数据库就需要1秒的时间,返回又1秒。这样你光是网络原因的等待时间就1000秒了。而如果你是在for语句外一次性取500条数据,这个网络请求等待时间就2秒。

      当循环量特别大的时候,一方面会造成严重的网络延迟,一方面可能会连接数据库失败,因此,避免循环中执行sql也算是业界的一个准则。

二、如何避免

大前提:博主一直用的laravel框架,因此会用到一些laravel特有的方法,殊途同归,各位道友心里明白就好。

1、循环内拼接sql语句,循环外执行

例如:

//假设这里的$users是一个很大的数组,我们要循环取出其中的值
foreach($users as $user){
//这部分的sql是我从项目中随便摘抄出来的。主要就是实现拼接sql语句
$sql .= " ('{$order_sn}', '{$this->user_id}', '{$deliver_fee}', 0, "
                        . " '', '', '', "
                        . " '{$add_time}','{$order_status}', '{$order_amount}', '{$remark}', "
                        . " '{$pickup_code}', '{$self_pickup}', '{$collect_order_sn}', '{$key}', {$reservation_time}),";
}
//循环中拼接好sql之后,我们在循环外执行
$sql = substr($sql, 0, -1);
DB::statement($sql);

2、where条件换为where in()

比如我们的where()条件中要使用大规模的数组,那么在语句应该是:

foreach($arr as $v){
DB::table('')->where('id',$v->id);
}

此时我们可以转变一下:

//先取出条件数组$arr,在直接执行whereIn即可
DB::table('')->whereIn('id',$arr);

注意:如果数据量很大,比如几十万条数据,那么whereIn也会成为代码的瓶颈,这个函数适用于数据量不是很大的情况。

3、laravel的chunk方法

官方文档:https://laravel-china.org/docs/laravel/5.5/eloquent/1332

      如果你需要处理数千个 Eloquent 记录,可以使用 chunk 命令。chunk 方法会检索 Eloquent 模型的「分块」,将它们提供给指定的 Closure 进行处理。在处理大型结果集时,使用 chunk 方法可节省内存:

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //
    }
});

      传递到方法的第一个参数是希望每个「分块」接收的数据量。闭包则被作为第二个参数传递,它会在每次执行数据库查询传递每个块时被调用。

      上面这段代码的意思是,先执行200条,执行完之后,再继续执行后200条。用一种类似于分页的方法来小规模的调用数据库操作,如此便可避免大数据量的操作。

4、laravel的chunk()方法的漏洞

注意:laravel的chunk方法在筛选条件并且更新数据的时候,会出现漏掉数据的bug,具体的可参考博客:https://www.jianshu.com/p/5dafd0d6e69a
例如:

User::where('approved', 0)->chunk(100, function ($users) {
  foreach ($users as $user) {
    $user->update(['approved' => 1]);
  }
});

      如果要运行上面的代码,并不会有报错,但是where条件是筛选approved为0的user然后将approved的值跟新为1。

      在这个过程中,档第一数据库的数据被修改后,下一个数据块的数据将是在被修改后的数据中选出来的,这个时候数据变了,而page也加了1。所以执行结束后,只对数据中一半的数据进行了更新操作。

      如果没有明白的话,我们来看一下chunk的底层实现。还以上面的代码为例,假如一共有400条数据,数据被按照100条进行分块处理。

page = 1: 最开始的时候page为1,选取1-100条数据进行处理;

      page = 2: 这时候前一百数据的approved值全部为1,那么在次筛选的时候,此时数据库中符合条件的数据只有300条,而且数据将从第101条开始,而这个时候的page=2,那么处理的数据将是第200-300之前的数据。之后依旧。

public function chunk($count, callable $callback)
{
    $results = $this->forPage($page = 1, $count)->get();

    while (count($results) > 0) {
        // On each chunk result set, we will pass them to the callback and then let the
        // developer take care of everything within the callback, which allows us to
        // keep the memory low for spinning through large result sets for working.
        if (call_user_func($callback, $results) === false) {
            return false;
        }
        $page++;
        $results = $this->forPage($page, $count)->get();
    }
    return true;
}

当然,关于chunk()的这个漏洞,我没找到chunk()的源码,但是我用laravel5.5没有出现这个问题。如果有大神看到这篇博客,请不吝赐教。

end

你可能感兴趣的:(php开发)