Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 47 · После жизненного цикла результата MySQL

Mysqlnd: статистика, память, настройки и persistent-соединения

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

Mysqlnd: драйвер под mysqli и PDO_MYSQL

Mysqlnd — нативный MySQL-драйвер проекта PHP, включённый в распространение PHP с 5.3. Он работает ниже пользовательских mysqli/PDO_MYSQL, а не вводит отдельный mysqlnd_query. Сам сервер MySQL — другой процесс и отдельная установка. Клиентская библиотека не требует локального сервера для сборки/подключения к удалённому серверу; наша лаборатория намеренно использует локальный.

Кто какую работу делает
  1. PHP-приложение: параметры, бизнес-правила, вывод
  2. mysqli или PDO_MYSQL: пользовательский API
  3. mysqlnd: протокол, память результата, статистика
  4. MySQL-сервер: SQL, план, данные, транзакции

Драйвер распространяется под лицензией PHP, интегрирован с менеджером памяти и streams; это упростило распространение по сравнению с внешним libmysqlclient. Статистика, доступные режимы результата и persistent-подключения помогают исследовать узкие места; «нативный» сам по себе не доказывает ускорение вашего запроса. Поддерживаются TLS/SSL, сжатый протокол для соответствующего API и named pipes на Windows. Применимость конкретной опции сжатия и аутентификации проверяют по текущему API/сборке: старый обзор описывает ограничение PDO_MYSQL, но текущий Pdo\Mysql::ATTR_COMPRESS документирован, а исходник PHP 8.5.11 устанавливает MYSQL_OPT_COMPRESS и для mysqlnd. Это уточнение по текущему API и исходнику, не результат замера сжатия в наших программах. В старых обзорах ext/mysql уже удалён из PHP 7; fetch_all перестал быть только-mysqlnd-возможностью с PHP 8.1. Для mysqli 8.2+ mysqlnd — единственная поддержанная библиотека.

Установка, версии и проверка текущего PHP

На Windows официальная PHP-сборка уже содержит mysqlnd, но расширение mysqli всё равно включают через совместимую php_mysqli.dll; PDO_MYSQL — отдельно. На Linux пакет расширения выбирают для своей ветки PHP. При сборке актуального mysqli используется --with-mysqli, PDO_MYSQL — --with-pdo-mysql=mysqlnd. Команды старого manual с --with-mysql относятся к удалённому ext/mysql и не являются инструкцией PHP 8.5.

Ключевые действия: фрагмент самостоятельной программы ниже
php --ri mysqli
php --ri mysqlnd
php --ri pdo_mysql

Это команды терминала: они показывают текущую сборку, не устанавливают сервер. SHA-256-аутентификация требует OpenSSL-возможностей; --with-openssl включает расширенную поддержку SSL при сборке. С PHP 8.4 добавлен --with-mysqlnd-ssl для явного включения расширенной поддержки без сборки openssl-расширения. Проверяйте TLS и аутентификацию на своём сервере; локальный учебный TCP-сеанс не является тестом удалённого TLS. Подробный пошаговый вход и ошибки находятся в первой главе MySQL.

Установить, какой клиент и настройки реально использует PHP

На одном компьютере PHP работает, на другом функция статистики отсутствует или настройки отличаются. Сначала нужно узнать фактический клиент текущего процесса, а не угадывать его по установленному серверу. Отчёт проверяет mysqlnd, кодировку соединения и включённость измерений.

Путь выполнения
  1. mysqli → клиентская библиотека → MySQL server
  2. mysqli_get_client_info → реальный клиент
  3. ini_get → значения текущего PHP
  4. utf8mb4 → состояние открытого соединения
  5. diagnostics.json → версии и настройки

Полученный результат

CheckValue
mysqlnd clienttrue
connection utf8mb4true
statistics enabledtrue
memory statistics enabledtrue
positive read timeouttrue
Ключевые действия: фрагмент самостоятельной программы ниже
$client = mysqli_get_client_info();
$native = str_contains(strtolower($client), 'mysqlnd');
$statistics = filter_var(ini_get('mysqlnd.collect_statistics'), FILTER_VALIDATE_BOOLEAN);

mysqlnd — MySQL Native Driver, нижний уровень связи для mysqli и PDO_MYSQL; у него нет отдельного нового API для выполнения SELECT. Версия клиентской библиотеки не равна версии сервера. В PHP 8.2+ mysqli использует только mysqlnd. INI collect_statistics и collect_memory_statistics читаются в текущем CLI/SAPI; наличие php.ini рядом с файлом ещё не означает, что он загружен. Для запуска примера флаги включаются до старта PHP через -d, а не через ini_set в середине запроса.

Открыть таблицу · JSON результата · Измерения и диагностика.

Установить, какой клиент и настройки реально использует PHPСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$db = my106Connect();
try {
    $client = mysqli_get_client_info(); $server = $db->server_info;
    $native = str_contains(strtolower($client), 'mysqlnd');
    $statistics = filter_var(ini_get('mysqlnd.collect_statistics'), FILTER_VALIDATE_BOOLEAN);
    $memoryStatistics = filter_var(ini_get('mysqlnd.collect_memory_statistics'), FILTER_VALIDATE_BOOLEAN);
    $readTimeout = (int)ini_get('mysqlnd.net_read_timeout');
    $rows = [['mysqlnd client',$native],['connection utf8mb4',$db->character_set_name() === 'utf8mb4'],['statistics enabled',$statistics],['memory statistics enabled',$memoryStatistics],['positive read timeout',$readTimeout > 0]];
} finally { $db->close(); }
my106Report('mysql106-driver-report', ['Check','Value'], $rows, ['settings_from_current_process' => true], ['mysqlnd' => $native, 'statistics' => $statistics && $memoryStatistics, 'timeout' => $readTimeout > 0], ['client'=>$client,'server'=>$server,'sapi'=>PHP_SAPI,'mysqlnd_ini'=>ini_get_all('mysqlnd',false)]);
echo "mysqlnd detected; statistics enabled; effective settings saved\n";
Ожидаемый результат
mysqlnd detected; statistics enabled; effective settings saved

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

Это отчёт локальной среды, не гарантия настроек FPM или другого worker. Архив не содержит реквизитов базы; пример получает их из PHPMAN_MYSQL_* и допускает только 127.0.0.1 учебного сервера. TLS/compression/named pipes — отдельные возможности протокола, которые данный локальный TCP-пример не тестирует. Приложению всё равно нужен доступный сервер MySQL: встроенный клиентский драйвер сервер не заменяет.

Замерить запросы и прочитанные строки одного участка кода

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

