Потребление памяти PDO / MySQL с большим набором результатов - PullRequest
20 голосов
/ 01 августа 2011

У меня странное время, когда я выбираю из таблицы с примерно 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?

Ответы [ 5 ]

56 голосов
/ 04 августа 2011

После создания соединения необходимо установить для 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)

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

1 голос
/ 13 мая 2013

Другим вариантом было бы сделать что-то вроде:

$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);
    }
}
1 голос
/ 04 августа 2011

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

Если вы действительно этого не сделаетеПодумайте, что использование выражений на основе 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)

Просто идея ...

1 голос
/ 04 августа 2011

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

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

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

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

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

1 голос
/ 01 августа 2011

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

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

SELECT SUM(home) AS home, SUM(away) AS away, COUNT(*) AS c FROM round
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...