Обработка MySQL ошибок в PHP коде

Раздел: PHP -> Ошибки и отладка PHP

Основные подходы к обработке ошибок MySQL в PHP

Как сделать обработку ошибок MySQL централизованной и предсказуемой?

Наиболее эффективным решением считается использование расширения PDO с режимом исключений. При таком подходе каждая ошибка запроса или соединения превращается в исключение, которое можно перехватить в блоке try-catch. Это избавляет от необходимости проверять результат каждой функции вручную.

Пример настройки PDO с исключениями:


$dsn = 'mysql:host=localhost;dbname=test;charset=utf8';
$user = 'root';
$pass = '';

try {
    $pdo = new PDO($dsn, $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    // Ошибка соединения обрабатывается здесь
    echo 'Не удалось соединиться: ' . $e->getMessage();
}
  

После этой настройки любые неудачные запросы (например, синтаксическая ошибка SQL или нарушение ограничений) будут автоматически вызывать исключение PDOException. Это упрощает отладку, особенно на этапе разработки.

Типичная проблема: если забыть установить PDO::ATTR_ERRMODE, PDO по умолчанию работает в «тихом» режиме - ошибки не выводятся и не выбрасываются. Тогда разработчик может не заметить проблему до тех пор, пока данные не окажутся некорректными.

Решение: всегда явно задавать нужный режим. Для продакшена можно использовать PDO::ERRMODE_WARNING (только предупреждения) или комбинировать с логированием, но на разработке рекомендуется ERRMODE_EXCEPTION.

Как обрабатывать ошибки MySQL с помощью mysqli и исключений?

Расширение mysqli также может выбрасывать исключения, если включить строгий режим отчёта об ошибках. Это делается одной директивой:


mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$conn = new mysqli('localhost', 'root', '', 'test');
  

После этого любая ошибка (включая проблемы соединения) вызывает исключение mysqli_sql_exception. Поведение становится похожим на PDO.

Проблема: если добавить эту строку внутри уже существующего проекта, могут неожиданно «полететь» участки кода, где ошибки раньше игнорировались.

Решение: оборачивать все вызовы mysqli в try-catch, либо постепенно внедрять новый режим на новых модулях.

Как включить вывод ошибок MySQL на этапе разработки?

На локальной машине полезно видеть все ошибки прямо на экране. Для этого в PHP настраиваются директивы display_errors и error_reporting. Пример для php.ini:


display_errors = On
error_reporting = E_ALL
  

В коде это можно сделать функциями:


ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);
  

Тогда любые предупреждения MySQL будут отображаться в браузере.

Опасность: включение display_errors на боевом сервере раскрывает детали соединения с БД.

Решение: оставлять display_errors = Off на продакшене, а для отладки использовать логирование.

Как скрыть ошибки MySQL от пользователя, но записать их в лог?

На боевом сервере ошибки следует логировать, не показывая пользователю. В php.ini настраиваются:


display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
  

В коде можно перехватывать исключения PDO и записывать их в собственный лог с дополнительной информацией:


try {
    $pdo->query('SELECT * FROM non_existent_table');
} catch (PDOException $e) {
    error_log('MySQL query failed: ' . $e->getMessage() . ' [SQL: ' . $sql . ']');
    // Пользователю выводится общее сообщение
    echo 'Извините, произошла внутренняя ошибка.';
}
  

Такой подход защищает данные и помогает администратору анализировать сбои.

Проблема: если лог-файл не создан или недоступен для записи, ошибка не сохранится.

Решение: проверять права на каталог лога, использовать syslog или внешние сервисы (Sentry, Graylog).

Как обработать конкретный код ошибки MySQL (например, дублирование ключа 1062)?

В некоторых сценариях требуется различать типы ошибок. Например, при вставке данных возможен дубликат первичного ключа. В PDO код ошибки можно получить через $e->getCode() или $e->errorInfo[1]:


try {
    $pdo->exec("INSERT INTO users (id, name) VALUES (1, 'test')");
} catch (PDOException $e) {
    if ($e->getCode() == '23000' && $e->errorInfo[1] == 1062) {
        echo 'Такая запись уже существует. Попробуйте другое значение.';
    } else {
        throw $e; // перебросить другие ошибки
    }
}
  

Это позволяет дать пользователю понятное сообщение, не раскрывая структуру БД.

Сложность: коды ошибок MySQL различаются в зависимости от версии и драйвера.

Решение: сверяться с документацией MySQL и тестировать на целевой версии.

Расширенные примеры отладки MySQL ошибок в PHP

Пример 1: PDO с разными режимами ошибок (сравнение)

Рассмотрим три режима: ERRMODE_SILENT (по умолчанию), ERRMODE_WARNING и ERRMODE_EXCEPTION. Код и результат для ошибочного запроса:

Пример

$dsn = 'mysql:host=localhost;dbname=test;charset=utf8';
$pdo = new PDO($dsn, 'root', '');

