SQLSTATE errors in Laravel, code by code

What SQLSTATE HY000 2002, 1045, 42000, 23000, 42S02 and 42S22 mean in a Laravel QueryException, how to tell which cause you have, and the fix for each.

Updated View as Markdown

A Laravel QueryException starts with the database's error code: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry …. The five characters in brackets are the standard SQLSTATE class; the number after them is MySQL's or MariaDB's own error code, and that is the one to search for. This page covers the codes Laravel developers hit most, in the order a new app tends to meet them.

Message starts withMeansSection
SQLSTATE[HY000] [2002]Can't connect to the server at all2002
SQLSTATE[HY000] [1045]Connected, but the login was refused1045
SQLSTATE[42S02]The table doesn't exist42S02
SQLSTATE[42S22]A column doesn't exist42S22
SQLSTATE[42000]SQL syntax error, or a limit or mode the query breaks42000
SQLSTATE[23000]A constraint refused the row: duplicate, foreign key or NULL23000

SQLSTATE[HY000] [2002] Connection refused

PHP couldn't reach the database server: wrong host or port, the server isn't running, or DB_HOST=localhost makes PHP look for a socket file that isn't there ("No such file or directory"). In Docker and Sail, the host is the service name, not 127.0.0.1. This one has its own page: SQLSTATE[HY000] [2002] Connection refused.

SQLSTATE[HY000] [1045] Access denied for user

SQLSTATE[HY000] [1045] Access denied for user 'forge'@'10.0.1.12' (using password: YES)

The server is reachable and rejected the username, password or the host you connect from. using password: NO means Laravel sent an empty password, so DB_PASSWORD didn't load.

A common cause is a password with a # in .env. Without quotes, everything from # on is a comment. We checked it on Laravel 13.40:

.env
DB_PASSWORD=ab#cd      # Laravel reads "ab"
DB_PASSWORD="ab#cd"    # Laravel reads "ab#cd"

Double quotes also expand ${OTHER_VAR}; use single quotes for a password that contains ${. Other causes:

  • The MySQL user exists only for 'user'@'localhost', and you now connect over TCP from another host or container. Create it for the right host: CREATE USER 'forge'@'10.0.1.%' IDENTIFIED BY '…'; and grant it the database.
  • The config is cached with the old password: php artisan config:clear && php artisan config:cache.
  • On Ubuntu, MySQL's root uses socket authentication and has no password. Create a separate user for the app.

SQLSTATE[42S02]: Base table or view not found: 1146

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'shop.order_product' doesn't exist

The query names a table that isn't in the database it's connected to.

  • Migrations didn't run on this environment. php artisan migrate:status shows which are pending; deploy scripts need php artisan migrate --force.
  • Wrong database. The message names it (shop.); compare it with DB_DATABASE.
  • Laravel guessed the name. A model OrderItem uses order_items; a belongsToMany between Order and Product uses order_product, in alphabetical order. Set protected $table = 'order_lines'; on the model, or pass the pivot name: $this->belongsToMany(Product::class, 'product_orders').
  • Sessions, cache or queue on the database driver without their tables. New apps need the sessions, cache and jobs tables from the default migrations.

SQLSTATE[42S22]: Column not found: 1054

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'discount_total' in 'field list'

A query names a column the table doesn't have.

  • The migration that adds it hasn't run here (migrate:status), or ran on another database.
  • A typo, or a column that was renamed in a migration while the code still uses the old name.
  • Unknown column 'updated_at': the table has no timestamps. Add public $timestamps = false; to the model, or add $table->timestamps() to the table.
  • An accessor or appended attribute that you write back with $model->update($request->all()). Validate the input and pass only real columns.

SQLSTATE[42000]: Syntax error or access violation

42000 groups several different MySQL errors. The number after it tells them apart:

CodeMessageCause and fix
1064You have an error in your SQL syntaxUsually a raw query: a reserved word as a column name (order, group, key) without backticks, or string concatenation instead of bindings. Use the query builder or DB::select('… where id = ?', [$id]).
1055… isn't in GROUP BYLaravel runs MySQL in strict mode, which includes ONLY_FULL_GROUP_BY. Select only grouped columns and aggregates, or wrap the others in ANY_VALUE(). Turning off 'strict' in config/database.php hides the problem instead of fixing it.
1071Specified key was too long; max key length is 767 bytesMySQL before 5.7.7 or MariaDB before 10.2.2 with utf8mb4. Upgrade the server, or add Schema::defaultStringLength(191); to AppServiceProvider::boot().
1142 / 1044… command denied to user / Access denied for user … to databaseThe database user lacks a privilege, often CREATE or ALTER during migrations, or access to the database at all. Grant it.

SQLSTATE[23000]: Integrity constraint violation

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'ord_8F2K' for key 'orders.number'

A constraint refused the row. Again, the number decides the fix:

  • 1062 Duplicate entry. A unique index already has this value. If users submit it, validate first with Rule::unique('orders', 'number'). If two requests or jobs race, validation can't help: let the index decide and handle the result. Laravel throws Illuminate\Database\UniqueConstraintViolationException for this case:

    PHP
    use Illuminate\Database\UniqueConstraintViolationException;
    
    try {
        $order = Order::create(['number' => $number, 'user_id' => $user->id]);
    } catch (UniqueConstraintViolationException) {
        $order = Order::where('number', $number)->firstOrFail();
    }
    

    Order::createOrFirst([...]) does the same in one call. Retried queue jobs are a common source: a job that inserts a row and then fails runs again and hits the duplicate.

  • 1452 Cannot add or update a child row: a foreign key constraint fails. The referenced row doesn't exist, often because it was created in a transaction that hasn't committed yet, or in a queued job dispatched before the commit. Dispatch with ->afterCommit().

  • 1451 Cannot delete or update a parent row. Other rows still reference this one. Delete them first, or use ->cascadeOnDelete() on the foreign key.

  • 1048 Column 'x' cannot be null. The value is missing; the column needs a default, ->nullable(), or the code must set it.

Catch SQLSTATE errors in production

Every QueryException that isn't caught reaches Laravel's exception handler. With the Laravel package, Lampwick groups them into issues by class, code, file and line, so a thousand duplicate-entry errors from one create() call are one row in Issues with a count, the users affected and the request or job each one broke. See work with issues for resolving them and getting notified when one comes back.

Related: MySQL server has gone away, the error you see when a connection that worked is lost.