PHP:如何在生成器函数之后进行清理

时间:2014-06-28 02:32:07

标签: php mysql mysqli

经过一些研究,我已经出售了许多通常会将结果缓冲到数组中的生成器(更常见的是迭代器)的想法,因为内存使用是O(1)而不是O(n)

所以我计划使用生成器来处理通过mysqli查询的数据库结果。关于这种方法,我有两个问题,我无法找到答案,我希望社区可以给我一些创造性的解决方案:

  1. 如果消费代码选择不完全迭代结果,是否有办法释放生成器打开的资源?使用Iterator类,可以在__desctruct方法中执行此操作。但是,根据我的测试,如果生成器没有自然结束,那么生成器将不会在迭代序列之后执行代码。我正在寻找可以防止必须创建Iterator子类的变通方法。请参阅以下代码。

  2. 使用生成器或迭代器是否为数据库结果提供了任何好处?我的一些窥探似乎表明mysqli可能正在将结果集加载到内存中(MYSQLI_STORE_RESULT),从而破坏了迭代器的目的。如果结果没有缓冲,我很好奇是否可以在同时迭代(获取)结果集的同时执行多个查询(想想嵌套循环,在这些循环中迭代一组项目,然后查询每个项目的子项目父母)。这似乎是数据库游标在整个迭代过程中可能会锁定。

  3. 实施例

    下面简化了清理的意思。从我的测试中,只有在迭代整个结果时才会释放结果。如果消费循环中存在异常或中断,则结果永远不会被释放。也许我正在推翻这个并且垃圾收集器已经足够好了?

    function query($mysqli, $sql){
      $result = $mysqli->query($sql);
      foreach($result as $row){
        yield $row;
      }
      $result->free(); //Never reached if break, exception, take first n rows, etc.
    }
    

    tl; dr我很好奇如何释放生成器使用的资源,以及随后如果数据库访问的生成器确实保存了任何内容,或者结果是否仍然缓冲

    更新

    默认情况下,这里看起来像http://www.php.net/manual/en/mysqlinfo.concepts.buffering.php),就像PHP缓冲区查询一样,可能会破坏生成器的目的。虽然可以认为只缓冲一个数组比创建缓冲数组的副本然后有两个缓冲集更好。

    我正在寻找有经验的人来衡量。你的想法很受欢迎!

3 个答案:

答案 0 :(得分:6)

我可能会晚一点,但是如果你正在使用发电机并且需要在完成后清理(比如你在完成循环之前打破了你的父循环),你可以使用try / catch /最后在finally块中进行清理:

function query($mysqli, $sql) {
  $result = $mysqli->query($sql);
  try {
    if ($result) {
      foreach($result as $row) {
        yield $row;
      }
    }
  } catch (Exception $e) {
    throw $e; // send this up the stack (or you could handle here)
  } finally {
    $result->free(); // clean up when the loop is finished.
  }
}

答案 1 :(得分:3)

以下是如何检测循环中断,以及如何在中断后处理或清理。

function generator()
{
    $complete = false;
    try {

        while (($result = some_function())) {
            yield $result;
        }
        $complete = true;

    } finally {
        if (!$complete) {
            // cleanup when loop breaks 
        } else {
            // cleanup when loop completes
        }
    }

    // Do something only after loop completes
}

答案 2 :(得分:0)

function query($mysqli, $sql){
  $result = $mysqli->query($sql);
  foreach($result as $i => $row) {
    if ($i + 1 === $result->num_rows) {
      $result->free();
    }
    yield $row;
  }
}