Skip to content

MySQL Tutorial MySQL with PHP

MySQL Errors in PHP

Lesson 264 of 308 3 min read By Deepak Paswan Updated Oct 5, 2026

When a query fails, PDO (with ERRMODE_EXCEPTION) throws a PDOException. General error handling is in Database Errors. This lesson shows how to read MySQL’s own error number, so your code can tell a duplicate email from a deadlock.

Where the details are

index.php
PHP
<?php
$pdo = require __DIR__ . '/db.php';

try {
    $pdo->query('SELECT missing_column FROM products');
} catch (PDOException $e) {
    echo 'getCode():       ', $e->getCode(), "\n";                 // the SQLSTATE
    echo 'errorInfo[0]:    ', $e->errorInfo[0], "\n";             // SQLSTATE (5 characters)
    echo 'errorInfo[1]:    ', $e->errorInfo[1], "\n";             // the MySQL error number
    echo 'errorInfo[2]:    ', $e->errorInfo[2], "\n";             // the message text
}
Output
OUTPUT
getCode():       42S22
errorInfo[0]:    42S22
errorInfo[1]:    1054
errorInfo[2]:    Unknown column 'missing_column' in 'field list'

Note that getCode() returns the SQLSTATE (a string such as 42S22), while errorInfo[1] is the MySQL error number (1054), and that is the one you can compare with an int. (For connection errors thrown by new PDO, getCode() returns the MySQL number such as 2002, as you saw earlier.)

The codes you will meet

CodeSQLSTATEMeaningTypical cause and response
106223000Duplicate entry for a unique keyEmail already registered. Tell the user
145123000Cannot delete or update a parent row: a foreign key constraint failsDeleting a customer who has orders. Explain, or archive instead
145223000Cannot add or update a child row: a foreign key constraint failsThe parent does not exist. A bug or a stale form
104823000Column cannot be nullMissing required value. Validate earlier
1364HY000Field doesn’t have a default valueThe insert forgot a NOT NULL column
140622001Data too long for columnValidate length before inserting
126422003Out of range valueA number too large for the type
105442S22Unknown columnA typo or a wrong deployment: a bug
114642S02Table doesn’t existA missing migration: a bug
106442000SQL syntax errorA bug (often dynamic SQL or LIMIT with a string)
121340001DeadlockRetry the transaction
1205HY000Lock wait timeoutRetry, or shorten transactions
1040HY000Too many connectionsServer overloaded; back off
2006 / 2013HY000Server has gone away / Lost connectionReconnect, check wait_timeout and max_allowed_packet

Group them: user mistakes (1062, 1048, 1406, 1451) deserve a friendly message. Bugs (1054, 1064, 1146) deserve a log entry, an alert and a general message. Transient errors (1213, 1205, 2006) deserve a retry.

A small mapping

index.php
PHP
<?php
$pdo = require __DIR__ . '/db.php';

function friendlyMessage(PDOException $e): ?string
{
    return match ($e->errorInfo[1] ?? 0) {
        1062    => 'That value is already in use.',
        1451    => 'This record is still used by other records.',
        1452    => 'The related record does not exist.',
        1048    => 'A required value is missing.',
        1406    => 'One of the values is too long.',
        default => null,           // not a user mistake: let the caller log it and show a general message
    };
}

$attempts = [
    ['INSERT INTO customers (name, email) VALUES ("Dup", "maya@example.com")'],
    ['DELETE FROM customers WHERE id = 1'],
    ['INSERT INTO customers (name, email) VALUES (NULL, "n@example.com")'],
    ['INSERT INTO customers (name, email) VALUES (REPEAT("x", 80), "long@example.com")'],
    ['SELECT nothing FROM customers'],
];
foreach ($attempts as [$sql]) {
    try {
        $pdo->exec($sql);
    } catch (PDOException $e) {
        echo $e->errorInfo[1], ' => ', friendlyMessage($e) ?? '(general error, log it)', "\n";
    }
}
Output
OUTPUT
1062 => That value is already in use.
1451 => This record is still used by other records.
1048 => A required value is missing.
1406 => One of the values is too long.
1054 => (general error, log it)

Log details, show little

index.php
PHP
<?php
$pdo = require __DIR__ . '/db.php';

try {
    $pdo->query('SELECT * FROM missing_table');
} catch (PDOException $e) {
    error_log(sprintf('[db] code=%s sqlstate=%s msg=%s', $e->errorInfo[1], $e->errorInfo[0], $e->errorInfo[2]));
    http_response_code(500);
    echo "Sorry, something went wrong. Please try again later.\n";
}
Output
OUTPUT
Sorry, something went wrong. Please try again later.

The visitor sees a general message. The log (error_log) holds the code, the SQLSTATE and the message, but not the parameter values, which may be personal data or secrets. Never print $e->getMessage() or the SQL on a public page: it reveals table names and helps attackers (SQL Injection).

Warnings are not exceptions

Some problems in MySQL are warnings, not errors (for example truncation in non-strict mode). PDO does not throw for warnings. Keep strict mode on, so that the same problems become real errors, and check with SHOW WARNINGS when debugging (Errors and Warnings).

Test the error paths

Write at least one test for each error you handle: insert a duplicate, delete a parent with children, and so on. Error branches that never run are where bugs hide.

lightbulb
Compare the number, not the message
Message texts change between versions and languages. The numeric error code (errorInfo[1]) and the SQLSTATE are stable.