I need to check if a row exists, and if it does – update some column, and if not, create a new record.
But the queries I am using are not thread safe, as I do not have unique indexes on that table because I can create new rows with same values based on the last_update
value:
$row = DB::table('some_table')
->where('last_update', '>=', now()->subMinutes(5))
->where('user_id', '=', $user_id)
->where('comment_type', '=', $comment_type)
->first();
if ($row === null) {
// record not found, create new
DB::table('some_table')->insert([
'user_id' => $user_id,
'comment_type' => $comment_type,
'created_at' => $created_at,
'last_update' => $last_update
]);
} else {
// record found, update existing
DB::table('some_table')
->where('id', '=', $row->id)
->update(['last_update' => now()]);
}
Is there a way to make it more thread safe?
Edit: What I mean by safe is that, in the above code, there might be a situation where 2 threads reach the code and almost the same time, both of them getting null
value for $row
and then they will continue to insert a new row, ending up with two records
2
Answers
I don’t know if it is possible without a unique constraint, but one way you can do it is to create shallow copies of the row.
In your case, you will insert directly regardless of whether the user_id and comment type exist or not in the database and add a timestamp to the row. When you want to read the row, do a SELECT … WHERE … ORDER BY timestamp DESC LIMIT 1. Use a cronjob or something like that to do a database cleanup at intervals.
Use DB::transaction to guarantee a block is executed exlusively
Transactions will lock your table so beaware about performance issues. You may also check laravel’s updateOrInsert or updateOrCreate methods to combine conditions and query