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

Дерево руководства
Глава 47 · После жизненного цикла результата MySQL
Mysqlnd: статистика, память, настройки и persistent-соединения
Измеряем реальный участок запросов, видим память данных и проверяем очистку повторно используемой сессии.
Mysqlnd: драйвер под mysqli и PDO_MYSQL
Mysqlnd — нативный MySQL-драйвер проекта PHP, включённый в распространение PHP с 5.3. Он работает ниже пользовательских mysqli/PDO_MYSQL, а не вводит отдельный mysqlnd_query. Сам сервер MySQL — другой процесс и отдельная установка. Клиентская библиотека не требует локального сервера для сборки/подключения к удалённому серверу; наша лаборатория намеренно использует локальный.
- PHP-приложение: параметры, бизнес-правила, вывод
- mysqli или PDO_MYSQL: пользовательский API
- mysqlnd: протокол, память результата, статистика
- 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, кодировку соединения и включённость измерений.
- mysqli → клиентская библиотека → MySQL server
- mysqli_get_client_info → реальный клиент
- ini_get → значения текущего PHP
- utf8mb4 → состояние открытого соединения
- diagnostics.json → версии и настройки
Полученный результат
| Check | Value |
|---|---|
| mysqlnd client | true |
| connection utf8mb4 | true |
| statistics enabled | true |
| memory statistics enabled | true |
| positive read timeout | true |
$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
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.
- Создание данных завершено
- before = connection stats
- SELECT1 → 4 строки; SELECT2 → 2 строки
- after = connection stats
- after − before → дельты запросов, fetch и трафика
Полученный результат
| Observation | Value |
|---|---|
| application rows | 6 |
| SELECT result sets | 2 |
| buffered result sets | 2 |
| rows received from server | 6 |
| rows placed in buffer | 6 |
| client fetch counter (observed) | 0 |
| received bytes increased | true |
$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-таблице запуска.
- SELECT → буфер mysqlnd
- fetch_all → пользовательский массив
- free → результат освобождён, массив жив
- unset массива → данные больше не нужны
- memory_get_usage(false) → наблюдаемое выделение PHP
Числа зависят от сборки и запуска. Ниже загружается фактически сохранённая таблица, а не заранее выбранные «красивые» значения.
Сохранённый результат реального запуска
Реальные измерения памяти этого запуска
| Stage | PHP allocated bytes |
|---|---|
| before query | 556184 |
| buffered result | 1263440 |
| PHP array also alive | 2366568 |
| result freed, PHP array alive | 1659472 |
| PHP array unset | 556344 |
$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
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-процессе.
- HOST с p: → persistent handle
- SELECT7 → close handle
- Повторные те же параметры → handle2
- thread_id тот же; connection_reused увеличился
- SELECT8 работает
Полученный результат
| Observation | Value |
|---|---|
| first query | 7 |
| second query | 8 |
| same server connection | true |
| reuse counter increased | true |
$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 убирает прежнее состояние перед новым использованием.
- handle1: @user_note и TEMPORARY TABLE
- close → вернуть persistent-соединение
- handle2: повторная аутентификация/очистка
- @note → NULL; таблица → ошибка 1146
- set_charset → явный utf8mb4
Полученный результат
| Observation | Value |
|---|---|
| same server connection | true |
| old user variable | NULL |
| temporary table missing | true |
| connection charset | utf8mb4 |
$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 результата · Измерения и диагностика.
<?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 с тем же первым товаром.
- Исходный SELECT → returned4
- Приложение fetch1 → used1
- free → остальные данные не нужны
- LIMIT1 → returned1 и used1
- INI + scoped stats → отчёт без пароля
Полученный результат
| Variant | Rows returned | Rows used |
|---|---|---|
| original SELECT | 4 | 1 |
| with LIMIT 1 | 1 | 1 |
$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_statistics | 1 / INI_SYSTEM | INI_ALL; текущее 1 | Сбор общей статистики клиента; без него значения не доказывают отсутствие запросов. |
mysqlnd.collect_memory_statistics | 0 / INI_SYSTEM | INI_SYSTEM; текущее 1 | Дополнительные счётчики операций памяти; включение увеличивает диагностическую работу. |
mysqlnd.debug | пусто / INI_SYSTEM | INI_SYSTEM; текущее None | Трассировка связи для debug-сборки; формат option[,argument]:option. Лог может быть объёмным и чувствительным. |
mysqlnd.log_mask | 0 / INI_ALL | INI_ALL; текущее 0 | Числовая маска серверных флагов для логирования; 0 отключает. См. полный перечень ниже. |
mysqlnd.mempool_default_size | 16000 / INI_ALL | INI_ALL; текущее 16000 | Начальный размер пула памяти для результатов в байтах; не лимит результата и не общий memory_limit. |
mysqlnd.net_read_timeout | 86400 / INI_ALL | INI_ALL; текущее 86400 | Таймаут сетевого чтения в секундах, не серверный предел длительности SQL. До PHP 7.2 default 31536000 и INI_SYSTEM. |
mysqlnd.net_cmd_buffer_size | 4096 / INI_SYSTEM | INI_ALL; текущее 4096 | Начальный командный буфер; minimum 4096. При росте увеличивается command_buffer_too_small. Возможна mysqli option MYSQLI_OPT_NET_CMD_BUFFER_SIZE. |
mysqlnd.net_read_buffer_size | 32768 / INI_SYSTEM | INI_ALL; текущее 32768 | Максимальная порция чтения тела пакета; меньшее тело читается целиком, большое порциями. Возможна MYSQLI_OPT_NET_READ_BUFFER_SIZE. |
mysqlnd.sha256_server_public_key | пусто / INI_PERDIR | INI_PERDIR; текущее None | Файл публичного RSA-ключа для SHA-256-аутентификации. Это не пароль и не универсальная замена проверке TLS-сертификата. |
mysqlnd.trace_alloc | пусто / INI_SYSTEM | INI_SYSTEM; текущее None | Внутренняя диагностика выделений; manual перечисляет директиву без полного прикладного контракта. Не предлагаем её как настройку обычного сайта. |
mysqlnd.fetch_data_copy | 0 (исторически) / 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-файла в строке лога. |
| i | PID процесса. |
| L | Номер строки исходника. |
| n | Глубина вложенности функции. |
| o[,file] | Как a, но перезаписывать файл. |
| O[,file] | Как A, но перезаписывать файл. |
| t[,N] | Трасса вызовов, глубина по умолчанию 200. |
| x | Профилирование. |
| m | Операции выделения/освобождения памяти. |
| Флаг log_mask | Значение |
|---|---|
| SERVER_STATUS_IN_TRANS | 1 |
| SERVER_STATUS_AUTOCOMMIT | 2 |
| SERVER_MORE_RESULTS_EXISTS | 8 |
| SERVER_QUERY_NO_GOOD_INDEX_USED | 16 |
| SERVER_QUERY_NO_INDEX_USED | 32 |
| SERVER_STATUS_CURSOR_EXISTS | 64 |
| SERVER_STATUS_LAST_ROW_SENT | 128 |
| SERVER_STATUS_DB_DROPPED | 256 |
| SERVER_STATUS_NO_BACKSLASH_ESCAPES | 512 |
| SERVER_QUERY_WAS_SLOW | 1024 |
Формат 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_executed | Statements, выполненные ровно один раз; это не само по себе ошибка использования 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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


