PDO MYSQL_ATTR_USE_BUFFERED_QUERY Не влияет - PullRequest
6 голосов
/ 30 мая 2020

У меня есть следующий приблизительный код (полный код - 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;}' для отслеживания потребления во время select и show full processlist SQL, чтобы проверить). Как только сценарий попадает в while, он увеличивается до +5 ГБ.

Тестирование 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 голосов
/ 01 июня 2020

Значение 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 голосов
/ 01 июня 2020

Разве вы не можете избавиться от большей части кода, просто выполнив один запрос:

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

Таким образом, вы можете избавиться от проблемы с памятью 7 ГБ.

Если оказывается, что сразу делает слишком много, а затем разбить его на куски. См. Обсуждение здесь: http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks (Это говорит о DELETEs, но его можно адаптировать к другим вещам, например к SELECT.)

На другом топи c : Почему select somedata from users limit 1 выполняется внутри l oop? Кажется, каждый раз получают одни и те же данные. Кроме того, без ORDER BY вы не можете предсказать, какую строку limit 1 вы получите.

0 голосов
/ 30 мая 2020
• 1000 1004 *

Вам даже не нужен этот if, это logi c, который может быть быстрее использован самой БД:

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

и т. Д.

...