Путь выполнения
  1. Создание данных завершено
  2. before = connection stats
  3. SELECT1 → 4 строки; SELECT2 → 2 строки
  4. after = connection stats
  5. after − before → дельты запросов, fetch и трафика

Полученный результат

ObservationValue
application rows6
SELECT result sets2
buffered result sets2
rows received from server6
rows placed in buffer6
client fetch counter (observed)0
received bytes increasedtrue
Ключевые действия: фрагмент самостоятельной программы ниже
$before = mysqli_get_connection_stats($db);
// Нужный участок SQL и чтения результатов.
$after = mysqli_get_connection_stats($db);
$delta = (int)$after['result_set_queries'] - (int)$before['result_set_queries'];

mysqli_get_connection_stats возвращает ассоциативный массив статистики этого соединения. Значения счётчиков представлены строками: приводим к int перед вычитанием. my106Stat не заменяет неизвестный ключ нулём, а сообщает о недоступной метрике. Собственный счётчик приложения подтверждает 6 обработанных строк. Дельты server_normal и rows_buffered также дают 6, число результатов и буферов — 2, трафик положительный. При этом rows_fetched_from_client_normal_buffered в фактическом PHP 8.5.11 остался 0, хотя цикл действительно получил строки. Мы показываем этот ноль как наблюдение данной сборки, а не выдаём его за отсутствие чтения. Поэтому корректность обхода проверяется по собственному счётчику и полученным данным; существование ключа статистики ещё не гарантирует, что он обновляется на выбранном пути API. Точные байтовые числа сохраняются в diagnostics.json: они меняются вместе с версиями сервера и протокола.

Открыть таблицу · JSON результата · Измерения и диагностика.

Замерить запросы и прочитанные строки одного участка кодаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$db = my106Connect(); my106Seed($db);
try {
    $before = mysqli_get_connection_stats($db);
    $read = 0;
    foreach ([1,3] as $minimum) {
        $result = $db->query('SELECT id FROM mysql106_result_items WHERE id >= ' . $minimum . ' ORDER BY id', MYSQLI_STORE_RESULT);
        try { while ($row = $result->fetch_assoc()) { $read++; } }
        finally { $result->free(); }
    }
    $after = mysqli_get_connection_stats($db);
    $delta = [];
    foreach (['result_set_queries','buffered_sets','rows_fetched_from_server_normal','rows_buffered_from_client_normal','rows_fetched_from_client_normal_buffered','bytes_received'] as $name) { $delta[$name] = my106Stat($after,$name) - my106Stat($before,$name); }
} finally { $db->close(); }
$rows = [['application rows',$read],['SELECT result sets',$delta['result_set_queries']],['buffered result sets',$delta['buffered_sets']],['rows received from server',$delta['rows_fetched_from_server_normal']],['rows placed in buffer',$delta['rows_buffered_from_client_normal']],['client fetch counter (observed)',$delta['rows_fetched_from_client_normal_buffered']],['received bytes increased',$delta['bytes_received'] > 0]];
my106Report('mysql106-statistics',['Observation','Value'],$rows,['measurement_window'=>'two SELECTs after setup'],['read_count'=>$read===6,'query_delta'=>$delta['result_set_queries']>=2,'buffer_delta'=>$delta['buffered_sets']>=2,'server_rows'=>$delta['rows_fetched_from_server_normal']===$read,'buffered_rows'=>$delta['rows_buffered_from_client_normal']===$read,'traffic'=>$delta['bytes_received']>0],['delta'=>$delta,'before'=>$before,'after'=>$after]);
echo "stats: 2 SELECTs, 6 application rows; positive measured deltas\n";
Ожидаемый результат
stats: 2 SELECTs, 6 application rows; positive measured deltas

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

mysqli_get_client_stats относится к клиенту/процессу и может объединять вклад разных расширений, использующих mysqlnd, в том числе PDO_MYSQL. По одному глобальному счётчику нельзя установить точный PHP-вызов виновника. Отсутствующая статистика не равна нулю; выключенный сбор не доказывает отсутствие запросов. Показатель no_index_used не заменяет EXPLAIN, а slow_queries зависит от серверных порогов.

Увидеть память буфера, PHP-массива и освобождения

Большая выдача может занять память ещё до того, как приложение построило свой массив. Измерим отдельные моменты: до SELECT, после буферизации, после fetch_all, после free результата и после unset массива. Временная таблица содержит 128 текстов по 4096 байт; реальные цифры приведены в сохранённой HTML-таблице запуска.

Путь выполнения
  1. SELECT → буфер mysqlnd
  2. fetch_all → пользовательский массив
  3. free → результат освобождён, массив жив
  4. unset массива → данные больше не нужны
  5. memory_get_usage(false) → наблюдаемое выделение PHP

Числа зависят от сборки и запуска. Ниже загружается фактически сохранённая таблица, а не заранее выбранные «красивые» значения.

Сохранённый результат реального запуска

Реальные измерения памяти этого запуска

StagePHP allocated bytes
before query556184
buffered result1263440
PHP array also alive2366568
result freed, PHP array alive1659472
PHP array unset556344
Ключевые действия: фрагмент самостоятельной программы ниже
$before = memory_get_usage(false);
$result = $db->query('SELECT id,body FROM mysql106_memory ORDER BY id', MYSQLI_STORE_RESULT);
$buffered = memory_get_usage(false);
$items = $result->fetch_all(MYSQLI_ASSOC);
$result->free();
unset($items);

memory_get_usage(false) показывает память, учтённую менеджером PHP, а не RSS процесса в диспетчере задач. mysqlnd использует этот менеджер, поэтому его буфер входит в memory_limit. free не делает переменную $items пустой: она по-прежнему содержит 128 строк. В зависимости от версии mysqlnd и копирования PHP данные могут разделяться или копироваться; пример проверяет смысловую сохранность и наблюдаемое освобождение после unset, не фиксирует универсальную разницу в байтах.

Открыть таблицу · JSON результата · Измерения и диагностика.

