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

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

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

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

Глава 42 · Первое знакомство с MySQL

MySQL с нуля: подключение, первая таблица и данные в HTML

От различия сервера, базы и соединения до MySQLi/PDO, создания каталога, чтения строк и понятных ошибок.

Как PHP, MySQL, база и таблица связаны друг с другом

Нужна витрина, в которой товар имеет название и остаток, а изменение сохраняется для следующих запросов сайта. MySQL хранит такие данные на сервере; PHP отправляет команды и превращает ответ в страницу. Здесь начнём с первого соединения и трёх строк товаров. Временные таблицы используются только для воспроизводимого упражнения; в настоящем сайте каталог хранится в постоянных таблицах.

Путь одного каталога
  1. Браузер просит страницу каталога
  2. PHP вызывает mysqli или PDO_MYSQL
  3. Клиентская библиотека передаёт SQL серверу MySQL
  4. Сервер работает с базой → таблицей → строками
  5. Ответ → 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_mysql

MySQL устанавливается отдельно из официального дистрибутива или пакета вашей ОС; инструкция: установка MySQL8.4. На Windows расширения mysqli/pdo_mysql включают соответствующими DLL в PHP; в Linux — пакетами именно используемой версии PHP. Workbench и phpMyAdmin — клиенты управления, наличие их окна ещё не означает, что сервер запущен. Администратор готовит отдельную учебную схему и ограниченную учётную запись; программы не создают пользователей и не меняют серверную конфигурацию.

Один раз подготовить свою учебную базу

Откройте SQL-клиент своего локального сервера под учётной записью администратора. На свежем учебном сервере выполните команды ниже, заменив шаблон пароля своим. Это подготовка окружения, не код PHP-страницы. Названия phpmanual_training и phpmanual_student намеренно учебные. Если такое имя уже существует, проверьте его назначение или выберите другое; не удаляйте неизвестную базу ради запуска примера.

SQL-команды администратора локального учебного сервера; пароль в шаблоне необходимо заменить
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 — отдельная задача; этот комплект локальный.

Первая строка — команда терминала; после запроса пароля остальные строки вводятся в SQL-клиенте
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_HOST127.0.0.1Адрес локального сервера; явный IP выбирает TCP, в отличие от особых правил localhost/socket
PHPMAN_MYSQL_PORT33070 для этого стенда; свой порт в другом окруженииНомер точки подключения; стандартный MySQL-порт часто 3306
PHPMAN_MYSQL_DATABASEИмя выделенной учебной базыОна должна существовать до подключения
PHPMAN_MYSQL_USERИмя учебной учётной записиПользователь MySQL, не логин Windows/WordPress
PHPMAN_MYSQL_PASSWORDЕё собственный пароль локальноНе включать в исходники, публичный HTML, архив или чат
Откуда берутся настройки для короткого вызова new mysqli ниже
$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 допускает только локальный адрес, чтобы учебное упражнение не ушло в рабочую базу.

PowerShell: все пять настроек и запуск; подставьте собственный локальный пароль
$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. Это маленькая проверка адреса, учётной записи и доступности базы до разработки каталога.

От действия к результату
  1. PHP: new mysqli(...) → соединение
  2. query: SQL «SELECT 2 + 3 AS total» → MySQL
  3. MySQL: строка total=5 → mysqli_result
  4. fetch_assoc: ["total" => "5"] → PHP
  5. Вывод / result.html: 5

Ожидаемые значения, подтверждаемые реальным запуском

APIКлиентская библиотекаСоединение2 + 3
mysqlimysqlndready5
Короткая запись основного действия; настройки и подготовка данных находятся в полном примере
$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; ноль не исчезнет при обходе.

От действия к результату
  1. CREATE: колонки id / name / stock
  2. INSERT: три строки товаров
  3. SELECT: id,name,stock ORDER BY id
  4. fetch_assoc по одной строке → while
  5. Значения ячеек → htmlspecialchars → HTML-таблица

Ожидаемые значения, подтверждаемые реальным запуском

idnamestock
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-параметры: это разные границы.

Как вывести уже прочитанный PHP-массив $rows в HTML; имя — индекс 1, целый остаток — индекс 2
<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.

Создать таблицу товаров, прочитать строки и вывести HTMLСамостоятельный пример
<?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 остаются понятными; меняются создание соединения и извлечение строки.

От действия к результату
  1. PDO + DSN mysql:host...;dbname...;charset...
  2. PDO_MYSQL → mysqlnd → сервер
  3. query → PDOStatement
  4. fetch(PDO::FETCH_ASSOC) → массив / false
  5. Те же Чай=12, Кофе=0, Какао=7

Ожидаемые значения, подтверждаемые реальным запуском

idnamestock
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 и общая идея одинаковы.

Дополнительный запрос той же полной PDO-программы: параметр минимального остатка
$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.

Прочитать такие же данные через PDO_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 и расширение — разные уровни

ТерминГде встречаетсяЧто делает
APInew mysqli(), query(), fetch_assoc() или PDOНабор классов, методов, функций и значений, доступных PHP-программе
Процедурный / объектный стильmysqli_query($db,$sql) / $db->query($sql)Две записи MySQLi: функция с соединением-аргументом или метод конкретного объекта; поведение нужно выбирать осознанно
ConnectorСредство связи приложения и MySQLОбщее название комплекта, предоставляющего API и обмен с сервером
DriverPDO_MYSQL внутри PDOЧасть, знающая особенности конкретной СУБД
Клиентская библиотекаmysqlnd, исторически libmysqlclientРеализует низкоуровневый протокол связи
Расширение PHPmysqli, pdo_mysqlМодуль, добавляющий возможности к ядру PHP; наличие сервера не загружает его автоматически

ООП-стиль не означает, что вы обязаны написать свой класс для первого запроса: new mysqli уже создаёт объект готового расширения. Процедурный вариант тоже рабочий, не «плохой код»: при одинаковых настройках он решает ту же задачу. Не смешивайте понятие MySQL-драйвера с драйвером Windows-устройства; здесь речь о библиотеке обмена данными.

Выбрать MySQLi или PDO_MYSQL без потери возможностей

Для нового PHP-проекта подходят MySQLi и PDO_MYSQL. Первый подробно раскрывает возможности MySQL, второй даёт общий PHP-интерфейс для нескольких СУБД. По одному названию API нельзя обещать ускорение: на практике сравнивают запросы, объём ответа, индексы и сеть. Два самостоятельных примера выше дают одинаковую таблицу разными API.

ВозможностьMySQLiPDO_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; версии сохраняет первый пример.

Пункт исторического сравнения библиотекmysqlndlibmysqlclient
Поставка и появлениеВ составе 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 в своей учебной базе.

От действия к результату
  1. Правильный адрес/пользователь + другое имя базы
  2. Подключение → отказ 1044 или 1049
  3. catch → код и SQLSTATE, без пароля
  4. Правильная база → отдельное соединение → 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.

От действия к результату
  1. CREATE: поле stock
  2. SELECT stcok → сервер не находит колонку
  3. mysqli_sql_exception: 1054 / 42S22
  4. SELECT stock → 12
  5. Исходная строка осталась

Ожидаемые значения, подтверждаемые реальным запуском

Имя в 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.

От действия к результату
  1. INSERT → Чай id=1 / Кофе id=2
  2. SELECT до: stock 12 / 0
  3. UPDATE WHERE id=1 → stock 15
  4. DELETE WHERE id=2 → одна строка убрана
  5. SELECT после: Чай id=1, stock 15

Ожидаемые значения, подтверждаемые реальным запуском

Этапidnamestock
До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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.