PDO против mysqli: что выбрать под MySQL в PHP
Два способа работать с базой в PHP делают одно и то же по-разному. Разбираем, где mysqli выигрывает пару процентов, а PDO экономит недели при смене СУБД и защищает от инъекций.

Оба расширения умеют одно: подключиться к базе, отправить запрос, вернуть строки. Разница видна не на первом запросе, а на втором проекте — когда вы переносите код на PostgreSQL, ловите SQL-инъекцию в динамическом фильтре или пытаетесь привязать массив параметров к IN (...). Здесь mysqli и PDO ведут себя настолько по-разному, что выбор перестаёт быть делом вкуса. Статья — про выбор между ними для PHP 8.x под MySQL/MariaDB, без разбора внутренностей драйверов.
Что это вообще за два расширения
mysqli — специализированный интерфейс только к MySQL и MariaDB. Работает в двух стилях: процедурном (mysqli_query($link, ...)) и объектном ($mysqli->query(...)). Второе имя буквы «i» в названии — improved, улучшенная версия старого расширения mysql_*, которое выпилили ещё в PHP 7.
PDO (PHP Data Objects) — абстракция над разными СУБД. Один и тот же код через смену DSN1 работает с MySQL, PostgreSQL, SQLite и другими — при условии, что сам SQL совместим. PDO даёт единый API, но не переписывает ваши запросы: диалектные различия остаются на вас.
Оба поддерживают подготовленные выражения (prepared statements) — главный механизм защиты от SQL-инъекций. Оба работают в PHP 8.x. Разница — в удобстве, переносимости и нескольких принципиальных деталях.
Где различия видны сразу
Именованные параметры
PDO поддерживает и позиционные (?), и именованные (:name) плейсхолдеры. В длинном запросе с десятком параметров именованные читаются проще и не ломаются при перестановке столбцов.
// PDO, PHP 8.x — именованные плейсхолдеры
$pdo = new PDO(
'mysql:host=localhost;dbname=shop;charset=utf8mb4',
'app_user',
'YOUR_PASSWORD',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
$stmt = $pdo->prepare(
'SELECT id, title, price FROM products
WHERE category_id = :category AND price <= :max_price'
);
$stmt->execute(['category' => 5, 'max_price' => 1000]);
$rows = $stmt->fetchAll();
mysqli именованных параметров не поддерживает вовсе — только позиционные ?. И привязка в нём многословнее.
// mysqli, PHP 8.x — те же данные, позиционные плейсхолдеры
$mysqli = new mysqli('localhost', 'app_user', 'YOUR_PASSWORD', 'shop');
$mysqli->set_charset('utf8mb4');
// в PHP 8.1+ mysqli по умолчанию бросает исключения при ошибках
$stmt = $mysqli->prepare(
'SELECT id, title, price FROM products
WHERE category_id = ? AND price <= ?'
);
$stmt->bind_param('id', $categoryId, $maxPrice); // i = int, d = double
$categoryId = 5;
$maxPrice = 1000;
$stmt->execute();
$result = $stmt->get_result();
$rows = $result->fetch_all(MYSQLI_ASSOC);
Обратите внимание на bind_param('id', ...): первый аргумент — строка типов, где каждый символ описывает тип соответствующего параметра. Ошиблись в порядке символов — получили тихое приведение типа, а не понятную ошибку.
Привязка массива для IN
Классическая боль. Ни то ни другое расширение не умеет подставлять массив в один плейсхолдер. Но в PDO обвязка короче, потому что execute() принимает массив значений напрямую.
// PDO: динамический список параметров для IN
$ids = [3, 7, 11, 42];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare(
"SELECT id, title FROM products WHERE id IN ($placeholders)"
);
$stmt->execute($ids); // просто передаём весь массив
$rows = $stmt->fetchAll();
В mysqli тот же сценарий требует распаковки массива в bind_param через spread-оператор и ручной сборки строки типов — кода заметно больше, и он менее очевиден при чтении.
Обработка ошибок
PDO с PDO::ERRMODE_EXCEPTION бросает PDOException при любой ошибке — это поведение задаётся явно и работает одинаково для всех драйверов. mysqli в PHP 8.1 и новее по умолчанию тоже бросает исключения (mysqli_sql_exception) благодаря режиму MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, включённому из коробки. Если ваш код рассчитан на PHP 8.0 и раньше, за режимом отчётов mysqli приходилось следить вручную — это частый источник «молчаливых» багов при переезде на новую версию.
Сравнение по делу
| Критерий | PDO | mysqli |
|---|---|---|
| Поддерживаемые СУБД | Много (MySQL, PostgreSQL, SQLite, ...) | Только MySQL / MariaDB |
| Именованные плейсхолдеры | Да | Нет |
| Стиль API | Только объектный | Объектный и процедурный |
| Привязка параметров | Массивом в execute() | bind_param со строкой типов |
| Доступ к специфике MySQL (async, multi-query) | Ограничен | Полный |
| Исключения по умолчанию | Задаётся явно | С PHP 8.1 — да |
Переносимость PDO — это про API, а не про SQL. Диалект запросов при смене СУБД всё равно придётся вычитывать вручную: LIMIT, автоинкремент и работа с датами различаются.
А что с производительностью
Разница между PDO и mysqli на типичном веб-приложении теряется в шуме: сеть до базы, парсинг SQL и сам запрос стоят на порядки дороже, чем накладные расходы расширения. Синтетические микробенчмарки иногда показывают минимальное преимущество mysqli на голых выборках, но на реальной нагрузке — с бизнес-логикой, шаблонизацией и внешними вызовами — этот выигрыш не виден.
Если вам важны абсолютные цифры именно на вашем железе и данных — не верьте чужим бенчмаркам, замерьте сами. Возьмите свой типовой запрос, прогоните 10 000 раз через каждое расширение на боевом объёме таблицы, замерьте медиану и 95-й перцентиль, а не среднее. С высокой вероятностью разница окажется в пределах погрешности, и решать вы будете по удобству, а не по скорости.
Когда что выбирать
- Берите PDO, если проект новый, если есть хоть малейший шанс сменить или добавить СУБД, если вы используете фреймворк или ORM (Doctrine, Eloquent под капотом работают через PDO), или если вам просто нужен более чистый и единообразный код.
- Берите mysqli, если проект намертво привязан к MySQL и вам нужны его специфичные возможности: асинхронные запросы, множественные результаты (
multi_query), детальная работа с метаданными результата. Или если вы поддерживаете старый код, где mysqli уже везде, и переписывать его нет смысла. - Не берите ни то ни другое напрямую, если пишете новое приложение на фреймворке: работайте через слой абстракции фреймворка, а он сам решит, что вызвать под капотом.
Практический вывод простой: по умолчанию — PDO. Он покрывает 95% задач, читается лучше и не запирает вас в одной СУБД. mysqli — осознанный выбор под конкретную потребность, а не режим по инерции.
Что ломается на практике
- Эмуляция подготовленных выражений в PDO. По умолчанию драйвер MySQL для PDO эмулирует prepared statements на стороне PHP. Это работает, но при некоторых сценариях (например, с числовыми плейсхолдерами) может давать неожиданное приведение типов. Явно ставьте
PDO::ATTR_EMULATE_PREPARES => false, если хотите настоящие серверные подготовленные выражения. - Кодировка мимо DSN. Забыли
charset=utf8mb4в строке подключения PDO илиset_charset()в mysqli — и получаете «кракозябры» либо, хуже, уязвимость при работе со старыми кодировками. Указывайте кодировку явно всегда. - Строка типов в mysqli. Перепутанный порядок символов в
bind_param('is', ...)не вызывает ошибку — база молча приведёт значение. Это тихий баг, который всплывает на проде. - Конкатенация вместо плейсхолдеров. И в PDO, и в mysqli подстановка переменных прямо в текст запроса — открытая дверь для инъекции. Плейсхолдеры не «стиль», а защита; не обходите их ради «одного маленького запроса».
Что делать дальше
Если проект новый — заведите один класс-обёртку над PDO с настроенными атрибутами (исключения, режим выборки, отключённая эмуляция) и подключайтесь только через него, чтобы не дублировать настройки. Если поддерживаете старый код на mysqli — проверьте, включён ли режим отчёта об ошибках через исключения, и что все запросы идут через prepare/bind_param, а не через конкатенацию. И перед выбором сверьтесь с официальной документацией PHP по обоим расширениям: набор методов и поведение по умолчанию уточняются от версии к версии.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: PHP Manual: PDO, PHP Manual: MySQLi
Частые вопросы
А если у меня PHP 7.4, а не 8.x?
Что делать, если код на старом mysql_* (без i)?
PDO защищает от SQL-инъекций автоматически?
Можно ли использовать PDO и mysqli в одном проекте?
Не сработало: PDOException «could not find driver». В чём дело?
Именованные плейсхолдеры в PDO медленнее позиционных?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.