#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:
PDO для Postgres использует курсоры внутри.
Комментарии:
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.