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

Дерево руководства
Глава 42 · Первое знакомство с MySQL
MySQL с нуля: подключение, первая таблица и данные в HTML
От различия сервера, базы и соединения до MySQLi/PDO, создания каталога, чтения строк и понятных ошибок.
Как PHP, MySQL, база и таблица связаны друг с другом
Нужна витрина, в которой товар имеет название и остаток, а изменение сохраняется для следующих запросов сайта. MySQL хранит такие данные на сервере; PHP отправляет команды и превращает ответ в страницу. Здесь начнём с первого соединения и трёх строк товаров. Временные таблицы используются только для воспроизводимого упражнения; в настоящем сайте каталог хранится в постоянных таблицах.
- Браузер просит страницу каталога
- PHP вызывает mysqli или PDO_MYSQL
- Клиентская библиотека передаёт SQL серверу MySQL
- Сервер работает с базой → таблицей → строками
- Ответ → PHP-массив → HTML → браузер
| Понятие | Что это в примере | Чем не является |
|---|---|---|
| Сервер MySQL | Отдельный запущенный процесс, принимающий подключения на 127.0.0.1:33070 | Не PHP-файл и не WordPress-плагин |
| База / схема | Именованная группа таблиц в этом сервере | Не массив PHP и не одно соединение |
| Таблица | products: колонки id/name/stock, строки товаров | Не HTML-таблица браузера; HTML строится позже |
| Соединение с MySQL | Канал текущей программы к серверу с пользователем и настройками | Не копия всей базы |
| Запрос SQL | Команда SELECT/INSERT/UPDATE для сервера | Не PHP-функция, хотя передаётся как PHP-строка |
| Результат | Набор выбранных строк, который читает PHP | Не автоматический вывод на страницу |
Реляционная база представляет данные отношениями: таблицы описывают сущности, ключи связывают строки. В первой задаче таблица одна. Затем свяжем покупателя, заказ, позиции и товары и разберём первичные/внешние ключи. Не нужно сначала понимать весь SQL, чтобы получить первый ответ, но соединение, схема и таблица уже должны различаться.
Подготовить отдельное окружение и задать параметры
Для запуска нужны: установленный PHP с mysqli, отдельный работающий MySQL, созданная учебная база и пользователь с правом CREATE TEMPORARY TABLES на неё. Для сравнения PDO включите pdo_mysql. PHP CLI и веб-сервер могут иметь разные ini; установка расширения в одном не включает его автоматически в другом. Проверка пакета выполняется на PHP8.5.11/mysqlnd8.5.11 и MySQL8.4.13 LTS. Примеры используют базовые возможности PHP8.2+; это не утверждение о проверке всех сочетаний старых версий.
php --ini
php --ri mysqli
php --ri pdo_mysqlMySQL устанавливается отдельно из официального дистрибутива или пакета вашей ОС; инструкция: установка MySQL8.4. На Windows расширения mysqli/pdo_mysql включают соответствующими DLL в PHP; в Linux — пакетами именно используемой версии PHP. Workbench и phpMyAdmin — клиенты управления, наличие их окна ещё не означает, что сервер запущен. Администратор готовит отдельную учебную схему и ограниченную учётную запись; программы не создают пользователей и не меняют серверную конфигурацию.
Один раз подготовить свою учебную базу
Откройте SQL-клиент своего локального сервера под учётной записью администратора. На свежем учебном сервере выполните команды ниже, заменив шаблон пароля своим. Это подготовка окружения, не код PHP-страницы. Названия phpmanual_training и phpmanual_student намеренно учебные. Если такое имя уже существует, проверьте его назначение или выберите другое; не удаляйте неизвестную базу ради запуска примера.
CREATE DATABASE phpmanual_training
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'phpmanual_student'@'127.0.0.1'
IDENTIFIED BY 'REPLACE_WITH_YOUR_OWN_LOCAL_PASSWORD';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX,
REFERENCES, CREATE TEMPORARY TABLES
ON phpmanual_training.*
TO 'phpmanual_student'@'127.0.0.1';CREATE DATABASE создаёт пустую схему. CREATE USER создаёт отдельную учётную запись, причём часть после @ ограничивает источник подключения; пароль не является именем базы. GRANT перечисляет разрешения только на phpmanual_training.*: таблицы этой схемы, не все базы сервера. Полный набор используется соседними SQL-уроками с постоянными таблицами, изменением структуры и внешними ключами. Эти двенадцать TEMPORARY-примеров требуют только создания временных таблиц в выделенной схеме. Не добавляйте GRANT OPTION, глобальный *.* или административные права приложению. Если сервер находится на другом хосте, продуманная сетевая конфигурация и TLS — отдельная задача; этот комплект локальный.
mysql --protocol=TCP --host=127.0.0.1 --port=33070 --user=phpmanual_student --password
SELECT VERSION();
SHOW DATABASES;
USE phpmanual_training;
SELECT 2 + 3 AS total;Если появилась строка total=5, сервер принимает TCP-подключение, пароль принят и учебная схема доступна. Connection refused до ввода SQL означает, что сначала надо проверить запуск службы и порт. Access denied означает уже другой этап — аутентификацию/права. Unknown database после USE означает неверное или не созданное имя. Подробный разбор ниже разделяет эти ситуации. 127.0.0.1 и явный порт выбирают TCP; localhost может использовать сокет или named pipe в зависимости от системы, поэтому для опыта оставляем явный IP.
| Переменная окружения | Что задать | Как участвует в подключении |
|---|---|---|
| PHPMAN_MYSQL_HOST | 127.0.0.1 | Адрес локального сервера; явный IP выбирает TCP, в отличие от особых правил localhost/socket |
| PHPMAN_MYSQL_PORT | 33070 для этого стенда; свой порт в другом окружении | Номер точки подключения; стандартный MySQL-порт часто 3306 |
| PHPMAN_MYSQL_DATABASE | Имя выделенной учебной базы | Она должна существовать до подключения |
| PHPMAN_MYSQL_USER | Имя учебной учётной записи | Пользователь MySQL, не логин Windows/WordPress |
| PHPMAN_MYSQL_PASSWORD | Её собственный пароль локально | Не включать в исходники, публичный HTML, архив или чат |
$host = getenv('PHPMAN_MYSQL_HOST');
$user = getenv('PHPMAN_MYSQL_USER');
$password = getenv('PHPMAN_MYSQL_PASSWORD');
$database = getenv('PHPMAN_MYSQL_DATABASE');
$port = (int)getenv('PHPMAN_MYSQL_PORT');getenv читает строку из окружения процесса. В PowerShell переменные текущего терминала задаются как $env:ИМЯ = 'значение'; в sh — export ИМЯ='значение'. Затем в этом же терминале выполните php example.php. Проверка подключения не требует загружать пароль на сайт. Архивы содержат только имена настроек и helper, который проверяет их наличие; реальных паролей там нет. По умолчанию helper допускает только локальный адрес, чтобы учебное упражнение не ушло в рабочую базу.
$env:PHPMAN_MYSQL_HOST='127.0.0.1'
$env:PHPMAN_MYSQL_PORT='33070'
$env:PHPMAN_MYSQL_DATABASE='phpmanual_training'
$env:PHPMAN_MYSQL_USER='phpmanual_student'
$env:PHPMAN_MYSQL_PASSWORD='REPLACE_WITH_YOUR_OWN_LOCAL_PASSWORD'
php example.phpВ Unix-терминале замените присваивания на export PHPMAN_MYSQL_HOST='127.0.0.1' и так же задайте остальные четыре переменные, затем выполните ту же команду php. В новом терминале настройки предыдущего процесса могут отсутствовать. Для веб-сервера используется его закрытая конфигурация окружения, а не значения из чужого интерактивного терминала. Файлы с настройками не кладут в публичный каталог сайта.
Подключиться к серверу и получить первый ответ
Сначала убедимся, что PHP действительно разговаривает с сервером MySQL. Попросим сервер вычислить сумму 2 + 3: получим одну строку с колонкой total, затем число 5. Это маленькая проверка адреса, учётной записи и доступности базы до разработки каталога.
- PHP: new mysqli(...) → соединение
- query: SQL «SELECT 2 + 3 AS total» → MySQL
- MySQL: строка total=5 → mysqli_result
- fetch_assoc: ["total" => "5"] → PHP
- Вывод / result.html: 5
Ожидаемые значения, подтверждаемые реальным запуском
| API | Клиентская библиотека | Соединение | 2 + 3 |
|---|---|---|---|
| mysqli | mysqlnd | ready | 5 |
$db = new mysqli($host, $user, $password, $database, $port);
$result = $db->query('SELECT 2 + 3 AS total');
$row = $result->fetch_assoc();
echo $row['total'];new создаёт объект соединения; $db хранит этот объект, а не таблицу. Стрелка -> вызывает действие объекта. query отправляет строку SQL: SELECT выбирает значение, AS даёт колонке имя total. Для этого вычисления таблица не требуется. Результат query при SELECT — mysqli_result, набор строк; fetch_assoc извлекает следующую строку в ассоциативный массив. $row["total"] читает колонку по имени. echo показывает значение. Точка с запятой заканчивает PHP-инструкцию; кавычки ограничивают SQL-строку, сами по сети как внешняя оболочка не передаются.
В четырёх строках $host, $user, $password, $database, $port — уже заданные настройки. Полный комплект ниже читает их через getenv в mysql106Config, затем выполняет тот же запрос. mysql106Report только сохраняет HTML/JSON и сведения о запуске; для самого SELECT этот отчётный helper не нужен. В полном примере числовое значение приводится к int для однозначного JSON: обычный текстовый протокол часто возвращает числа строками. С mysqlnd на это также влияют настройки native numeric conversion; подготовленные запросы используют другой протокол.
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$c = mysql106Config();
$db = new mysqli($c['host'], $c['user'], $c['password'], $c['database'], $c['port']);
$db->set_charset('utf8mb4');
$result = $db->query('SELECT 2 + 3 AS total');
$row = $result->fetch_assoc();
$total = (int)$row['total'];
$rows = [['mysqli', 'mysqlnd', 'ready', $total]];
$runtime = mysql106Runtime($db);
$checks = ['server_computed_sum'=>$total===5, 'mysqlnd_loaded'=>str_contains($db->client_info,'mysqlnd'), 'charset_utf8mb4'=>$db->character_set_name()==='utf8mb4'];
$result->free();
$db->close();
mysql106Report('mysql106-connect-diagnostics', ['API', 'Клиентская библиотека', 'Соединение', '2 + 3'], $rows, $checks, $runtime, $details);
echo "connection: ready; server calculated 2 + 3 = 5\n";
Ожидаемый результат
connection: ready; server calculated 2 + 3 = 5
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
Код не устанавливает MySQL и не создаёт базу. Если соединение не удалось, до query выполнение не дойдёт: переходите к разделу ошибок соединения. Успешный SELECT 2+3 подтверждает связь, но ещё не права на рабочие таблицы и не их существование. PHP_VERSION, версия клиентской библиотеки и версия сервера — три разные величины; actual metrics сохраняет все три.
Создать таблицу товаров, прочитать строки и вывести HTML
Для небольшой витрины нужно вывести названия и остатки трёх товаров. Теперь результат берётся из настоящей таблицы, которую полный пример сам создаёт и заполняет. Чай имеет остаток 12, Кофе — 0, Какао — 7; ноль не исчезнет при обходе.
- CREATE: колонки id / name / stock
- INSERT: три строки товаров
- SELECT: id,name,stock ORDER BY id
- fetch_assoc по одной строке → while
- Значения ячеек → htmlspecialchars → HTML-таблица
Ожидаемые значения, подтверждаемые реальным запуском
| id | name | stock |
|---|---|---|
| 1 | Чай | 12 |
| 2 | Кофе | 0 |
| 3 | Какао | 7 |
$result = $db->query('SELECT id,name,stock FROM mysql106_basic_products ORDER BY id');
while ($row = $result->fetch_assoc()) {
echo htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8'), ': ', $row['stock'], "\n";
}CREATE TEMPORARY TABLE задаёт структуру таблицы на время текущего соединения. id INT PRIMARY KEY — целое уникальное имя строки, name VARCHAR(60) NOT NULL — обязательный текст до 60 символов, stock INT NOT NULL — обязательное целое количество. PRIMARY KEY не означает позицию в выдаче. ENGINE=InnoDB выбирает механизм хранения с транзакциями; DEFAULT CHARSET=utf8mb4 задаёт кодировку текстовых столбцов. INSERT перечисляет имена колонок, затем VALUES с тройками значений в том же порядке. SELECT id,name,stock выбирает колонки; FROM указывает таблицу; ORDER BY id задаёт порядок по возрастанию. Без ORDER BY порядок не обещан.
while повторяет тело, пока fetch_assoc возвращает очередной массив. Когда строк больше нет, mysqli возвращает null; при ошибке возможен false, а включённый строгий режим переводит ошибки в исключения. Остаток 0 находится внутри массива и не прерывает цикл. $rows[] добавляет прочитанную строку в PHP-список; это уже отдельные данные приложения. HTML-отчёт строит <tr> для строки и <td> для каждого значения; htmlspecialchars выводит название как текст, даже если в нём есть < или &. Экранирование HTML не заменяет SQL-параметры: это разные границы.
<table>
<?php foreach ($rows as $row): ?>
<tr>
<td><?= htmlspecialchars($row[1], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= $row[2] ?></td>
</tr>
<?php endforeach ?>
</table>Здесь foreach перебирает готовый массив, <tr> создаёт HTML-строку, <td> — ячейку. <?= — короткая запись echo. Сервер выполняет PHP и отправляет браузеру готовые теги; браузер не подключается напрямую к MySQL. Полный пример сохраняет аналогичную таблицу в result.html, поэтому её можно открыть и после остановки PHP.
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
$db = mysql106Connect();
$db->query('CREATE TEMPORARY TABLE mysql106_basic_products (id INT PRIMARY KEY, name VARCHAR(60) NOT NULL, stock INT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$db->query("INSERT INTO mysql106_basic_products (id,name,stock) VALUES (1,'Чай',12),(2,'Кофе',0),(3,'Какао',7)");
$result = $db->query('SELECT id,name,stock FROM mysql106_basic_products ORDER BY id');
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = [(int)$row['id'], $row['name'], (int)$row['stock']];
}
$checks = ['three_products'=>count($rows)===3, 'zero_stock_preserved'=>$rows[1][2]===0, 'end_is_null'=>$result->fetch_assoc()===null];
$runtime = mysql106Runtime($db);
$result->free();
$db->close();
mysql106Report('mysql106-first-table', ['id', 'name', 'stock'], $rows, $checks, $runtime, $details);
echo "products: 3; stock: 12, 0, 7; result.html saved\n";
Ожидаемый результат
products: 3; stock: 12, 0, 7; result.html saved
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
TEMPORARY выбрана для безопасного самостоятельного упражнения: таблица видна только своей сессии и исчезает после close. result.html/result.json сохраняются в папке. Для настоящего каталога нужна постоянная таблица, создаваемая миграцией один раз, а не на каждом запросе страницы. Дальше это подробно раскрывает проектирование постоянных таблиц и связей. Большую выдачу не собирают без ограничений в $rows: в главе о буферах и потоке показана стоимость результата.
Прочитать такие же данные через PDO_MYSQL
Ту же витрину можно читать через PDO — общий PHP-интерфейс для разных СУБД. Проверим одинаковые строки через другой API. Исходная MySQL-таблица и SQL остаются понятными; меняются создание соединения и извлечение строки.
- PDO + DSN mysql:host...;dbname...;charset...
- PDO_MYSQL → mysqlnd → сервер
- query → PDOStatement
- fetch(PDO::FETCH_ASSOC) → массив / false
- Те же Чай=12, Кофе=0, Какао=7
Ожидаемые значения, подтверждаемые реальным запуском
| id | name | stock |
|---|---|---|
| 1 | Чай | 12 |
| 2 | Кофе | 0 |
| 3 | Какао | 7 |
$statement = $pdo->query('SELECT name,stock FROM mysql106_pdo_products ORDER BY id');
while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
echo htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8'), ': ', $row['stock'], "\n";
}DSN — строка настроек драйвера. mysql: выбирает PDO_MYSQL; host — адрес, port — TCP-порт, dbname — база, charset=utf8mb4 — кодировка соединения. Имя пользователя и пароль передаются следующими двумя аргументами new PDO, отдельно от DSN. Последний массив задаёт режим ошибок, способ fetch и реальные серверные prepared statements. PDO:: означает константу класса, => связывает ключ настройки и значение. exec выполняет SQL без набора строк; query возвращает PDOStatement для чтения. fetch с FETCH_ASSOC возвращает массив колонка→значение; окончание — false, а не null как у mysqli fetch_assoc.
В этом полном примере FETCH_ASSOC уже задан по умолчанию; поэтому fetch() можно вызвать без аргумента. closeCursor освобождает результат, ссылки на statement и PDO затем обнуляются. Это две самостоятельные программы с отдельными TEMPORARY-таблицами: пример не требует предварительно запускать соседний. Для PDO ошибки связи дают PDOException; в mysqli — mysqli_sql_exception. try/catch и общая идея одинаковы.
$find = $db->prepare('SELECT name FROM mysql106_pdo_products WHERE stock>=? ORDER BY id');
$find->execute([7]);
$available = $find->fetchAll(PDO::FETCH_COLUMN);Знак ? обозначает одно значение; execute([7]) передаёт список параметров. FETCH_COLUMN извлекает только первую колонку каждой строки: получится ["Чай", "Какао"], потому что остатки 12 и 7 проходят порог 7, а 0 — нет. Эти имена сохранены в details отчёта запуска. SQL не собирается склейкой пользовательского значения. Разбор prepare/bind/execute для MySQLi — в следующей главе.
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
$c = mysql106Config();
$dsn = 'mysql:host='.$c['host'].';port='.$c['port'].';dbname='.$c['database'].';charset=utf8mb4';
$db = new PDO($dsn,$c['user'],$c['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES=>false]);
$db->exec('CREATE TEMPORARY TABLE mysql106_pdo_products (id INT PRIMARY KEY, name VARCHAR(60) NOT NULL, stock INT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$db->exec("INSERT INTO mysql106_pdo_products (id,name,stock) VALUES (1,'Чай',12),(2,'Кофе',0),(3,'Какао',7)");
$statement = $db->query('SELECT id,name,stock FROM mysql106_pdo_products ORDER BY id');
$rows = [];
while (($row = $statement->fetch()) !== false) { $rows[] = [(int)$row['id'],$row['name'],(int)$row['stock']]; }
$find = $db->prepare('SELECT name FROM mysql106_pdo_products WHERE stock>=? ORDER BY id');
$find->execute([7]);
$available = $find->fetchAll(PDO::FETCH_COLUMN);
$checks = ['pdo_mysql'=>$db->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql','three_products'=>count($rows)===3,'native_prepares'=>!$db->getAttribute(PDO::ATTR_EMULATE_PREPARES),'prepared_filter'=>$available===['Чай','Какао']];
$details = ['prepared_minimum'=>7,'prepared_names'=>$available];
$runtime = mysql106Runtime($db);
$find->closeCursor();
$find = null;
$statement->closeCursor();
$statement = null;
$db = null;
mysql106Report('mysql106-pdo-table', ['id', 'name', 'stock'], $rows, $checks, $runtime, $details);
echo "PDO_MYSQL: 3 products; stock: 12, 0, 7\n";
Ожидаемый результат
PDO_MYSQL: 3 products; stock: 12, 0, 7
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
PDO не переводит автоматически MySQL SQL на PostgreSQL или SQLite: AUTO_INCREMENT, типы, ограничители и часть функций нужно адаптировать. В обоих API используйте один выбранный стиль последовательно. Старые mysql_connect/mysql_query из ext/mysql не являются третьим современным вариантом: расширение удалено из PHP7. Для подробных контрактов уже есть раздел PDO и справочник MySQLi.
API, connector, driver и расширение — разные уровни
| Термин | Где встречается | Что делает |
|---|---|---|
| API | new mysqli(), query(), fetch_assoc() или PDO | Набор классов, методов, функций и значений, доступных PHP-программе |
| Процедурный / объектный стиль | mysqli_query($db,$sql) / $db->query($sql) | Две записи MySQLi: функция с соединением-аргументом или метод конкретного объекта; поведение нужно выбирать осознанно |
| Connector | Средство связи приложения и MySQL | Общее название комплекта, предоставляющего API и обмен с сервером |
| Driver | PDO_MYSQL внутри PDO | Часть, знающая особенности конкретной СУБД |
| Клиентская библиотека | mysqlnd, исторически libmysqlclient | Реализует низкоуровневый протокол связи |
| Расширение PHP | mysqli, pdo_mysql | Модуль, добавляющий возможности к ядру PHP; наличие сервера не загружает его автоматически |
ООП-стиль не означает, что вы обязаны написать свой класс для первого запроса: new mysqli уже создаёт объект готового расширения. Процедурный вариант тоже рабочий, не «плохой код»: при одинаковых настройках он решает ту же задачу. Не смешивайте понятие MySQL-драйвера с драйвером Windows-устройства; здесь речь о библиотеке обмена данными.
Выбрать MySQLi или PDO_MYSQL без потери возможностей
Для нового PHP-проекта подходят MySQLi и PDO_MYSQL. Первый подробно раскрывает возможности MySQL, второй даёт общий PHP-интерфейс для нескольких СУБД. По одному названию API нельзя обещать ускорение: на практике сравнивают запросы, объём ответа, индексы и сеть. Два самостоятельных примера выше дают одинаковую таблицу разными API.
| Возможность | MySQLi | PDO_MYSQL |
|---|---|---|
| Появление / актуальность | PHP5.0; присутствует в PHP7/8, развивается | PHP5.1; присутствует в PHP7/8, развивается |
| Стиль вызова | Методы объектов и процедурные функции | Объектный PDO/PDOStatement |
| Неблокирующие запросы через mysqlnd | Есть API async/poll | Аналогичного async API нет |
| Постоянные соединения / кодировки | Поддерживаются | Поддерживаются |
| Prepared statements на сервере | Поддерживаются | Поддерживаются; явно отключаем эмуляцию в примере |
| Клиентская эмуляция prepared | Не предоставляет такую альтернативу | Поддерживается, у PDO_MYSQL по умолчанию включена |
| Хранимые процедуры | Поддерживаются, результаты нужно дочитать | Поддерживаются, учитывайте возможности драйвера и наборы результата |
| Несколько SQL-команд / специфика MySQL | Специализированный multi_query и полный MySQL-ориентированный API | Много возможностей, но не полное соответствие специализированному API |
| Транзакции методами и SQL-командами | Есть оба способа | Есть оба способа |
Функции сервера могут зависеть от движка таблицы: наличие метода begin ещё не делает MyISAM транзакционным. Для нескольких команд из внешнего текста не выбирайте multi_query по умолчанию; используйте отдельные подготовленные операции. Полная матрица старого обзора PHP сформулирована вокруг MySQL5.1+; здесь её возможности сохранены, но это не заявление, будто PDO и MySQLi одинаково охватывают любую новую серверную возможность. Выбор зависит от нужных функций, стиля проекта и требований к переносимости SQL.
mysqlnd и libmysqlclient: современная конфигурация и история
API и клиентская библиотека — разные уровни. У современных MySQLi PHP8.2+ библиотека mysqlnd является единственным вариантом; выбирать libmysqlclient для такого mysqli уже нельзя. Старый обзор с двумя configure-командами относится к более ранним PHP. PDO_MYSQL имеет собственные параметры сборки, mysqlnd является вариантом по умолчанию. В нашем окружении оба расширения используют mysqlnd; версии сохраняет первый пример.
| Пункт исторического сравнения библиотек | mysqlnd | libmysqlclient |
|---|---|---|
| Поставка и появление | В составе PHP с5.3; по умолчанию в более новых сборках | Отдельная библиотека MySQL; не часть PHP |
| Лицензия / жизненный цикл в обзоре | PHP License3.01; активный компонент PHP | Двойное лицензирование; активный компонент MySQL, условия выбранной версии проверяют отдельно |
| Сжатие / TLS / named pipes | Поддерживаются при нужной конфигурации | Поддерживаются при нужной конфигурации |
| Async / статистика PHP-клиента | Есть возможности mysqlnd | Нет соответствующих возможностей mysqlnd |
| PHP memory_limit и распределение памяти | Участвует в управлении памятью PHP | Не использует его таким же образом |
| LOAD LOCAL INFILE и open_basedir | Учитывает ограничение PHP | Исторический обзор отмечает отличие; не включайте загрузку файлов без отдельного договора |
| Числа обычного COM_QUERY | Может отдавать числа нативно при настройке, также строки | В исторической матрице — строки; проверяйте конкретный драйвер |
| Расширение плагинами | Есть отдельный plugin API | Ограниченные средства; не равно mysqlnd plugins |
| Автопереподключение | Не делает его автоматически | Исторически настраивалось; не основание считать оборванную транзакцию продолженной |
Конфигурация mysqli: PHP5.0–5.2 — libmysqlclient; 5.3 — mysqlnd появился, но не был значением по умолчанию; 5.4–8.1 — mysqlnd по умолчанию при наличии исторической альтернативы; 8.2+ — только mysqlnd. Это справка для чтения старого проекта, не совет устанавливать устаревший PHP. Сборка PHP использует --with-mysqli и --with-pdo-mysql; имена пакетов и DLL зависят от платформы и версии. Проверяйте фактически загруженную сборку через php --ri и client_info, а не только номер сервера.
Упоминания lazy connection и query cache в старых обзорах не означают, что обычный mysqli автоматически кеширует SELECT или откладывает любое соединение: такие возможности связаны с отдельными плагинами и конфигурацией. Подробные статистика, память и текущие ограничения — в главе mysqlnd. Ни один драйвер не разрешает продолжить потерянную транзакцию после переподключения как ни в чём не бывало.
Отделить неверную базу от работающего соединения
Иногда «база не работает» означает только неверное имя базы в конфигурации. Полный пример пробует заведомо другое имя без создания чего-либо, перехватывает отказ и тут же успешно выполняет SELECT 1 в своей учебной базе.
- Правильный адрес/пользователь + другое имя базы
- Подключение → отказ 1044 или 1049
- catch → код и SQLSTATE, без пароля
- Правильная база → отдельное соединение → SELECT 1
Ожидаемые значения, подтверждаемые реальным запуском
| Проверка | Результат | Подтверждено |
|---|---|---|
| Другая база | отказ перехвачен | true |
| Учебная база | SELECT 1 выполнен | true |
try {
$db = new mysqli($host, $user, $password, $database, $port);
} catch (mysqli_sql_exception $error) {
$code = $error->getCode();
// Показываем понятный отказ, точную причину разбираем в закрытом журнале.
}try содержит операцию, которая может завершиться исключением. catch (mysqli_sql_exception $error) получает именно ошибку MySQLi; getCode выдаёт числовой код, getSqlState — классификацию SQLSTATE. В строгом режиме исключение прерывает try и не позволяет обращаться к несуществующему результату. Имя отсутствующей базы может дать 1049, а у ограниченного пользователя раньше сработает проверка доступа — 1044. Оба означают контролируемый отказ этого опыта, поэтому мы не делаем вид, что сервер обязан раскрыть список баз любому пользователю.
Сообщение «Connection refused» указывает прежде всего на адрес/порт/незапущенный сервер; таймаут — на доступность маршрута/фильтры; 1045 — на проверку учётной записи; 1044 — на доступ к базе; 1049 — на неизвестную базу. Причину выбирают по фактическому коду и закрытому журналу, а не по догадке. Настройка MYSQLI_OPT_CONNECT_TIMEOUT задаётся до real_connect, чтобы ошибочный адрес не оставлял бесконечного ожидания. Проверяйте тот же PHP CLI/FPM и ту же конфигурацию, которыми запускается сайт.
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$c = mysql106Config();
$caught = false; $errorCode = 0; $errorState = '';
try {
$wrong = new mysqli();
$wrong->options(MYSQLI_OPT_CONNECT_TIMEOUT,5);
$wrong->real_connect($c['host'],$c['user'],$c['password'],'phpmanual106_absent_database',$c['port']);
$wrong->close();
} catch (mysqli_sql_exception $error) {
$caught = true;
$errorCode = $error->getCode();
$errorState = $error->getSqlState();
}
$db = mysql106Connect();
$probe = $db->query('SELECT 1 AS ready')->fetch_assoc();
$ready = (int)$probe['ready']===1;
$rows = [['Другая база','отказ перехвачен',$caught],['Учебная база','SELECT 1 выполнен',$ready]];
$checks = ['wrong_database_rejected'=>$caught && in_array($errorCode,[1044,1049],true),'valid_database_still_works'=>$ready];
$details = ['controlled_error_code'=>$errorCode,'controlled_sqlstate'=>$errorState];
$runtime = mysql106Runtime($db);
$db->close();
mysql106Report('mysql106-connection-error', ['Проверка', 'Результат', 'Подтверждено'], $rows, $checks, $runtime, $details);
echo "connection error caught; correct database remains usable\n";
Ожидаемый результат
connection error caught; correct database remains usable
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
Не исправляйте 1044 выдачей всех прав всем пользователям. Сверьте базу, сочетание пользователя и хоста, необходимые права. Не повторяйте постоянную ошибку пароля в цикле. Веб-ответ должен сообщить понятный отказ, а технический журнал — точную причину без секретов. Пример не отключает аутентификацию, TLS или сетевые ограничения и не подключается к рабочему сайту.
Найти неверное имя поля и продолжить правильным запросом
Соединение успешно, но вместо stock написали stcok. Покажем разницу между транспортным отказом и ошибкой SQL: получим 1054, исправим имя и прочитаем исходный остаток 12.
- CREATE: поле stock
- SELECT stcok → сервер не находит колонку
- mysqli_sql_exception: 1054 / 42S22
- SELECT stock → 12
- Исходная строка осталась
Ожидаемые значения, подтверждаемые реальным запуском
| Имя в SELECT | Что произошло | Код / значение |
|---|---|---|
| stcok | ошибка имени поля | 1054 |
| stock | прочитан остаток | 12 |
try {
$result = $db->query('SELECT stcok FROM mysql106_query_products');
} catch (mysqli_sql_exception $error) {
$code = $error->getCode();
}
$result = $db->query('SELECT stock FROM mysql106_query_products');В SQL идентификатор должен совпадать с реальной схемой. Соединение не создаёт автоматически пропущенные колонки. query со строгими MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT выбрасывает исключение при ошибке; поэтому нельзя бездумно вызывать fetch_assoc у false. Начиная с PHP8.1 строгая настройка MySQLi включена по умолчанию, но мы задаём её явно для читаемого поведения. В старом или отключённом режиме ошибки могут давать предупреждение и false — это другая ветка обработки.
1054 — конкретный неизвестный столбец; SQLSTATE 42S22 — его класс в данном случае. Разберитесь, что написано в CREATE/миграции и SELECT, затем исправьте запрос. Этим отказом сервер не потерял соединение и не изменил товар. Значение stock в следующем успешном ответе — подтверждение, а не просто текст «успех».
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
$db = mysql106Connect();
$db->query('CREATE TEMPORARY TABLE mysql106_query_products (id INT PRIMARY KEY, stock INT NOT NULL) ENGINE=InnoDB');
$db->query('INSERT INTO mysql106_query_products (id,stock) VALUES (1,12)');
$code = 0; $state = '';
try { $db->query('SELECT stcok FROM mysql106_query_products'); }
catch (mysqli_sql_exception $error) { $code = $error->getCode(); $state = $error->getSqlState(); }
$actual = $db->query('SELECT stock FROM mysql106_query_products WHERE id=1')->fetch_assoc();
$rows = [['stcok','ошибка имени поля',$code],['stock','прочитан остаток',(int)$actual['stock']]];
$checks = ['unknown_column'=>$code===1054 && $state==='42S22','original_data_intact'=>(int)$actual['stock']===12];
$details = ['sqlstate'=>$state];
$runtime = mysql106Runtime($db);
$db->close();
mysql106Report('mysql106-query-error', ['Имя в SELECT', 'Что произошло', 'Код / значение'], $rows, $checks, $runtime, $details);
echo "query error: 1054; corrected query returned stock=12\n";
Ожидаемый результат
query error: 1054; corrected query returned stock=12
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
Ловить каждое исключение и продолжать все записи опасно: в реальной операции часть изменений могла уже произойти. Здесь ошибочный SELECT ничего не записывает. Для связанных изменений ниже нужна транзакция. Не публикуйте stack trace и внутреннюю схему в HTML-ошибке посетителя; храните диагностические сведения в закрытом журнале.
Добавить, изменить и удалить собственные учебные строки
К каталогу добавили два товара, получили id нового Чая, затем увеличили его остаток и удалили только Кофе с id=2. Покажем строки до и после; человек увидит конкретное изменение данных, а не только логическое true.
- INSERT → Чай id=1 / Кофе id=2
- SELECT до: stock 12 / 0
- UPDATE WHERE id=1 → stock 15
- DELETE WHERE id=2 → одна строка убрана
- SELECT после: Чай id=1, stock 15
Ожидаемые значения, подтверждаемые реальным запуском
| Этап | id | name | stock |
|---|---|---|---|
| До | 1 | Чай | 12 |
| До | 2 | Кофе | 0 |
| После | 1 | Чай | 15 |
$db->query("INSERT INTO mysql106_crud (name,stock) VALUES ('Чай',12)");
$id = $db->insert_id;
$db->query('UPDATE mysql106_crud SET stock=15 WHERE id=1');
$db->query('DELETE FROM mysql106_crud WHERE id=2');AUTO_INCREMENT создаёт id, когда INSERT его не задаёт. insert_id читается после вставки через то же соединение: это значение сгенерированного ключа, не число строк. UPDATE указывает таблицу, SET — новое значение, WHERE — какие строки менять. DELETE FROM удаляет строки, соответствующие WHERE; таблица и её описание остаются. affected_rows нужно сохранить сразу после своей операции, до следующего запроса. Для первого INSERT/UPDATE/DELETE это число действий над строками, а не сами строки.
Числа и названия в этом первом опыте зашиты автором в SQL. Когда значения приходят из формы или интеграции, вместо склейки используйте prepare/bind/execute из следующей главы. Сначала проверьте условие SELECT с тем же WHERE. Здесь вся работа внутри изолированной временной таблицы; другой каталог не затрагивается. Первые id равны 1 и 2 потому, что таблица новая; в живой базе не ждите последовательности без пропусков.
Открыть таблицу фактического запуска · Скачать прочитанные значения JSON. Эти файлы создаёт сама программа; JSON не подменяет источник данных и не является базой MySQL.
<?php
declare(strict_types=1);
require __DIR__ . '/mysql106-helper.php';
$details = [];
$db = mysql106Connect();
$db->query('CREATE TEMPORARY TABLE mysql106_crud (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(60) NOT NULL,stock INT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$db->query("INSERT INTO mysql106_crud (name,stock) VALUES ('Чай',12)");
$firstId = (int)$db->insert_id;
$db->query("INSERT INTO mysql106_crud (name,stock) VALUES ('Кофе',0)");
$rows = [];
foreach ($db->query('SELECT id,name,stock FROM mysql106_crud ORDER BY id') as $row) { $rows[]=['До',(int)$row['id'],$row['name'],(int)$row['stock']]; }
$db->query('UPDATE mysql106_crud SET stock=15 WHERE id=1');
$updated = $db->affected_rows;
$db->query('DELETE FROM mysql106_crud WHERE id=2');
$deleted = $db->affected_rows;
foreach ($db->query('SELECT id,name,stock FROM mysql106_crud ORDER BY id') as $row) { $rows[]=['После',(int)$row['id'],$row['name'],(int)$row['stock']]; }
$checks = ['first_generated_id'=>$firstId===1,'one_updated'=>$updated===1,'one_deleted'=>$deleted===1,'final_stock'=>$rows[2][3]===15];
$details = ['insert_id'=>$firstId,'updated'=>$updated,'deleted'=>$deleted];
$runtime = mysql106Runtime($db);
$db->close();
mysql106Report('mysql106-crud-result', ['Этап', 'id', 'name', 'stock'], $rows, $checks, $runtime, $details);
echo "CRUD: inserted id=1; updated=1; deleted=1; remaining stock=15\n";
Ожидаемый результат
CRUD: inserted id=1; updated=1; deleted=1; remaining stock=15
Полный самостоятельный комплект: example.php, mysql106-helper.php и README.txt. Настройте отдельный локальный MySQL и переменные окружения из инструкции выше, затем выполните php example.php. Код сам создаёт свои TEMPORARY-таблицы и исходные строки; другие примеры предварительно запускать не нужно. Helper читает окружение, устанавливает соединение и сохраняет HTML/JSON; вся SQL-работа видна в example.php. Для повторения достаточно новой сессии; локальные файлы отчёта будут перезаписаны.
Без WHERE UPDATE затронет все строки, DELETE удалит все строки. Отдельный INSERT ещё не означает сохранение полного заказа из нескольких сущностей. Имена таблиц и столбцов берутся из кода, не из произвольного пользовательского ввода. Далее — подробные SQL-условия, выборки и изменение строк и контракт входных данных.
Упражнение и продолжение каталога
Упражнение: добавьте в собственный учебный каталог четвёртый товар Мёд с остатком 4 и выведите только name/stock. Решение: в INSERT добавьте (4,'Мёд',4), в SELECT оставьте name,stock и ORDER BY id; при сборке строки читайте соответствующие именованные ключи. Ожидается четвёртая строка Мёд / 4. Затем перейдите к проектированию схемы и связей и условиям, объединениям и группировкам SQL.
Методы соединения/запроса/statement не создаются заново этой главой: их справочные контракты доступны в полном индексе MySQLi и PDO. Здесь добавлен реальный самостоятельный путь от сервера до HTML. Выполнение новых программ не означает повторного запуска всех старых контекстных примеров справочника.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


