Подтвердить что ты не робот

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

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

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

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

<?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?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Реальность ситуации заключается в том, что если вы выберете все строки и ожидаете, что сможете перебирать все из них в 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)

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

Ответ 4

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

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

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

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

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

Ответ 5

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

$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);
    }
}