// 1. SILENT
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$result = $pdo->query('SELECT invalid_column FROM users');
if (!$result) {
    echo 'Ошибка в тихом режиме: ' . $pdo->errorCode() . "\n";
} // Вывод: Ошибка в тихом режиме: 42S22

// 2. WARNING
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$result = $pdo->query('SELECT invalid_column FROM users');
// Вывод предупреждения на экран (если display_errors включен)
// Warning: PDO::query(): SQLSTATE[42S22]: ...

// 3. EXCEPTION
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    $pdo->query('SELECT invalid_column FROM users');
} catch (PDOException $e) {
    echo 'Исключение: ' . $e->getMessage() . "\n";
} // Вывод: Исключение: SQLSTATE[42S22]: Column not found: ...
Ошибка в тихом режиме: 42S22
Исключение: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'invalid_column' in 'field list'

Для продакшена часто выбирают ERRMODE_WARNING с логированием или ERRMODE_EXCEPTION с глобальным обработчиком.

Пример 2: mysqli с подготовленными запросами и детальным отловом ошибок

Использование mysqli_report с исключениями позволяет перехватывать ошибки даже в подготовленных запросах, которые могут вернуть false при неудачной подготовке.

Пример

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$conn = new mysqli('localhost', 'root', '', 'test');

try {
    $stmt = $conn->prepare('SELECT * FROM users WHERE email = ?');
    $stmt->bind_param('s', $email);
    $email = 'test@example.com';
    $stmt->execute();
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        print_r($row);
    }
} catch (mysqli_sql_exception $e) {
    echo 'Ошибка MySQL: ' . $e->getMessage() . ' (код ' . $e->getCode() . ')';
}
// Если таблица 'users' не существует, будет выброшено исключение с сообщением
// 'Table 'test.users' doesn't exist'
Ошибка MySQL: Table 'test.users' doesn't exist (код 1146)

Примечание: код ошибки (1146) можно использовать для выборочной обработки.

Пример 3: Логирование ошибок с дополнительным контекстом (файл, строка, SQL)

Для детального логирования полезно передавать в лог не только сообщение, но и текст запроса, вызвавшего ошибку.

Пример

function logSqlError(PDOException $e, $sql) {
    $message = sprintf(
        "[%s] SQL Error: %s in file %s on line %d\nQuery: %s\n",
        date('Y-m-d H:i:s'),
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $sql
    );
    error_log($message, 3, '/var/log/mysql_errors.log');
}

$sql = "INSERT INTO logs (data) VALUES ('test')";
try {
    $pdo->exec($sql);
} catch (PDOException $e) {
    logSqlError($e, $sql);
    echo 'Операция не выполнена. Пожалуйста, свяжитесь с администратором.';
}
(Файл /var/log/mysql_errors.log появится с записью типа)
[2025-03-18 12:30:00] SQL Error: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'test' for key 'data' in file /var/www/html/index.php on line 15
Query: INSERT INTO logs (data) VALUES ('test')

Такой лог включает время, исходный код и параметры запроса, что ускоряет поиск причины.

Пример 4: Глобальный обработчик ошибок для PDO и MySQL

Можно установить пользовательский обработчик исключений, который перехватывает все PDOException и отправляет их в единый лог-сервис.

Пример

set_exception_handler(function (Throwable $e) {
    if ($e instanceof PDOException) {
        // Запись в файл
        error_log('PDO Exception: ' . $e->getMessage());
        // Отправка в Sentry (пример)
        // \Sentry\captureException($e);
        // Вывод пользователю
        echo 'Произошла ошибка базы данных. Мы уже работаем над её исправлением.';
    } else {
        // Для других исключений - стандартное поведение
        throw $e;
    }
});

// Теперь любой необработанный PDOException попадёт в этот обработчик
$pdo->query('SELECT * FROM nonexistent');
(На экране отобразится сообщение:)
Произошла ошибка базы данных. Мы уже работаем над её исправлением.

Этот подход позволяет централизованно управлять всеми ошибками MySQL, не прописывая try-catch в каждом месте.

Пример 5: Обработка ошибок при работе с кодировкой (charset)

Если не указать кодировку соединения, возможны ошибки неверного сравнения строк или искажения данных. Пример ошибки и её исправления:

Пример

// Ошибочное соединение (без charset)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Запрос с русскими символами может привести к ошибке или неправильному результату
try {
    $pdo->exec("INSERT INTO users (name) VALUES ('Иван')");
} catch (PDOException $e) {
    echo 'Ошибка вставки: ' . $e->getMessage() . "\n";
}
// Возможная ошибка: 'Incorrect string value' (SQLSTATE[22007])

// Исправление - указать charset в DSN
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("INSERT INTO users (name) VALUES ('Иван')");
(При первом варианте - сообщение об ошибке, при втором - успешная вставка)
Ошибка вставки: SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value: '\xD0\xB8...'

Рекомендуется всегда указывать utf8mb4 для полной поддержки Unicode.

Ошибки PHP MySQL - comments

En
Php mysql ошибки (php)