Увидеть память буфера, PHP-массива и освобожденияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$db = my106Connect();
try {
    $db->query('CREATE TEMPORARY TABLE mysql106_memory (id INT PRIMARY KEY, body TEXT NOT NULL)');
    $stmt = $db->prepare('INSERT INTO mysql106_memory VALUES (?,?)');
    $payload = str_repeat('x', 4096);
    try { for ($id=1; $id<=128; $id++) { $stmt->bind_param('is',$id,$payload); $stmt->execute(); } }
    finally { $stmt->close(); }
    unset($payload);
    $before = memory_get_usage(false);
    $result = $db->query('SELECT id,body FROM mysql106_memory ORDER BY id', MYSQLI_STORE_RESULT);
    $buffered = memory_get_usage(false);
    $items = $result->fetch_all(MYSQLI_ASSOC); $fetched = memory_get_usage(false);
    $valid = count($items) === 128 && strlen($items[127]['body']) === 4096;
    $result->free(); $afterFree = memory_get_usage(false);
    // Пользовательский массив ещё жив и продолжает содержать данные.
    $retained = strlen($items[0]['body']) === 4096;
    unset($items); $afterUnset = memory_get_usage(false);
} finally { $db->close(); }
$rows = [['before query',$before],['buffered result',$buffered],['PHP array also alive',$fetched],['result freed, PHP array alive',$afterFree],['PHP array unset',$afterUnset]];
my106Report('mysql106-memory',['Stage','PHP allocated bytes'],$rows,['rows'=>128,'payload_bytes_per_row'=>4096,'not_os_rss'=>true],['data_complete'=>$valid,'buffer_cost_visible'=>$buffered>$before,'array_survives_free'=>$retained,'array_release_visible'=>$afterUnset<$afterFree],['memory_get_usage_false'=>array_column($rows,1),'memory_peak_usage_false'=>memory_get_peak_usage(false),'client_stats'=>mysqli_get_client_stats()]);
echo "memory: 128 x 4096-byte rows measured; buffer and PHP array lifetimes shown\n";
Ожидаемый результат
memory: 128 x 4096-byte rows measured; buffer and PHP array lifetimes shown

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

Пиковая память, текущая PHP-память и память ОС — разные величины. Аллокатор может оставить освобождённые страницы процессу; отсутствие немедленного падения RSS не доказывает утечку. Нельзя сравнивать mysqlnd с историческим libmysqlclient только по memory_get_usage: у них разный учёт. Эксперимент малый и последовательный, не нагрузочный тест. Для больших данных используйте нужные колонки/WHERE, потоковый вывод или ограниченные порции; fetch_all может сам накопить результат независимо от способа SQL-чтения.

Разобранная ситуация: Unexpectedly hitting PHP memory limit with a single PDO query?. Один запрос расходует PHP memory_limit до пользовательского накопления строк. Обсуждение использовано как источник реальной задачи; объяснения и программы здесь собственные, правила сверены с PHP/MySQL.

Проверить повторное использование соединения в одном процессе

Нужно проверить, что persistent-соединение действительно переиспользовалось, а не просто предположить ускорение по префиксу p:. Откроем handle, выполним запрос, закроем handle и откроем второй с теми же параметрами в одном CLI-процессе.

Путь выполнения
  1. HOST с p: → persistent handle
  2. SELECT7 → close handle
  3. Повторные те же параметры → handle2
  4. thread_id тот же; connection_reused увеличился
  5. SELECT8 работает

Полученный результат

ObservationValue
first query7
second query8
same server connectiontrue
reuse counter increasedtrue
Ключевые действия: фрагмент самостоятельной программы ниже
$first = my106Connect(true);
$firstId = $first->thread_id;
$first->close();
$second = my106Connect(true);
$same = $firstId === $second->thread_id;

Префикс p: добавляет helper к имени хоста; отдельной mysqli_pconnect нет. close завершает использование handle приложением, но пригодное persistent-соединение может вернуться в локальный пул процесса. thread_id — идентификатор серверной сессии; он не равен PHP object ID. Счётчик connection_reused подтверждает reuse с другой стороны. Ни конкретный ID, ни полные абсолютные счётчики не зашиты в ожидаемый результат.

Открыть таблицу · JSON результата · Измерения и диагностика.

Проверить повторное использование соединения в одном процессеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$before = mysqli_get_client_stats();
$first = my106Connect(true); $firstId = $first->thread_id;
$probe = $first->query('SELECT 7 AS number'); $number = (int)$probe->fetch_assoc()['number']; $probe->free();
$first->close();
$second = my106Connect(true); $secondId = $second->thread_id;
try { $probe = $second->query('SELECT 8 AS number'); $next = (int)$probe->fetch_assoc()['number']; $probe->free(); }
finally { $second->close(); }
$after = mysqli_get_client_stats();
$reused = my106Stat($after,'connection_reused') - my106Stat($before,'connection_reused');
$rows = [['first query',$number],['second query',$next],['same server connection',$firstId===$secondId],['reuse counter increased',$reused>=1]];
my106Report('mysql106-persistent-reuse',['Observation','Value'],$rows,['scope'=>'one CLI process'],['queries'=>$number===7&&$next===8,'same_connection'=>$firstId===$secondId,'reuse_counter'=>$reused>=1],['first_thread_id'=>$firstId,'second_thread_id'=>$secondId,'reuse_delta'=>$reused]);
echo "persistent: two handles, one reused server connection in this process\n";
Ожидаемый результат
persistent: two handles, one reused server connection in this process

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

Два самостоятельных запуска php example.php — два процесса, а не один общий пул. PHP-FPM имеет несколько worker: каждый хранит собственные соединения; чужой HTTP-запрос не получает обещание конкретной сессии. Persistent не означает автоматически быстрее: повторная аутентификация/очистка имеют цену. Проверяйте лимиты mysqli.max_persistent/max_links и MySQL max_connections, измеряйте свой сценарий, не включайте p: как лечение медленного SQL.

Не хранить данные пользователя в persistent-сессии

После reuse серверное соединение осталось тем же. Значит ли это, что можно хранить в нём черновик следующего пользователя? Проверим переменную сессии и temporary table: обычная очистка mysqli/mysqlnd убирает прежнее состояние перед новым использованием.

Путь выполнения
  1. handle1: @user_note и TEMPORARY TABLE
  2. close → вернуть persistent-соединение
  3. handle2: повторная аутентификация/очистка
  4. @note → NULL; таблица → ошибка 1146
  5. set_charset → явный utf8mb4

Полученный результат

ObservationValue
same server connectiontrue
old user variableNULL
temporary table missingtrue
connection charsetutf8mb4
Ключевые действия: фрагмент самостоятельной программы ниже
$first->query("SET @mysql106_user_note = 'private draft'");
$first->close();
$second = my106Connect(true);
$probe = $second->query('SELECT @mysql106_user_note AS note');

В обычной сборке mysqli/mysqlnd выполняет COM_CHANGE_USER при повторном получении persistent-соединения. Документированная очистка откатывает активную транзакцию, удаляет временные таблицы, снимает блокировки, сбрасывает session variables, закрывает statements/handlers и освобождает GET_LOCK. Проверяем две наблюдаемые части: наша переменная исчезла, temporary table недоступна. Helper заново задаёт charset при каждом открытии, поэтому требование приложения явно восстановлено.

