Обработка 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();
}
  

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

После этой настройки любые неудачные запросы (например, синтаксическая ошибка 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)