PDO MYSQL_ATTR_USE_BUFFERED_QUERY 不生效。

问题描述 投票:0回答:1

我有以下粗略的代码(完整的代码是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.netmanualenref.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.

如果我取消第二个连接的comment,并使其不被缓冲,以及我收到。

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

并有相同的行为。

php mysql pdo unbuffered-queries
1个回答
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));

0
投票

难道你不能通过简单地运行一个查询来摆脱大部分的代码吗?

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

这样一来,你就可以摆脱7G内存的问题了。

如果发现一次做得太多,那就把它分成几块。 请看这里的讨论。 http:/mysql.rjweb.orgdoc.phpdeletebig#deleting_in_chunks。 (这说的是 DELETEs但它可以适应于其他事物,比如你的。SELECT.)

关于另一个话题。 为什么 select somedata from users limit 1 在循环内执行? 似乎每次得到的数据都是一样的。 此外,如果没有一个 ORDER BY你无法预知 limit 1 行,你将得到。


-1
投票

你实际上是在做13500万个查询,而不是在迭代13500万个对象。

修改代码,只做一个查询,但是要把元素排序,就像它们在你的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

我没有看PDOMySQL一段时间, 但我假设unbuffered允许你使用游标。

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

考虑到每个连接只能有一个查询活动。你基本上是在使用连接的缓冲区。

更好的选择是以地图减少的方式只加载小块数据。

SELECT * FROM stats LIMIT 100, 0

使用结果,然后

SELECT * FROM stats LIMIT 100, 100

诸如此类。

© www.soinside.com 2019 - 2024. All rights reserved.