Введение: от истории к проблеме
Если вы когда-либо писали на 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 *