Открыть таблицу · JSON результата · Измерения и диагностика.

Не хранить данные пользователя в persistent-сессииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$first = my106Connect(true); $firstId = $first->thread_id;
$first->query("SET @mysql106_user_note = 'private draft'");
$first->query('CREATE TEMPORARY TABLE mysql106_session_draft (id INT PRIMARY KEY)');
$first->query('INSERT INTO mysql106_session_draft VALUES (1)');
$first->close();
$second = my106Connect(true);
try {
    $secondId = $second->thread_id;
    $probe = $second->query('SELECT @mysql106_user_note AS note'); $note = $probe->fetch_assoc()['note']; $probe->free();
    $missing = false; $errorCode = 0;
    try { $second->query('SELECT id FROM mysql106_session_draft'); }
    catch (mysqli_sql_exception $error) { $errorCode = $error->getCode(); $missing = $errorCode === 1146; }
    $charset = $second->character_set_name();
} finally { $second->close(); }
$rows = [['same server connection',$firstId===$secondId],['old user variable',$note],['temporary table missing',$missing],['connection charset',$charset]];
my106Report('mysql106-persistent-reset',['Observation','Value'],$rows,['cleanup_observed'=>'mysqli/mysqlnd default build'],['same_connection'=>$firstId===$secondId,'variable_reset'=>$note===null,'temporary_removed'=>$missing,'charset_reapplied'=>$charset==='utf8mb4'],['first_thread_id'=>$firstId,'second_thread_id'=>$secondId,'missing_table_error'=>$errorCode]);
echo "persistent reset: user variable NULL, temporary table absent, utf8mb4 reapplied\n";
Ожидаемый результат
persistent reset: user variable NULL, temporary table absent, utf8mb4 reapplied

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

Программа проверяет стандартную конфигурацию текущего runtime, не все возможные сборки. Компиляционный MYSQLI_NO_CHANGE_USER_ON_PCONNECT отключает этот механизм в пользу ping; не советуем отключать очистку ради сомнительного ускорения. Не переносите контракт mysqli автоматически на PDO persistent. Temporary table не подходит для хранения черновика между HTTP-запросами; нужен обычный устойчивый storage с идентификатором пользователя и сроком жизни. Резервирование/транзакции завершайте явно, не рассчитывая только на уборку пула.

Разобранная ситуация: Persistent connections that dont persist between two php scripts. Временную таблицу ошибочно пытаются переносить между отдельными скриптами через persistent connection. Обсуждение использовано как источник реальной задачи; объяснения и программы здесь собственные, правила сверены с PHP/MySQL.

Собрать ограниченную диагностику и найти лишнюю выдачу

В отчёте поддержки важно увидеть лишнюю работу, не сохраняя пароль и весь phpinfo. Исходный код выбирает 4 товара, использует 1 и выбрасывает остальное. Покажем ограниченную диагностику и вариант LIMIT1 с тем же первым товаром.

Путь выполнения
  1. Исходный SELECT → returned4
  2. Приложение fetch1 → used1
  3. free → остальные данные не нужны
  4. LIMIT1 → returned1 и used1
  5. INI + scoped stats → отчёт без пароля

Полученный результат

VariantRows returnedRows used
original SELECT41
with LIMIT 111
Ключевые действия: фрагмент самостоятельной программы ниже
$returned = $result->num_rows;
$chosen = $result->fetch_assoc();
$result->free();
// Для одного товара формулируем ограничение в SQL: ORDER BY id LIMIT 1.

Число returned берётся из num_rows buffered-результата, used ведёт приложение. Разница показывает неиспользованную выдачу, хотя сама по себе не измеряет время или байты. diagnostics.json содержит выбранные INI-настройки, снимки connection stats и client stats; HOST/USER/PASSWORD не записываются. Для однозначного «первого» применяется ORDER BY id. Исправленный запрос выбирает тот же ID с LIMIT1.

Открыть таблицу · JSON результата · Измерения и диагностика.

Собрать ограниченную диагностику и найти лишнюю выдачуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/my106-results-helper.php';

$db = my106Connect(); my106Seed($db);
try {
    $before = mysqli_get_connection_stats($db);
    $result = $db->query('SELECT id,title FROM mysql106_result_items ORDER BY id', MYSQLI_STORE_RESULT);
    $returned = $result->num_rows; $used = 0;
    try { $chosen = $result->fetch_assoc(); $used++; }
    finally { $result->free(); }
    $after = mysqli_get_connection_stats($db);
    $fixed = $db->query('SELECT id,title FROM mysql106_result_items ORDER BY id LIMIT 1');
    try { $fixedCount = $fixed->num_rows; $fixedRow = $fixed->fetch_assoc(); }
    finally { $fixed->free(); }
    $clientStats = mysqli_get_client_stats();
    $ini = [];
    foreach (['collect_statistics','collect_memory_statistics','net_read_timeout','net_cmd_buffer_size','net_read_buffer_size'] as $name) { $ini['mysqlnd.'.$name] = ini_get('mysqlnd.'.$name); }
} finally { $db->close(); }
$rows = [['original SELECT',$returned,$used],['with LIMIT 1',$fixedCount,1]];
my106Report('mysql106-incident-report',['Variant','Rows returned','Rows used'],$rows,['same_chosen_id'=>(int)$chosen['id']===(int)$fixedRow['id'],'credentials_logged'=>false],['unneeded_rows_visible'=>$returned===4&&$used===1,'bounded_fix'=>$fixedCount===1&&(int)$fixedRow['id']===1,'stats_available'=>array_key_exists('result_set_queries',$after)],['ini'=>$ini,'connection_before'=>$before,'connection_after'=>$after,'client_stats'=>$clientStats,'application_received'=>$returned,'application_used'=>$used]);
echo "diagnostics: original used1 of4; LIMIT used1 of1; credentials omitted\n";
Ожидаемый результат
diagnostics: original used1 of4; LIMIT used1 of1; credentials omitted

Комплект содержит example.php, helper и README. Задайте переменные окружения PHPMAN_MYSQL_* для отдельного локального сервера и выполните php -d mysqlnd.collect_statistics=1 -d mysqlnd.collect_memory_statistics=1 example.php. Расширение mysqli/mysqlnd обязательно; пароль в исходник и отчёт не записывается. Учебные TEMPORARY-таблицы создаёт программа, их входные строки видны в helper. Результаты — result.html, result.json и diagnostics.json. Таблицы принадлежат только учебной сессии; обычные таблицы пользователя не удаляются.

