It reads, finds nothing, and inserts. Two requests doing that concurrently both find nothing and both insert, which is why the duplicates appear only under load.
// racy
$tag = Tag::firstOrCreate(['name' => $name]);
// the database decides, which is the only thing that can
DB::table('tags')->insertOrIgnore(['name' => $name]); // 5.8; before that:
try {
$tag = Tag::create(['name' => $name]);
} catch (QueryException $e) {
if ($e->errorInfo[1] !== 1062) { // not a duplicate key
throw $e;
}
$tag = Tag::where('name', $name)->firstOrFail();
}
The unique index is what makes any of this work; without it the try/catch never fires and the duplicates arrive regardless. Catching the driver error code rather than matching on the message is the part worth being careful about, since the message is localised on some MySQL builds. This is the same shape as every check-then-act race, and the answer is always to let a constraint arbitrate.