Управление памятью

Введение

Встроенный драйвер MySQL (mysqlnd) управляет памятью по-другому, в отличие от клиентской библиотеки MySQL (libmysql). Библиотеки различаются способом выделения и освобождения памяти, тем, как память выделяется по кускам во время чтения результатов из MySQL, существующими опциями для отладки и разработки, и тем, как результаты, считанные из MySQL, связаны с пользовательскими переменными PHP.

Следующая информация предназначена в качестве введения и обобщения для пользователей, заинтересованых в понимании mysqlnd на уровне C-кода.

Используемые функции для управления памятью

Все операции выделения и освобождения памяти происходят используя PHP-функции, предназначенные для управления памятью. Поэтому, потребление памяти встроенного драйвера MySQL может быть отслежено при помощи вызовов PHP API, таких как memory_get_usage(). Из-за того, что память выделяется и освобождается при помощи системы управления памятью PHP, изменения на уровне операционной системы могут быть видны не мгновенно. Система управления памятью PHP ведет себя как прокси, которая может вызывать задержку в освобождении памяти. Ввиду этого, сравнение использования памяти встроенного драйвера MySQL и клиентской библиотеки MySQL (libmysql) довольно сложно. Клиентская библиотека MySQL (libmysql) использует систему управления памятью операционной системы напрямую, следовательно, эффект на уровне операционной системы может быть виден незамедлительно.

Любое ограничение памяти, установленное в PHP, также влияет на встроенный драйвер MySQL. Это может вызвать ошибки переполнения памяти при извлечении больших массивов данных, которые превышают размер оставшейся памяти, предоставленных РНР. Из-за того, что клиентская библиотека MySQL не использует функций управления памяти PHP, она не подчиняется ограничению памяти, установленному в PHP. При использовании libmysql, в зависимости от модели развертывания, объем памяти, занимаемый PHP-процессом, может вырасти за пределы ограничений, установленных в PHP. В тоже время, PHP-скрипты могут обрабатывать больший объем массивов данных, так как области памяти, выделенные для хранения данных, не находятся под управлением РНР.

Функции системы управления памятью PHP вызываются встроенным драйвером MySQL через легковесную обертку. Среди прочего, обертка делает отладку легче.

Обработка массивов полученных данных

Различные MySQL-сервера и различные клиенткие API различают буферизированные и небуферизированные результаты. Небуферизированные результаты передаются строка за строкой от MySQL к клиенту и клиент читает их по порядку. Буферизированные результаты забираются клиентской библиотекой целиком до передачи их клиенту.

Встроенный драйвер MySQL использует PHP-потоки для сетевого общения с сервером MySQL. Результаты, посланные MySQL-сервером, выбираются из сетевых буферов PHP-потоков в результирующий буфер mysqlnd. Результирующий буфер состоит из zvals. На втором шаге результаты становятся доступными PHP-скрипту. Последняя передача из результирующего буфера в PHP-переменные вызывает потребление памяти и в большинстве случаев оно заметно при использовании буферизированных результатов.

По умолчанию встроенный драйвер MySQL пытается избежать двойного хранения буферного результа в памяти. Результаты хранятся только один раз во внутренних результирующих буферах и их zvals. Когда результаты забираются в РНР-переменные PHP-скриптом, переменные будут ссылаться на внутренние результаты буферов. Результаты запросов к базам данных не копируются и хранятся в памяти только один раз. Достаточно пользователю изменить содержимое переменной, содержащей результаты работы базы данных, как будет выполнен механизм копирования при записи (Copy-On-Write), для того, чтобы избежать изменения ссылающего внутреннего буфера результата. Содержимое буфера не должно быть изменено, так как пользователь может принять решение прочитать результат во второй раз. Механизм копирования при записи реализуется с помощью дополнительного управления списком ссылок и использования стандартных zval счетчиков ссылок. Копирование при записи также должно быть сделано, если пользователь читает данные результата в PHP переменных и освобождает данные результата прежде, чем переменные будут уничтожены.

В общем, этот шаблон работает хорошо для скриптов, которые читают наборы данных единожды и не изменяют переменных, содержащих результаты. Его главный недостаток в накладных расходах памяти, вызванных дополнительным управлением ссылками, причина которого в первую очередь связана с тем, что пользовательские переменные, удерживающие результаты, не могут быть полностью освобождены до того, как система управления ссылками mysqlnd содержит ссылки на них. Встроенный драйвер MySQL удаляет ссылку на на пользовательские переменные когда массив полученных данных освобождается или выполняется механизм копирования при записи. Наблюдатель увидит рост общего потребления памяти пока массив полученных данных не освободится. Используйте статистику, чтобы проверить, скрипт явно произвел освобождение данных результата или же драйвер сделал это неявно и поэтому память используется в течение более долгого времени, чем это необходимо. Статистика также помогает увидеть количество операций копирования при записи.

PHP-скрипт, читающий множество небольших строк в буферизированном массиве данных, использующий код, подобный while ($row = $res->fetch_assoc()) { ... }, может оптимизировать потребление памяти, запросив копии вместо ссылок. Хотя и запрос копий означает хранение тех же результатов в памяти дважды, это позволяет PHP уничтожить копию, содержащую в $row в качестве итерируемого массива данных и перед уничтожением результат устанавливает сам себя. На загруженном сервере оптимизация использования памяти может помочь улучшить общую производительность системы, хотя для отдельного скрипта подход с копией вместо ссылок может быть медленнее в связи с дополнительным выделением памяти и дополнительными операциями копирования в памяти.

Режим копирования может быть включен принудительно, установив mysqlnd.fetch_data_copy=1.

Контроль и отладка

Существует несколько методов отслеживания использования памяти во встроенном драйвере MySQL "mysqlnd". Если цель - получить быстрый высокоуровневый обзор или проверить эффективность PHP-скриптов при работе с памятью, то проверьте статистику, собранную библиотекой. Статистика позволит вам, например, поймать SQL-запрос, который генерирует больше результатов, чем обрабатываются PHP-скриптом.

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

В некоторых последних версиях встроенного драйвера MySQL "mysqlnd" присутствует возможность эмуляции случайных ситуаций нехватки памяти. Эта возможность была задумана для использования только C-разработчиками библиотеки или авторами плагина mysqlnd. Пожалуйста, используйте поиск по исходному коду для соответствующей настройки PHP и для дальнейшей информации. Это возможность является недокументированной и может быть изменена в любое время без дополнительного уведомления.