Не делайте автоматический совет LIMIT1 по любой разнице: бизнес может намеренно читать повторно или показывать несколько блоков из одного буфера. SQL-план, индексы, объём строк и частота выполнения — отдельные измерения. В production отчёты и trace обычно должны быть закрыты от посетителей; опубликованные здесь файлы содержат исключительно синтетические локальные данные. trace_alloc/debug не включаются ради каждого обычного запроса.

Все настройки mysqlnd и версии их действия

В таблице сохранены все директивы официального раздела. Важное расхождение версий: страница manual отмечает несколько параметров INI_SYSTEM, а наш PHP 8.5.11 сообщает access=7 (INI_ALL) для collect_statistics и обоих сетевых размеров. Колонка runtime получена из ini_get_all в реальном процессе. Это наблюдение сборки, не исправление правил всех старых PHP. Для воспроизводимого эксперимента задаём -d до запуска.

ДирективаDefault и область в manualНаблюдение PHP 8.5.11Назначение
mysqlnd.collect_statistics1 / INI_SYSTEMINI_ALL; текущее 1Сбор общей статистики клиента; без него значения не доказывают отсутствие запросов.
mysqlnd.collect_memory_statistics0 / INI_SYSTEMINI_SYSTEM; текущее 1Дополнительные счётчики операций памяти; включение увеличивает диагностическую работу.
mysqlnd.debugпусто / INI_SYSTEMINI_SYSTEM; текущее NoneТрассировка связи для debug-сборки; формат option[,argument]:option. Лог может быть объёмным и чувствительным.
mysqlnd.log_mask0 / INI_ALLINI_ALL; текущее 0Числовая маска серверных флагов для логирования; 0 отключает. См. полный перечень ниже.
mysqlnd.mempool_default_size16000 / INI_ALLINI_ALL; текущее 16000Начальный размер пула памяти для результатов в байтах; не лимит результата и не общий memory_limit.
mysqlnd.net_read_timeout86400 / INI_ALLINI_ALL; текущее 86400Таймаут сетевого чтения в секундах, не серверный предел длительности SQL. До PHP 7.2 default 31536000 и INI_SYSTEM.
mysqlnd.net_cmd_buffer_size4096 / INI_SYSTEMINI_ALL; текущее 4096Начальный командный буфер; minimum 4096. При росте увеличивается command_buffer_too_small. Возможна mysqli option MYSQLI_OPT_NET_CMD_BUFFER_SIZE.
mysqlnd.net_read_buffer_size32768 / INI_SYSTEMINI_ALL; текущее 32768Максимальная порция чтения тела пакета; меньшее тело читается целиком, большое порциями. Возможна MYSQLI_OPT_NET_READ_BUFFER_SIZE.
mysqlnd.sha256_server_public_keyпусто / INI_PERDIRINI_PERDIR; текущее NoneФайл публичного RSA-ключа для SHA-256-аутентификации. Это не пароль и не универсальная замена проверке TLS-сертификата.
mysqlnd.trace_allocпусто / INI_SYSTEMINI_SYSTEM; текущее NoneВнутренняя диагностика выделений; manual перечисляет директиву без полного прикладного контракта. Не предлагаем её как настройку обычного сайта.
mysqlnd.fetch_data_copy0 (исторически) / INI_ALLнет в PHP 8.5.11Удалена в PHP 8.1. Старый режим принудительного копирования результата, с оговоркой несовместимости PDO_MYSQL. В PHP 8.5 её не включают.
Отладочные флаги mysqlnd.debug и все значения log_mask
Флаг debugДействие
A[,file]Добавлять с принудительным закрытием/открытием после записи; медленно, но полезно при аварии.
a[,file]Добавлять в указанный файл.
d[,keywords]Вывод DBUG-макросов, при списке — только выбранные ключи.
f[,functions]Ограничить функции; пустой список означает все.
FИмя исходного C-файла в строке лога.
iPID процесса.
LНомер строки исходника.
nГлубина вложенности функции.
o[,file]Как a, но перезаписывать файл.
O[,file]Как A, но перезаписывать файл.
t[,N]Трасса вызовов, глубина по умолчанию 200.
xПрофилирование.
mОперации выделения/освобождения памяти.
Флаг log_maskЗначение
SERVER_STATUS_IN_TRANS1
SERVER_STATUS_AUTOCOMMIT2
SERVER_MORE_RESULTS_EXISTS8
SERVER_QUERY_NO_GOOD_INDEX_USED16
SERVER_QUERY_NO_INDEX_USED32
SERVER_STATUS_CURSOR_EXISTS64
SERVER_STATUS_LAST_ROW_SENT128
SERVER_STATUS_DB_DROPPED256
SERVER_STATUS_NO_BACKSLASH_ESCAPES512
SERVER_QUERY_WAS_SLOW1024

Формат debug: option[,argument]:option. Например, d:t:x:O,/tmp/mysqlnd.trace — контекстный пример строки конфигурации для debug-сборки, не программа этой главы. Логирование может раскрывать SQL и данные; пишите в закрытый каталог. Для log_mask нужны числовые значения, не строковые имена PHP-констант. 48 объединяет 16+32; перечисленные биты суммарно дают 2043. Не делайте вывод о реально записанном trace только по принятой INI-строке: в обычной сборке debug-возможность может быть неактивна.

Счётчики: полный каталог и правила интерпретации

Два входа: mysqli_get_connection_stats($db) для соединения и mysqli_get_client_stats() для клиента/процесса. Часть счётчиков процессная даже в общей таблице; глобальные значения агрегируют вызовы расширений, использующих mysqlnd. Дельта вокруг небольшого участка полезнее случайного абсолютного числа. Счётчики приходят строками; существование имени, включённость сбора и смысл единиц проверяются отдельно.

Каталог ниже содержит все 152 имени официального раздела с объяснениями; в исследованном runtime 163 ключа. Документированный com_daemon отсутствует. Старые/зарезервированные показатели могут возвращаться нулём и не иметь полезного измерения. Не вычитайте число строк из числа байтов: размер пакета и количество fetch — разные единицы. Ошибка сервера может прийти там, где ожидался пакет другого вида; статистика протокола не заменяет обработку исключений.

