One solution to prevent a race condition in this case is to use a database lock on the "inUse" table before inserting any new codes. This will ensure that only one transaction can access the "inUse" table at a time, preventing multiple users from inserting the same codes simultaneously.
Here's an updated version of the code with the lock added:
DB::transaction(function () use ($values) {
// Query here to get all available codes of each type into a collection
// Here I use the take() method on the collection with the number of codes the user requested from each code type, so now I have an array like this: ['1232', '112333', '333777'].
// After getting list of codes I re-select only the codes the user requested
$codes = DB::table('available')->whereIn('codes', $values)->pluck('codes')->toArray();
// Lock the "inUse" table to prevent race conditions
DB::table('inUse')->lockForUpdate()->get();
$existingValues = DB::table('inUse')->whereIn('codes', $codes)->get();
// Insert the values that don't exist
if (!empty($existingValues)) {
DB::table('inUse')->insert($codes);
}
});
Note that this solution may cause some transactions to wait for the lock to be released, which could lead to slower performance during high traffic periods. However, it should prevent any race conditions from occurring.