Как лучше перебирать результирующий набор в Postgres / PHP / PDO?

#php #postgresql #pdo

#php #postgresql #pdo

Вопрос:

Я использую PHP 5.3.6 с PDO для доступа к Postgres 9.0.4. Меня попросили уменьшить объем памяти отчета. Текущая реализация проста: выполните запрос, выполните fetchAll(), а затем выполните итерацию с помощью foreach() по результирующему массиву. Очевидно, что это не масштабируется с огромными наборами результатов: он может временно потреблять 100 МБ или больше.

У меня есть новая реализация, которая принимает дескриптор оператора PDO, а затем выполняет итерацию непосредственно по нему, используя foreach(), т. Е. Без промежуточного массива через fetchAll() . (Из того, что я прочитал, итерация дескриптора оператора с помощью foreach вызывает fetch() под прикрытием.) Это так же быстро и потребляет намного меньше памяти: около 28 КБ. Тем не менее, я не уверен, что делаю это правильно, потому что, хотя я много искал в Google, сложно найти ответы на основные вопросы об этом:

  • Я видел статьи, в которых предлагается решить мою исходную проблему с помощью курсоров. Использует ли драйвер Postgress PDO уже курсоры внутри? Если требуется написать свой собственный SQL для создания курсора, я готов, но я бы предпочел написать максимально простой код (но не проще!).

  • Если foreach вызывает fetch() на каждой итерации, не слишком ли это сетевая болтовня? Или это разумно и извлекает сразу несколько строк, например 500, для экономии пропускной способности? (Это может означать, что он использует курсоры внутри.)

  • Я видел статью, которая переносит дескриптор оператора в класс, который реализует интерфейс итератора. Разве это не избыточно, учитывая, что дескриптор оператора PDO уже делает это? Или я что-то упускаю?

  • Мой вызов для подготовки инструкции SQL выглядит следующим образом:

    $sth = $dbh-> подготовить ($sql);

Я обнаружил, что это не имело никакого значения для памяти или скорости, если я это сделал:

 $sth = $dbh->prepare($sql, array( PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY ) );
  

Это потому, что в любом случае это значение по умолчанию для драйвера Postgres PDO? Это имело бы смысл, если он уже использует курсоры внутри.

Общие комментарии о подходе и других способах решения этой проблемы приветствуются.

Ответ №1:

Комментарии:

1. Спасибо, Джон. Действительно, это так — я не думал смотреть прямо на источник. В моем прочтении говорится, что нет смысла извлекать более 1 строки за раз и буферизировать их на стороне клиента, чтобы уменьшить сетевой трафик. Если только это не происходит глубже в клиенте Postgres, который вызывает драйвер, но кажется маловероятным. Я проведу некоторое тестирование производительности, чтобы увидеть, как моя простая реализация работает с клиентом и Postgres в разных блоках. Если это слишком медленно, я сделаю свой собственный.

2. FWIW, мое мнение о источнике драйвера заключается в том, что он не использует необязательный аргумент для prepare() . Хотелось бы услышать, если кто-то не согласен.

Ответ №2:

По-видимому PDO::CURSOR_FWDONLY , не использует курсоры. Тесты черного ящика:

(0) Подготовка:

 $con = new PDO('dsn');
// you'll get "NO ACTIVE TRANSACTION" otherwise
$con->beginTransaction();

$sql = 'select * from largetable';
  

(1) По умолчанию — занимает вечно:

 $stmt = $con->prepare($sql);
$stmt->execute();
print_r($stmt->fetch());
  

(2) FWDONLY — занимает вечность:

 $stmt = $con->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$stmt->execute();
print_r($stmt->fetch());
  

(3) ПРОКРУЧИВАЕМЫЙ — выполняется во флэш-памяти:

 $stmt = $con->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
$stmt->execute();
print_r($stmt->fetch());
  

Я включил ведение журнала PG на всякий случай, и это действительно так — курсоры используются только для прокрутки.

Итак, единственный способ использовать курсоры — использовать ПРОКРУТКУ, по крайней мере, в PHP 5.4.23.