Полный каталог официальных счётчиков mysqlnd: назначение и доступность
ИмяЧто измеряетВ PHP 8.5.11
bytes_sentБайты от PHP к серверу, включая протокольную передачу; не размер PHP-массива.ключ есть
bytes_receivedБайты от сервера к клиенту; счётчик трафика, не объём полезных PHP-значений.ключ есть
packets_sentЧисло отправленных протокольных пакетов.ключ есть
packets_receivedЧисло полученных протокольных пакетов.ключ есть
protocol_overhead_inВходящая служебная часть протокола в байтах; учитываются четырёхбайтные заголовки.ключ есть
protocol_overhead_outИсходящая служебная часть протокола в байтах; учитываются заголовки исходящих пакетов.ключ есть
bytes_received_ok_packetБайты полученных пакетов: OK со статусом переменного размера. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_okЧисло полученных пакетов: OK со статусом переменного размера. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_eof_packetБайты полученных пакетов: EOF; размер и использование зависят от сервера. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_eofЧисло полученных пакетов: EOF; размер и использование зависят от сервера. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_rset_header_packetБайты полученных пакетов: заголовок результата. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_rset_headerЧисло полученных пакетов: заголовок результата. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_rset_field_meta_packetБайты полученных пакетов: метаданные столбцов. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_rset_field_metaЧисло полученных пакетов: метаданные столбцов. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_rset_row_packetБайты полученных пакетов: данные строк. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_rset_rowЧисло полученных пакетов: данные строк. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_prepare_response_packetБайты полученных пакетов: ответ на подготовку statement. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_prepare_responseЧисло полученных пакетов: ответ на подготовку statement. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
bytes_received_change_user_packetБайты полученных пакетов: ответ на смену/повторную аутентификацию пользователя. Включён заголовок; размер пакета может меняться, ошибка может прийти вместо ожидаемого ответа.ключ есть
packets_received_change_userЧисло полученных пакетов: ответ на смену/повторную аутентификацию пользователя. Сопоставляйте с байтовым счётчиком того же семейства.ключ есть
packets_sent_commandПакеты с командами клиента; детали команд смотрите отдельно в com_*.ключ есть
bytes_received_real_data_normalПолезные байты, действительно извлечённые PHP из обычного результата. Только буферизация без fetch не увеличивает этот показатель.ключ есть
bytes_received_real_data_psПолезные байты, действительно извлечённые PHP из prepared результата. Только буферизация без fetch не увеличивает этот показатель.ключ есть
result_set_queriesКоманды с набором строк, например SELECT/SHOW; отказ чтения заголовка может не увеличить счётчик.ключ есть
non_result_set_queriesКоманды без набора строк, например INSERT/UPDATE; не эквивалент числу изменённых строк.ключ есть
no_index_usedСервер отметил запрос без использованного индекса. Это повод изучить план, не автоматическое доказательство плохого запроса.ключ есть
bad_index_usedСервер сообщил об отсутствии подходящего индекса. Для выбора индекса нужен EXPLAIN и смысл фильтра.ключ есть
slow_queriesЗапросы, отвечающие серверным условиям long_query_time и min_examined_row_limit; mysqli_report не сообщает этот показатель как отдельную ошибку.ключ есть
buffered_setsБуферизованные обычные результаты; compare с unbuffered_sets, учитывая выбранный API.ключ есть
unbuffered_setsОбычные результаты, читаемые без полного клиентского буфера.ключ есть
ps_buffered_setsБуферизованные результаты prepared statements, например через stmt->store_result.ключ есть
ps_unbuffered_setsНебуферизованные prepared-результаты; подготовленный запрос по умолчанию относится сюда.ключ есть
flushed_normal_setsОбычные unbuffered-наборы с непрочитанным приложением остатком, который драйвер дочитал для очистки соединения.ключ есть
flushed_ps_setsАналогичная принудительная дочитка prepared-результатов. Break и аварийное завершение требуют внимания.ключ есть
ps_prepared_never_executedПодготовленные statements, которые так и не исполнили; возможна лишняя подготовка.ключ есть
ps_prepared_once_executedStatements, выполненные ровно один раз; это не само по себе ошибка использования prepare.ключ есть
rows_fetched_from_server_normalСтроки, полученные драйвером с сервера для обычного запроса, в том числе неиспользованный приложением остаток при дочитывании.ключ есть
rows_fetched_from_server_psСтроки, полученные драйвером с сервера для prepared запроса, в том числе неиспользованный приложением остаток при дочитывании.ключ есть
rows_buffered_from_client_normalСтроки, помещённые в клиентский буфер обычного результата; не обязательно уже обработаны PHP.ключ есть
rows_buffered_from_client_psСтроки, помещённые в клиентский буфер prepared результата; не обязательно уже обработаны PHP.ключ есть
rows_fetched_from_client_normal_bufferedСтроки, полученные приложением из обычного буферизованного результата. Это документированное назначение; сравнивайте с собственным числом обработанных строк. В нашем обычном buffered-fetch PHP 8.5.11 соответствующий счётчик остался 0 при шести фактически прочитанных строках; это наблюдение данного пути, не правило для всех API и версий.ключ есть
rows_fetched_from_client_ps_bufferedСтроки, полученные приложением из prepared буферизованного результата. Это документированное назначение; сравнивайте с собственным числом обработанных строк. В нашем обычном buffered-fetch PHP 8.5.11 соответствующий счётчик остался 0 при шести фактически прочитанных строках; это наблюдение данного пути, не правило для всех API и версий.ключ есть
rows_fetched_from_client_normal_unbufferedСтроки, полученные приложением из обычного небуферизованного результата. Это документированное назначение; сравнивайте с собственным числом обработанных строк. В нашем обычном buffered-fetch PHP 8.5.11 соответствующий счётчик остался 0 при шести фактически прочитанных строках; это наблюдение данного пути, не правило для всех API и версий.ключ есть
rows_fetched_from_client_ps_unbufferedСтроки, полученные приложением из prepared небуферизованного результата. Это документированное назначение; сравнивайте с собственным числом обработанных строк. В нашем обычном buffered-fetch PHP 8.5.11 соответствующий счётчик остался 0 при шести фактически прочитанных строках; это наблюдение данного пути, не правило для всех API и версий.ключ есть
rows_fetched_from_client_ps_cursorСтроки, полученные приложением из prepared серверного курсора. Это документированное назначение; сравнивайте с собственным числом обработанных строк. В нашем обычном buffered-fetch PHP 8.5.11 соответствующий счётчик остался 0 при шести фактически прочитанных строках; это наблюдение данного пути, не правило для всех API и версий.ключ есть
rows_skipped_normalВ официальном описании зарезервировано и не поддержано; присутствие ключа не означает работающий счётчик.ключ есть
rows_skipped_psТакже обозначено как резерв для будущего; не используем для доказательства потери строк.ключ есть
copy_on_write_savedИсторическая процессная метрика избегания копии данных; реальная полезность зависит от версии реализации.ключ есть
copy_on_write_performedПроцессные операции copy-on-write; не путать с числом SQL-строк или общим расходом памяти.ключ есть
explicit_free_resultЧисло явных освобождений результата; доступно в статистике соединения/процесса.ключ есть
implicit_free_resultЧисло освобождений результата драйвером; позднее освобождение может удерживать память дольше.ключ есть
proto_text_fetched_nullКоличество извлечённых значений столбцов типа NULL; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_nullКоличество извлечённых значений столбцов типа NULL; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_bitКоличество извлечённых значений столбцов типа BIT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_bitКоличество извлечённых значений столбцов типа BIT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_tinyintКоличество извлечённых значений столбцов типа TINYINT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_tinyintКоличество извлечённых значений столбцов типа TINYINT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_shortКоличество извлечённых значений столбцов типа SMALLINT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_shortКоличество извлечённых значений столбцов типа SMALLINT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_int24Количество извлечённых значений столбцов типа MEDIUMINT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_int24Количество извлечённых значений столбцов типа MEDIUMINT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_intКоличество извлечённых значений столбцов типа INT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_intКоличество извлечённых значений столбцов типа INT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_bigintКоличество извлечённых значений столбцов типа BIGINT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_bigintКоличество извлечённых значений столбцов типа BIGINT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_decimalКоличество извлечённых значений столбцов типа DECIMAL/NEWDECIMAL; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_decimalКоличество извлечённых значений столбцов типа DECIMAL/NEWDECIMAL; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_floatКоличество извлечённых значений столбцов типа FLOAT; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_floatКоличество извлечённых значений столбцов типа FLOAT; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_doubleКоличество извлечённых значений столбцов типа DOUBLE; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_doubleКоличество извлечённых значений столбцов типа DOUBLE; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_dateКоличество извлечённых значений столбцов типа DATE/NEWDATE; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_dateКоличество извлечённых значений столбцов типа DATE/NEWDATE; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_yearКоличество извлечённых значений столбцов типа YEAR; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_yearКоличество извлечённых значений столбцов типа YEAR; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_timeКоличество извлечённых значений столбцов типа TIME; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_timeКоличество извлечённых значений столбцов типа TIME; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_datetimeКоличество извлечённых значений столбцов типа DATETIME; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_datetimeКоличество извлечённых значений столбцов типа DATETIME; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_timestampКоличество извлечённых значений столбцов типа TIMESTAMP; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_timestampКоличество извлечённых значений столбцов типа TIMESTAMP; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_stringКоличество извлечённых значений столбцов типа STRING/VARSTRING/VARCHAR; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_stringКоличество извлечённых значений столбцов типа STRING/VARSTRING/VARCHAR; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_blobКоличество извлечённых значений столбцов типа семейство BLOB; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_blobКоличество извлечённых значений столбцов типа семейство BLOB; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_enumКоличество извлечённых значений столбцов типа ENUM; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_enumКоличество извлечённых значений столбцов типа ENUM; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_setКоличество извлечённых значений столбцов типа SET; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_setКоличество извлечённых значений столбцов типа SET; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_geometryКоличество извлечённых значений столбцов типа GEOMETRY; текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_geometryКоличество извлечённых значений столбцов типа GEOMETRY; бинарный prepared протокол. Это значения, не число запросов.ключ есть
proto_text_fetched_otherКоличество извлечённых значений столбцов типа другие типы (обычно ожидается 0); текстовый обычный протокол. Это значения, не число запросов.ключ есть
proto_binary_fetched_otherКоличество извлечённых значений столбцов типа другие типы (обычно ожидается 0); бинарный prepared протокол. Это значения, не число запросов.ключ есть
connect_successУспешные подключения, включая persistent; nonpersistent можно оценить разностью с pconnect_success.ключ есть
pconnect_successУспешные persistent-подключения; не то же самое, что reuse уже открытого соединения.ключ есть
connect_failureНеудачные попытки подключиться.ключ есть
reconnectПроцессная метрика повторных соединений; не является рекомендацией автоматически повторять бизнес-операцию.ключ есть
active_connectionsАктивные persistent и обычные соединения процесса; число меняется, это не накопительный итог.ключ есть
active_persistent_connectionsАктивные persistent-соединения процесса; разность с active_connections даёт обычные.ключ есть
explicit_closeЯвное закрытие соединения приложением; повторное connect тоже может закончить прежнее.ключ есть
implicit_closeНеявное закрытие: уборка процесса/объекта, непригодный pooled-handle или другая уборка драйвера.ключ есть
disconnect_closeОтказы соединения, зафиксированные при попытке real_connect; интерпретируется вместе с контекстом ошибки.ключ есть
in_middle_of_command_closeПроцесс закрыл соединение при незавершённой команде/выдаче; для синхронного нормального пути это подозрительно.ключ есть
init_command_executed_countВыполнения команды инициализации соединения; успешные можно оценить за вычетом failed.ключ есть
init_command_failed_countНеудачи команды инициализации; проверять до нормальной работы приложения.ключ есть
com_quitПопытки отправить COM-команду: завершение соединения. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_init_dbПопытки отправить COM-команду: выбор базы. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_queryПопытки отправить COM-команду: обычный SQL. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_field_listПопытки отправить COM-команду: список полей. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_create_dbПопытки отправить COM-команду: создание базы (историческая команда). Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_drop_dbПопытки отправить COM-команду: удаление базы (историческая команда). Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_refreshПопытки отправить COM-команду: обновление серверных состояний. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_shutdownПопытки отправить COM-команду: остановка сервера. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_statisticsПопытки отправить COM-команду: серверная статистика. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_process_infoПопытки отправить COM-команду: список процессов. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_connectПопытки отправить COM-команду: подключение. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_process_killПопытки отправить COM-команду: завершение процесса. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_debugПопытки отправить COM-команду: отладочная команда. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_pingПопытки отправить COM-команду: проверка связи. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_timeПопытки отправить COM-команду: историческая команда времени. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_delayed_insertПопытки отправить COM-команду: историческая отложенная вставка. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_change_userПопытки отправить COM-команду: смена/повторная аутентификация. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_binlog_dumpПопытки отправить COM-команду: передача binlog. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_table_dumpПопытки отправить COM-команду: историческая выгрузка таблицы. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_connect_outПопытки отправить COM-команду: историческое исходящее подключение. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_register_slaveПопытки отправить COM-команду: историческое имя команды регистрации replica. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_prepareПопытки отправить COM-команду: подготовка statement. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_executeПопытки отправить COM-команду: выполнение prepared statement. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_send_long_dataПопытки отправить COM-команду: порция большого параметра. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_closeПопытки отправить COM-команду: закрытие statement. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_resetПопытки отправить COM-команду: сброс statement. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_set_optionПопытки отправить COM-команду: изменение опции протокола. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_stmt_fetchПопытки отправить COM-команду: чтение курсора. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ есть
com_daemonПопытки отправить COM-команду: служебная команда daemon. Увеличение происходит перед отправкой и не доказывает успех на сервере.ключ отсутствует
explicit_stmt_closeПроцессное закрытие подготовленного statement явным путём.ключ есть
implicit_stmt_closeНеявное закрытие statement; manual связывает это прежде всего с неудачной подготовкой.ключ есть
mem_emalloc_countПроцессная память: emalloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_emalloc_amountПроцессная память: emalloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_ecalloc_countПроцессная память: ecalloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_ecalloc_amountПроцессная память: ecalloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_realloc_countПроцессная память: realloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_realloc_amountПроцессная память: realloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_efree_countПроцессная память: efree; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_malloc_countПроцессная память: malloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_malloc_amountПроцессная память: malloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_calloc_countПроцессная память: calloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_calloc_amountПроцессная память: calloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_erealloc_countПроцессная память: erealloc; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_erealloc_amountПроцессная память: erealloc; суммарные байты операций. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
mem_free_countПроцессная память: free; число вызовов. Требуется collect_memory_statistics; не равно живой памяти процесса.ключ есть
command_buffer_too_smallРасширения сетевого командного буфера сверх начального размера; частые росты помогают обосновать настройку.ключ есть
connection_reusedПовторные получения persistent-соединения из пула.ключ есть

