Laravel error
Laravel: Duplicate entry for key
The error
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'ada@example.com' for key 'users_email_unique'A unique index refused a row. Catching the exception is not the fix - deciding whether the second write was a mistake, a race or a retry is, and each wants different code.
SQLSTATE[23000]: Integrity constraint violation: 1062
Duplicate entry 'ada@example.com' for key 'users_email_unique'
The database refused a row because a unique index already holds that value. The message gives you both halves of the diagnosis: the value that collided, and the index name, which decodes as table_column_unique.
What it does not tell you is which of three situations you are in, and they want different fixes.
It is a mistake in your logic
The straightforward case. Something tried to create a record that should have been an update, or a seeder ran twice, or an import has the same row in it twice.
Fix the logic. updateOrCreate where an update was meant, a de-duplication pass over the import, a seeder that is idempotent. Nothing here is about the exception.
It is a race, and validation will not save you
This one surprises people, because the application does validate:
$request->validate(['email' => 'unique:users,email']);
User::create($request->only('email', 'name'));Two requests arrive at the same moment. Both run the validation query, both find nothing, both proceed, one inserts, the other hits the index. The rule was never atomic - it is a select, and the insert happens later.
The same is true of the shape people reach for to avoid exactly this:
User::firstOrCreate(['email' => $email], ['name' => $name]); // select, then insertIt reads as a single operation and compiles to two.
The index is the only thing that actually enforces uniqueness, so let it, and handle the loss:
try {
$user = User::create(['email' => $email, 'name' => $name]);
} catch (UniqueConstraintViolationException) {
$user = User::where('email', $email)->firstOrFail(); // someone won; use theirs
}Laravel surfaces this as Illuminate\Database\UniqueConstraintViolationException, which is narrower than catching QueryException and checking the driver's error code by hand.
Where the database supports it, an upsert does the same thing in one statement and is better still:
User::upsert([['email' => $email, 'name' => $name]], ['email'], ['name']);It is a retry, and the duplicate is the point
A queued job runs twice. A payment provider delivers the same webhook again. A user double-clicks submit.
Here the duplicate is not a failure - it is the system working as designed, and the unique index is what makes the second attempt harmless. A webhook table with a unique index on the provider's event id turns a repeated delivery into a rejected insert you can ignore, which is a far more reliable de-duplication than checking whether the row exists first.
That is the same reasoning that makes a queue safe: the index does the enforcing, because a job will be attempted more than once whatever your code assumes.
What not to do
Removing the index. It is the only thing standing between you and two users sharing an email address, and the pressure to drop it always arrives during an incident, when it looks like the cause rather than the detector.
If the index is genuinely wrong - too narrow, or over a column that was never meant to be unique - change it deliberately with a migration. Dropping it to make an error go away is how a table ends up with four accounts for one person and no way to tell which is real.
Constraints are cheap to add before a table has rows and awkward afterwards, which is part of why the schema decisions are worth settling early.
