История одной ошибки: почему mysql_* — это прошлый век
Представьте: вы пишете сайт на PHP, подключаетесь к базе данных MySQL и используете знакомую с университета функцию mysql_query(). Всё работает, сайт живет, пользователи довольны. Но однажды вы замечаете странное предупреждение в логах: «Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future». Это не просто ругань интерпретатора — это красный флаг, который игнорировать нельзя.
На Stack Overflow вопрос «Why shouldn't I use mysql_* functions in PHP?» собрал тысячи просмотров и сотни комментариев. И это не случайно. В этой статье мы разберемся, почему mysql_* функции (mysql_connect, mysql_query, mysql_fetch_array и другие) стали врагом номер один для PHP-разработчиков, покажем на реальных примерах кода, чем их заменить, и дадим пошаговый план миграции.
Что такое mysql_* и почему их убрали?
Расширение mysql_* (оригинальное MySQL) появилось в PHP 2.0 в далеком 1997 году. Оно было простым и интуитивным: вызвал mysql_connect(), выполнил запрос через mysql_query(), получил результат. Но у этой простоты была темная сторона.
Начиная с PHP 5.5.0, все функции mysql_* были помечены как устаревшие (deprecated), а в PHP 7.0 их полностью удалили. Если вы попробуете использовать mysql_connect() на PHP 7+, получите фатальную ошибку: Call to undefined function mysql_connect(). Причина — не каприз разработчиков PHP, а фундаментальные проблемы безопасности и архитектуры.
Три главные причины отказаться от mysql_*
- SQL-инъекции без защиты — функции не поддерживают подготовленные запросы (prepared statements), что делает ручное экранирование обязательным, но часто забытым шагом.
- Устаревшая архитектура — нет поддержки объектно-ориентированного стиля, хранимых процедур, множества запросов и транзакций.
- Отсутствие развития — расширение не обновлялось годами, в то время как MySQL менялся (например, появились новые типы данных, кодировки, механизмы аутентификации).
Живой пример: как mysql_* убивает ваш сайт
Давайте посмотрим на классический код, который до сих пор встречается в старых учебниках и на форумах:
<?php
// УСТАРЕВШИЙ КОД - НЕ ИСПОЛЬЗОВАТЬ!
$link = mysql_connect('localhost', 'root', 'password');
mysql_select_db('users_db', $link);
$user_input = $_GET['username']; // пользователь вводит: ' OR 1=1 --
$query = "SELECT * FROM users WHERE username = '$user_input'";
$result = mysql_query($query, $link);
while ($row = mysql_fetch_assoc($result)) {
echo "Привет, " . $row['username'] . "<br>";
}
?>
В чем проблема? Если злоумышленник передаст в параметр username значение ' OR 1=1 --, запрос превратится в:
SELECT * FROM users WHERE username = '' OR 1=1 --
Двойной дефис -- закомментирует остаток запроса, а условие 1=1 всегда истинно. В результате вы получите всех пользователей базы данных, включая пароли, email и другую конфиденциальную информацию. Это не теория — это реальная уязвимость, которая ломает тысячи сайтов каждый день.
Конечно, можно использовать mysql_real_escape_string(), но:
- Разработчики часто забывают его вызывать.
- Он не спасает от всех типов атак (например, от атак с использованием кодировок).
- Код становится грязным и трудночитаемым.
Чем заменить mysql_*: два современных подхода
В современном PHP есть два достойных расширения для работы с MySQL: mysqli (MySQL Improved) и PDO (PHP Data Objects). Оба поддерживают подготовленные запросы, объектно-ориентированный синтаксис и безопасную передачу данных.
1. mysqli — наследник mysql_*
Расширение mysqli — это эволюционное развитие mysql_*. Оно поддерживает как процедурный, так и объектно-ориентированный стиль. Вот как выглядит безопасный код с подготовленными запросами:
<?php
// Современный подход с mysqli (объектный стиль)
$mysqli = new mysqli('localhost', 'root', 'password', 'users_db');
// Проверка соединения
if ($mysqli->connect_error) {
die("Ошибка подключения: " . $mysqli->connect_error);
}
$user_input = $_GET['username']; // даже если это ' OR 1=1 --
// Подготовленный запрос
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $user_input); // "s" - строка
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo "Привет, " . htmlspecialchars($row['username']) . "<br>";
}
$stmt->close();
$mysqli->close();
?>
Обратите внимание на знак вопроса ? — это плейсхолдер. Метод bind_param гарантирует, что данные пользователя будут экранированы правильно, независимо от их содержимого. SQL-инъекция здесь невозможна.
2. PDO — универсальный драйвер для любой БД
PDO (PHP Data Objects) — мой личный фаворит. Он предоставляет единый интерфейс для работы с разными базами данных (MySQL, PostgreSQL, SQLite и другими). Если вы решите перейти с MySQL на PostgreSQL, вам нужно будет изменить только строку подключения — остальной код останется тем же.
<?php
// Современный подход с PDO (рекомендуется)
try {
$pdo = new PDO('mysql:host=localhost;dbname=users_db;charset=utf8', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$user_input = $_GET['username'];
// Подготовленный запрос
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $user_input]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "Привет, " . htmlspecialchars($row['username']) . "<br>";
}
} catch (PDOException $e) {
// Логируем ошибку, но не выводим пользователю
error_log("Ошибка БД: " . $e->getMessage());
echo "Произошла техническая ошибка. Попробуйте позже.";
}
?>
Здесь мы используем именованный плейсхолдер :username, что делает код более читаемым. Метод execute автоматически под