PDO MYSQL_ATTR_USE_BUFFERED_QUERY 不生效
PDO MYSQL_ATTR_USE_BUFFERED_QUERY Not taking affect
我有如下粗略的代码(完整代码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 '{=int(100 * /1024/1024)/100"GB";}{ print;}'
监控 select
和 show 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
并且具有相同的行为。
您实际上进行了 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
,它是数据库本身可以更快使用的逻辑:
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);
考虑到每个连接只能激活一个查询。您基本上是在使用连接的缓冲区。
更好的选择是在 map reduce 中只加载小块。
SELECT * FROM stats LIMIT 100, 0
使用结果,然后
SELECT * FROM stats LIMIT 100, 100
等等。
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));
您不能通过简单地 运行 单个查询来摆脱大部分代码吗:
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
行。
我有如下粗略的代码(完整代码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 '{=int(100 * /1024/1024)/100"GB";}{ print;}'
监控 select
和 show 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
并且具有相同的行为。
您实际上进行了 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
,它是数据库本身可以更快使用的逻辑:
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);
考虑到每个连接只能激活一个查询。您基本上是在使用连接的缓冲区。
更好的选择是在 map reduce 中只加载小块。
SELECT * FROM stats LIMIT 100, 0
使用结果,然后
SELECT * FROM stats LIMIT 100, 100
等等。
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));
您不能通过简单地 运行 单个查询来摆脱大部分代码吗:
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
行。