laravel cursor 游标

和mysql语法的cursor是两回事,laravel的游标实际上就是使用的PHP的生成器和PDO fetch 从结果集中获取下一行,达到的效果。

为什么使用游标

像 larvel 的 get 方法之类的操作,其实就是先查询获得一个查询结果集(资源),再fetch资源,组装返回一个数组,也就是说它已经先循环了一遍了,你拿到数据后,要处理的话往往又要循环一次数组;
如果查询的数据较多,从效率、性能上考虑,我们可以省略这预先一次的循环(使用游标)

使用laravel游标

cursor 允许你使用游标来遍历数据库数据,一次只执行单个查询。在处理大数据量请求时 cursor 方法可以大幅度减少内存的使用:

  1. foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
  2. //
  3. }

原理:使用PHP的生成器和PDO fetch方法

PDO获取数据的方式

PHP PDO操作数据库的时候,可以使用
fetch — 从结果集中获取下一行,通过循环的方式,可以获取所有的数据
fetchAll — 返回一个包含结果集中所有行的数组

我们从数据库的一张表中获取所有的用户,使用fetch或者feitchAll 都可以获取所有的用户。如果我们是要再前台展示所有的用户,会用到分页,性能没多大问题。

但是我们要再脚本查找出所有的用户,并且对每一个用户的信息做分析的时候,就得先把所有用户从数据库中查找出来,然后循环这些数据对每一个用户做分析。

这样问题就产生了,用户量很大的时候,从数据库中查找出的信息是很大的,占用内存大;这个问题虽然可以使用laravel 分块查询解决(就是通过limit 批量查询,每次的数据也很少,但是面对大量数据的时候,使用游标的方式,一次获取一行,会大大降低内存的使用)

另一个问题就是,数据库中查询出所有的数据遍历了一次,然后我们又foreach了一次,实际是不需要的。能不能循环一次解决呢?

使用PHP生成器

循环一次fetch一次
PDO可以使用fetch获取一行,然后使用PHP生成器yield一行,foreach循环一次,yield执行一次,产出一个值,不会有大数组占用内存。
实例代码:

<?php
ArtModel.php
class ArtModel {
    public $errno = 0;
    public $errmsg = "";
    private $_db;

    public function __construct() {
            $this->_db = new PDO("mysql:host=127.0.0.1;dbname=yafceshi;", "root", "910930",array(PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8"));
      //不设置下面这行的话,PDO会在拼SQL时候,把int 0转成string 0
            $this->_db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
    }

    public function pdoQueryUser(){
        //执行查询语句
            $query = $this->_db->prepare("select * from user");
            $query->execute();
            return $query;
    }

    //传统方法:一次获取所有的用户
    public function returnDate(){
        $res = $this->pdoQueryUser();
        $dataArray=array();
        //每次获取一行,循环输出查询结果集,并且设置结果集为关联数据形式。
        while($result=$res->fetch(PDO::FETCH_ASSOC)){
            $dataArray[] = $result;
        }
        return $dataArray;
    }

  // 使用生成器:循环一次yield产出一个值
    public function yieldData(){
        $res = $this->pdoQueryUser();
      //fetch获取一行
        while($result=$res->fetch(PDO::FETCH_ASSOC)){
          //这里foreach一次执行一次,返回给foreach,然后继续
            yield $result;
        }
    }
}

a.php
public function listsAction(){
    $model = new ArtModel();
    // 一次获取所有的用户,已经循环获取了一次数据,这里返回的是循环之后生成的所有的数据。如果要展示的话还需要遍历一次这个大数组
    // $data=$model->returnDate();

    // 循环一次yield产出一个值。只需要在需要展示数据的地方循环把返回的每一行的数据展示出来即可。
    $data=$model->yieldData();
    foreach($data as $value){
        echo '<pre>';
            print_r($value);
        echo '</pre>';
    }
}