PDO MYSQL_ATTR_USE_BUFFERED_QUERY未生效

时间:2020-05-30 01:04:58

标签: php mysql pdo unbuffered-queries

我有以下粗略代码(完整代码是146行,其中90行是字符串解析,如果需要可以添加):

ini_set('memory_limit', '7G');
$db = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$db_ub = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$db_ub->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$stmt = $db->prepare('select columns from stats where timestamp between ? and ?');
$stmt->execute(array('2020-04-25', '2020-05-25'));
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      echo memory_get_usage() .PHP_EOL;
      echo $row['id'] . PHP_EOL;
      $stmt2 = $db_ub->prepare('select somedata from users limit 1');
      $stmt2->execute();
      $row2 = $stmt2->fetch(PDO::FETCH_ASSOC);
      $type = !empty($row2['somedate']) ? 5 : 4;
      $result = $db_ub->prepare('insert ignore into newtable (old, type) values (?, ?)');
      $result->execute(array($row['id'], $type));
}

$stmt->execute(array('2020-04-25', '2020-05-25'));期间,我的内存消耗与.34GB相同(使用ps aux | grep 'php ' | awk '{$5=int(100 * $5/1024/1024)/100"GB";}{ print;}'监视selectshow full processlist SQL端期间的消耗)。脚本进入while后,它将跳到+5 GB。

测试setattribute

var_dump($db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false));

似乎已生效:

bool(true)

但是当我切换缓冲或不缓冲时,行为不会改变。

$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false)

$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true)

使用echo $db->getAttribute(constant('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'));还会显示设置更改。

将设置移动到语句而不是按照https://www.php.net/manual/en/ref.pdo-mysql.php的建议进行连接也没有用。

$stmt = $db->prepare('select columns from stats where timestamp between ? and ?', array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));

我还尝试了将缓冲区设置移至连接而没有任何影响:

$db = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));

排除第二个连接似乎可以使无缓冲查询按预期运行:

ini_set('memory_limit', '1G');
$db = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
//$db_ub = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
//$db_ub->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$stmt = $db->prepare('select columns from stats where timestamp between ? and ?');
$stmt->execute(array('2019-01-25', '2019-11-25'));
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      echo memory_get_usage() .PHP_EOL;
      echo $row['id'] . PHP_EOL;
      /*
     $stmt2 = $db_ub->prepare('select somedata from users limit 1');
      $stmt2->execute();
      $row2 = $stmt2->fetch(PDO::FETCH_ASSOC);
      $type = !empty($row2['somedate']) ? 5 : 4;
      $result = $db_ub->prepare('insert ignore into newtable (old, type) values (?, ?)');
      $result->execute(array($row['id'], $type));
     */
}

memory_get_usage的使用量不超过379999

如果我取消对第二个连接的注释并使其也没有缓冲,我会收到:

Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.

缓冲的第二个连接如最初所述执行,执行时消耗大量内存。如果ini_set('memory_limit'高,则错误,则工作。使用较大的memory_limit是不可行的解决方案。

正在使用(Red Hat Enterprise Linux Server release 7.3 (Maipo)):

php71u-pdo.x86_64                  7.1.19-1.ius.centos7

将脚本移动到较新的计算机(Amazon Linux release 2 (Karoo)):

php73-pdo.x86_64                   7.3.17-1.el7.ius

并且具有相同的行为。

3 个答案:

答案 0 :(得分:0)

PDO::ATTR_PERSISTENT的值不是布尔值。它标识正在使用的连接,对多个连接使用唯一的值。就我而言:

$db = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => 'unbuff', PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false));
$db_ub = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => 'buff', PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true));

答案 1 :(得分:0)

仅通过运行一个查询就不能摆脱大部分代码:

 INSERT IGNORE INTO newtable
     SELECT  ...,
             IF(..., 5, 4)
         FROM oldtable WHERE ...;

这样,您就可以摆脱7G内存问题。

如果发现一次执行太多操作,则将其分成多个块。参见此处的讨论:http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks(它讨论的是DELETEs,但它可以适应其他事物,例如您的SELECT。)

关于另一个主题:为什么在循环内执行select somedata from users limit 1?似乎每次都获得相同的数据。另外,如果没有ORDER BY,就无法预测将获得哪一行limit 1

答案 2 :(得分:-1)

您实际上是在进行135000000个查询,而不是遍历135000000个对象。

将代码更改为仅执行一次查询,但对元素进行排序,就好像它们在for循环中一样。

$db = new PDO("mysql:host=".$dbhost.";dbname=".$dbname, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$stmt = $db->prepare('SELECT * FROM stats ORDER BY id ASC');
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // ...
}

您甚至不需要此if,DB本身可以更快地使用它的逻辑:

if(!empty($ row ['id'])){

相反:

SELECT * FROM stats WHERE id IS NOT NULL ORDER BY id ASC

我有一段时间没有研究PDO / MySQL了,但是我假设没有缓冲就可以使用游标:

$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

请注意,每个连接只能有一个查询处于活动状态。您基本上是在使用连接的缓冲区。

更好的选择是在地图中以减少方式加载小块。

SELECT * FROM stats LIMIT 100, 0

使用结果,然后

SELECT * FROM stats LIMIT 100, 100

以此类推。

相关问题