COM-счётчик увеличивается перед отправкой: он не уменьшается автоматически при сетевом отказе. Поэтому com_stmt_execute/com_stmt_prepare помогает оценить повторное использование подготовки, но не число успешных транзакций. Сетевые байты включают служебную нагрузку; real_data отражает данные, дошедшие до PHP-fetch. rows_fetched_from_server включает дочитанный остаток unbuffered. Документированные client-fetch счётчики следует сверять с реальными строками: в нашем buffered-пути один из них остался нулём, поэтому он не служит единственным доказательством чтения. Для slow/no_index нужны серверные пороги и план; MYSQLI_REPORT_INDEX может сообщать индексные признаки, но не превращает slow_queries в исключение.

Память PHP, буфер результата и copy-on-write

Mysqlnd выделяет память через менеджер PHP. memory_get_usage отражает этот учёт и memory_limit его ограничивает. Менеджер может удерживать страницы у процесса после логического освобождения; RSS не обязан сразу уменьшаться. Историческое сравнение с libmysqlclient осложнено тем, что тот использовал собственные C-выделения вне PHP memory_limit.

Путь данных — сетевой пакет, внутренний буфер, затем PHP-значения. В исторических реализациях mysqlnd использовал ссылки на буфер и copy-on-write: изменение извлечённой переменной или освобождение буфера при живых переменных могло вызвать копию. Не превращайте старую реализационную схему в универсальную гарантию числа копий PHP 8.5: смотрите текущие измерения. Небольшие строки тоже имеют накладные расходы массивов/ссылок. Директива fetch_data_copy удалена в 8.1; совет включить её в современной среде не работает.

