Потребление памяти PDO / MySQL с большим набором результатов

У меня странное время, когда вы выбираете из таблицы около 30 000 строк.

Кажется, мой скрипт использует возмутительное количество памяти для того, что просто, вперед только по результатам запроса.

Обратите внимание, что этот пример представляет собой несколько надуманный, абсолютный минимальный пример, который очень мало похож на реальный код и не может быть заменен простой агрегацией базы данных. Он предназначен для иллюстрации того, что каждая строка не должна сохраняться на каждой итерации.

<?php $pdo = new PDO('mysql:host=127.0.0.1', 'foo', 'bar', array( PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, )); $stmt = $pdo->prepare('SELECT * FROM round'); $stmt->execute(); function do_stuff($row) {} $c = 0; while ($row = $stmt->fetch()) { // do something with the object that doesn't involve keeping // it around and can't be done in SQL do_stuff($row); $row = null; ++$c; } var_dump($c); var_dump(memory_get_usage()); var_dump(memory_get_peak_usage()); 

Эти результаты:

 int(39508) int(43005064) int(43018120) 

Я не понимаю, почему используется 40 мегабайт памяти, когда вряд ли какие-либо данные должны храниться в любой момент времени. Я уже разработал, что могу уменьшить память примерно в 6 раз, заменив «SELECT *» на «SELECT home, away», однако я считаю, что даже это использование будет безумно высоким, и таблица только увеличится.

Есть ли параметр, который мне не хватает, или есть некоторые ограничения в PDO, о которых я должен знать? Я рад избавиться от PDO в пользу mysqli, если он не может поддержать это, поэтому, если это мой единственный вариант, как бы это сделать, используя mysqli?

После создания соединения вам необходимо установить для PDO::MYSQL_ATTR_USE_BUFFERED_QUERY значение false:

 <?php $pdo = new PDO('mysql:host=127.0.0.1', 'foo', 'bar', array( PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, )); $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); // snip var_dump(memory_get_usage()); var_dump(memory_get_peak_usage()); 

Эти результаты:

 int(39508) int(653920) int(668136) 

Независимо от размера результата, использование памяти остается довольно статичным.

Весь набор результатов (все 30 000 строк) буферизуется в память, прежде чем вы сможете начать смотреть на него.

Вы должны позволить базе данных выполнять агрегацию и запрашивать только два числа, которые вам нужны.

 SELECT SUM(home) AS home, SUM(away) AS away, COUNT(*) AS c FROM round 

Реальность ситуации заключается в том, что если вы выберете все строки и ожидаете, что сможете перебирать все из них на PHP, сразу они будут существовать в памяти.

Если вы действительно не думаете, что использование выражений SQL и агрегации SQL – это решение, вы можете рассмотреть возможность ограничения / разделения вашей обработки данных. Вместо того, чтобы извлекать все строки сразу, сделайте что-то вроде:

 1) Fetch 5,000 rows 2) Aggregate/Calculate intermediary results 3) unset variables to free memory 4) Back to step 1 (fetch next set of rows) 

Просто идея …

Я не делал этого раньше в PHP, но вы можете рассмотреть выборку строк с помощью прокручиваемого курсора – см. Документацию выборки для примера.

Вместо того, чтобы сразу возвращать все результаты вашего запроса обратно на ваш PHP-скрипт, он содержит результаты на стороне сервера, и вы используете курсор для итерации через них, получая по одному.

Хотя я не тестировал это, он должен иметь другие недостатки, такие как использование большего количества ресурсов сервера и, скорее всего, снижение производительности из-за дополнительной связи с сервером.

Изменение стиля выборки также может оказать влияние, поскольку по умолчанию документация указывает, что он будет хранить как ассоциативный массив, так и численный индексированный массив, который связан с увеличением использования памяти.

Как предложили другие, сокращение числа результатов в первую очередь, скорее всего, является лучшим вариантом, если это возможно.

Другой вариант – сделать что-то вроде:

 $i = $c = 0; $query = 'SELECT home, away FROM round LIMIT 2048 OFFSET %u;'; while ($c += count($rows = codeThatFetches(sprintf($query, $i++ * 2048))) > 0) { foreach ($rows as $row) { do_stuff($row); } }