Почему mysql_* функции в PHP — это зло: полный разбор с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: от истории к проблеме

Если вы когда-либо писали на PHP и работали с базами данных MySQL, то наверняка встречали код вроде mysql_connect() или mysql_query(). Ещё лет 10–15 назад это был стандарт де-факто. Но времена меняются, и сегодня использовать mysql_* функции — это всё равно что пытаться запустить Windows 95 на новом MacBook. Можно, но зачем?

На Stack Overflow вопрос «Why shouldn't I use mysql_* functions in PHP?» собрал тысячи просмотров и сотни голосов. Это не случайно: тема безопасности и современного подхода к работе с БД критически важна для любого разработчика. В этой статье мы разберём, почему mysql_* — это не просто «старо», а откровенно опасно, и покажем, чем их заменить.

Проблема №1: mysql_* полностью удалены из PHP 7

Самая очевидная причина: начиная с PHP 5.5.0 функции mysql_* были помечены как deprecated (устаревшие), а в PHP 7.0 их полностью удалили. Если ваш сервер обновится до PHP 7+, весь код, использующий mysql_connect(), mysql_query() и подобные, просто перестанет работать. Вы получите фатальную ошибку:

Fatal error: Call to undefined function mysql_connect()

Это не «может быть, не заработает», это гарантированный крах сайта. Поэтому первая и главная причина: совместимость. Любой хостинг рано или поздно обновит PHP, и ваш проект окажется в мёртвой зоне.

Проблема №2: отсутствие защиты от SQL-инъекций

Давайте посмотрим на типичный код с mysql_*:

<?php
$connection = mysql_connect('localhost', 'user', 'password');
mysql_select_db('my_database', $connection);

$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysql_query($query, $connection);

if (mysql_num_rows($result) > 0) {
    echo "Добро пожаловать!";
} else {
    echo "Неверный логин или пароль";
}
?>

Выглядит безобидно? А теперь представьте, что злоумышленник вводит в поле username такую строку:

admin' OR '1'='1

Запрос превращается в:

SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '...'

И вуаля — '1'='1' всегда истинно, поэтому запрос вернёт первую запись из таблицы. Злоумышленник заходит в систему без пароля. Это классическая SQL-инъекция. Функции mysql_* не предоставляют никаких встроенных средств для экранирования данных — вы должны вручную использовать mysql_real_escape_string(), который, кстати, тоже устарел.

Сравните с современными подходами, где защита встроена в архитектуру (об этом чуть ниже).

Проблема №3: устаревшая архитектура и отсутствие поддержки

mysql_* — это процедурный интерфейс, который не поддерживает:

  • Подготовленные запросы (prepared statements) — главный инструмент защиты от инъекций.
  • Объектно-ориентированный стиль — в современном PHP принято работать с объектами.
  • Множественные запросы — выполнить несколько SQL-команд в одном вызове нельзя.
  • Транзакции — хотя их можно эмулировать, встроенной поддержки нет.
  • Хранимые процедуры — работа с ними крайне неудобна.

Кроме того, расширение mysql_* не обновлялось с 2014 года. Разработчики PHP просто перестали его поддерживать. Это означает, что любые новые функции БД, улучшения безопасности или оптимизации — всё это обходит вас стороной.

Что использовать вместо mysql_*?

У PHP есть два современных, поддерживаемых и безопасных расширения для работы с MySQL:

  • MySQLi (MySQL Improved) — процедурный и объектно-ориентированный интерфейс, наследник mysql_*.
  • PDO (PHP Data Objects) — универсальный интерфейс для работы с разными БД (MySQL, PostgreSQL, SQLite и др.).

Оба поддерживают подготовленные запросы, транзакции и объектный стиль. Какой выбрать? PDO считается более гибким и современным, особенно если вы планируете менять БД в будущем. MySQLi хорош, если вы работаете только с MySQL и хотите сохранить процедурный стиль.

Пример: исправляем код с mysql_* на PDO

Давайте перепишем наш опасный пример с использованием PDO и подготовленных запросов:

<?php
// Подключение к БД через PDO
$dsn = 'mysql:host=localhost;dbname=my_database;charset=utf8';
$username = 'user';
$password = 'password';

try {
    $pdo = new PDO($dsn, $username, $password);
    // Устанавливаем режим ошибок
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die('Ошибка подключения: ' . $e->getMessage());
}

// Получаем данные из POST
$user = $_POST['username'];
$pass = $_POST['password'];

// Подготовленный запрос — данные отделены от SQL!
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([':username' => $user, ':password' => $pass]);

if ($stmt->rowCount() > 0) {
    echo "Добро пожаловать!";
} else {
    echo "Неверный логин или пароль";
}
?>

Обратите внимание: мы используем плейсхолдеры (:username, :password), а значения передаём отдельно в execute(). PDO сам экранирует данные, и SQL-инъекция становится невозможной. Даже если злоумышленник введёт admin' OR '1'='1, это будет воспринято как обычная строка, а не как часть SQL-запроса.

Пример: исправляем код на MySQLi (объектный стиль)

Если вы предпочитаете MySQLi, вот аналогичный пример:

<?php
// Подключение через MySQLi
$mysqli = new mysqli('localhost', 'user', 'password', 'my_database');

if ($mysqli->connect_error) {
    die('Ошибка подключения: ' . $mysqli->connect_error);
}

$user = $_POST['username'];
$pass = $_POST['password'];

// Подготовленный запрос
$stmt = $mysqli->prepare("SELECT *                

Блоги

Рекомендаций по книгам