Практическая диагностика начинается со статистики и собственного участка измерения. Debug trace может показывать операции памяти, но не обязательно размер каждой порции. Внутренние режимы имитации out-of-memory предназначены разработчикам C-драйвера и не являются стабильным пользовательским API; мы их не включаем и не выдаём учебный пример за тест всех путей аварийного выделения.

Границы persistent и очистка соединения

Persistent сохраняет пригодное соединение внутри процесса, а не данные пользователя. Mysqli использует p: в имени хоста, обычная сборка при reuse выполняет COM_CHANGE_USER с повторной аутентификацией и очисткой. Это имеет стоимость. Компиляционный MYSQLI_NO_CHANGE_USER_ON_PCONNECT меняет путь на ping и убирает обычную очистку; главы не предлагают эту оптимизацию. Другой API/driver может иметь иной жизненный цикл; переносить правила mysqli на PDO нельзя.

Состояние прошлой сессииОбычная очистка mysqli
Незавершённая транзакцияRollback
TEMPORARY TABLEЗакрыть и удалить
Table locks и GET_LOCKОсвободить
Session variablesСбросить
Prepared statements и handlersЗакрыть

Результаты нашего CLI подтверждают reuse, сброс пользовательской переменной и удаление temporary table. Они не доказывают балансировку FPM, производительность пула или отсутствие ошибок приложения. Завершайте транзакции сами, устанавливайте нужные настройки при каждом получении соединения, не используйте состояние persistent-сессии как пользовательскую память.

Streams, таймауты и несовместимости старых библиотек

Mysqlnd использует PHP streams для транспорта, поэтому таймауты нужно интерпретировать по его настройкам и документации streams, а не переносить все детали MYSQL_OPT_READ_TIMEOUT из libmysqlclient. net_read_timeout задаёт ожидание чтения, а не точный общий бюджет SQL/HTTP; серверные max_execution_time/locks и срок HTTP-запроса — другие уровни. Командный буфер расширяется для длинной команды; рост command_buffer_too_small показывает причину рассмотреть размер, но не обещает ускорение от любого увеличения. Порция чтения ограничивает отдельное read тела пакета, не общий размер результата.

Аутентификация с публичным RSA-ключом и шифрованный проверенный TLS-канал — разные задачи. Выбор keyfile и способ обмена зависят от протокола/сервера; пароль в логи и архивы не помещают. В историческом разделе несовместимостей BIT у libmysqlclient возвращался бинарной строкой, у mysqlnd — десятичной строкой. Для общего числового договора выбирайте SQL вида SELECT flags + 0 AS flags_number FROM table_name. BIT + 0 здесь — поясняющий фрагмент формы запроса, не отдельный выполненный пример. Различия NULL, пустой строки и нуля разобраны в примере необязательного значения; сохранение и чтение цены DECIMAL — в примере создания товарной таблицы. Не приводите любое бинарное значение к int без знания SQL-типа.

Диагностировать по причине, затем менять поведение

Если приложение использует 1 из 10000 строк, сначала исправьте SQL. Если сохраняет все 10000 в массиве, unbuffered не устранит это накопление. Если выдача недочитана, завершите её до нового SQL. Если мало соединений на сервере, посчитайте worker и их persistent-пулы. Меняйте один фактор и сохраняйте вход, результат и измерение. Внутренний C Plugin API mysqlnd — отдельная тема следующего пакета; наличие этих глав не объявляет её уже раскрытой.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.