Ускорение запросов в базу данных с PDO и итераторами

FA-18_Hornet_breaking_sound_barrier

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

FA-18_Hornet_breaking_sound_barrier

 

Разработка без фреймворка

 

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

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

  • Вышепиведенный код не повторно используем, поэтому когда вам понадобится похожая функциональность, вы упретесь в дублирование существующего кода.
  • Даже если вы получаете объект используя  $stmt->fetchAll(\PDO::FETCH_OBJ); , вы по прежнему сталкиваетесь с проблемой, что вы используете массив объектов, что потребляет слишком много памяти когда выгружается множество данных.
  • Фильтрация осуществляется в рамках подпрограммы, что так же ознаает, что если вы имеете другие условия фильтрации, вам нужно модифицировать существующую логику, делая ее сложной для поддержки и расширяя функционал.

 

Итераторы

 

Большинство современных фреймворков используют Итераторы в их выгрузках данных потому, что они быстрые и повторно используемы. Но так же они предоставляют другие итераторы для фильтрации и модификации полученных результатов. Создание приложения без фреймворка все еще дает вам вариант использовать итераторы, т.к. они часть PHP начиная с версии 5.0.0 Beta 2.

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

  1. Использовать  PDOStatement::fetchAll()  для получения всех данных в одно действие
  2. Использовать  PDOSTatement::fetch()  для получение одной записи за итерацию

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

Вышеприведенный Итератор всего лишь реализует PHP Iterator interface, но в нашем примере этого более чем достаточно для достижения нашей цели.

Как вы можете видеть, мы реализовали логику для получения данных в “следующем” цикле, т.к. это наша прямая последовательность получения. Обратите внимание на второй и третий аргументы выражения  PDOSTatement::fetch() : вторым аргументом мы можем контроллировать курсор во время нашего получения данных, третий аргумент для позиционирования курсора для этого получения данных, которое было сделано прокручиваемым за пределами Итератора.

Для фильтрации наших данных мы можем теперь расширить SPL FilterIterator, который позволит нам присоединять нашу фильтрацию мнгновенно к нашему DbRowIterator, делая его расширяемым и повторно используемым немедленно.

Изменение нашего изначального кода получения данных внутри кода, который будет использовать оба наших Итератора, теперь очень просто:

Пожалуйста обратите внимание на  $pdo->prepare($sql, [\PDO::ATTR_CURSOR => \PDO::CURSOR_SCROLL]); , т.к. теперь мы должны убедиться, что курсор получения данных теперь прокручиваем, т.е. мы можем использовать запись через управление записями.

 

Измерения производительности

 

Я знаю, что все эти требования немного “лишняя” работа и вы можете удивляться, почему вы должны инвестировать в эту “большую работу” , если цикл foreach работает хорошо. Позвольте мне показать вам, используя измерение производительности между двумя:

Foreach цикл

  • Время получения данных для 63992 из 250000 записей: 2.14 секунды
  • Время обработки данных для 63992 из 250000 записей: 7.11 секунд
  • Общее время выполнения для 63992 из 250000 записей: 9.25 секунд
  • Потребление памяти для 63992 из 250000 записей: 217.75 МБ

Iterator цикл

  • Время получения данных для 63992 из 250000 записей: 0.92 секунды
  • Время обработки данных для 63992 из 250000 записей: 5.57 секунд
  • Общее время выполнения для 63992 из 250000 записей: 6.49 секунд
  • Потребление памяти для 63992 из 250000 записей: 0.25 МБ

Результат измерения

  • Получение данных быстрее с Итераторами
  • Обработка данных быстрее с Итераторами
  • Потребление памяти крайне лучше с Итераторами
Измерения проводились с MySQL 5.5.43 и PHP 5.5.26 на Ubuntu 12.04 LTS (виртуальной машине). Другие версии PHP, MySQL и OS могут дать вам другие результаты. 250000 записей сгенерировано используя fzaninotto/Faker.

 

Заключение

 

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

Замечание

Итераторы более эффективны для обработки больших объемов данных. Для малых объемов даных (приблизительно до 5000 записей) итераторы могут быть даже медленнее, чем использование массивов, но вы все еще будете в выигрыше по памяти.

 

Оригинал на английском:

http://www.dragonbe.com/2015/07/speeding-up-database-calls-with-pdo-and.html

Это мой . Если вы заметили ошибки или считаете, что какие-то части можно перевести лучше – пишите в комментарии.