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.
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 with | Means | Section |
|---|---|---|
SQLSTATE[HY000] [2002] | Can't connect to the server at all | 2002 |
SQLSTATE[HY000] [1045] | Connected, but the login was refused | 1045 |
SQLSTATE[42S02] | The table doesn't exist | 42S02 |
SQLSTATE[42S22] | A column doesn't exist | 42S22 |
SQLSTATE[42000] | SQL syntax error, or a limit or mode the query breaks | 42000 |
SQLSTATE[23000] | A constraint refused the row: duplicate, foreign key or NULL | 23000 |
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:
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
rootuses 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:statusshows which are pending; deploy scripts needphp artisan migrate --force. - Wrong database. The message names it (
shop.); compare it withDB_DATABASE. - Laravel guessed the name. A model
OrderItemusesorder_items; abelongsToManybetweenOrderandProductusesorder_product, in alphabetical order. Setprotected $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,cacheandjobstables 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. Addpublic $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:
| Code | Message | Cause and fix |
|---|---|---|
| 1064 | You have an error in your SQL syntax | Usually 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 BY | Laravel 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. |
| 1071 | Specified key was too long; max key length is 767 bytes | MySQL 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 database | The 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 throwsIlluminate\Database\UniqueConstraintViolationExceptionfor this case:PHPuse 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.