Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dinni's avatar
Level 1

Race conditions (lockForUpdate)

Hi, I have made 2 functions, where both updates the wallet column, but im not able to avoid race conditions

function 1

$user = User::lockForUpdate()->first();
sleep(10);
$user->wallet = $user->wallet+101;
$user->save();
\Log::info('function 1 wallet '.$user->wallet);

Function 2

sleep(5);
$user = User::lockForUpdate()->first();
sleep(30);
$user->wallet = $user->wallet+102;
 $user->save();
\Log::info('function 2 wallet'.$user->wallet);

im running both at same time, If user balance is 100 before start of these functions, im expecting result of 100+101+102 = 303, But im getting as 202.

How can i get 303.

0 likes
12 replies
lolsokje's avatar

You'd need to use $user->fresh()->wallet in your 2nd function to retrieve the updated value from the database. Just using $user->wallet gets the cached value from the initial lookup, not the value that was updated by function 1.

2 likes
dinni's avatar
Level 1

@lolsokje if 2 or more request hitting the server at same time , this will work? Will it avoid race conditions?

andrebarbosadev's avatar

@lolsokje Is this idea of ​​updating still viable? thinking that I have a list of numbers that can be called together and I need to block, but update the data after blocking.

Snapey's avatar

@andrebarbosadev Best ask your own question, and explain what "I have a list of numbers that can be called together" actually means.

1 like
andrebarbosadev's avatar

@Snapey Hello, thanks for responding. My question about this is: I have a function where it reserves random numbers that it takes from a list that is saved within the "product" table and I have a cron where it updates this list every 1 minute, but it can happen from the reserveNumbers function and the cron run at the same time and this generates competition where one of them can take an outdated list and update the list. I tried using lockforupdate with transaction but it still continues, and I tried the refresh solution.

Sinnbeck's avatar

Have you tried wrapping both in a transaction?

DB::transaction(function () {
$user = User::lockForUpdate()->first();
sleep(10);
$user->wallet = $user->wallet+101;
$user->save();
\Log::info('function 1 wallet '.$user->wallet);

});
2 likes
Snapey's avatar
Snapey
Best Answer
Level 122

both functions grab the user before doing the update so both are working on old data

forget locking and use the increment function which is atomic

https://laravel.com/docs/8.x/queries#increment-and-decrement

DB::table('users')->increment('wallet', 101);

You will obviously need to refresh the user model if you are going on to use the wallet value

2 likes
dinni's avatar
Level 1

@Snapey if 2 request hitting the server at same time , this will work? Will it avoid race conditions?

Snapey's avatar

@dinni yes, that's exactly the point

The increment function tells the database to add to the column. it cannot be interrupted so it is known as 'atomic'

Your approach is get the value, add to the value, save the value. Before saving, another process can get the old value as well. last to save wins. You can avoid this in this case with increment

1 like
dinni's avatar
Level 1

@Snapey one last clarification , should i use ->lockForUpdate() with increment or its not necessary ?

User::find('id',$user->id)
      ->increment('wallet',$creditAmount);
User::LockForUpdate()
	    ->find('id',$user->id)
        ->increment('wallet',$creditAmount);

which is recommended ?

Sinnbeck's avatar

@dinni Check the answer by @snapey again

forget locking and use the increment function which is atomic

1 like

Please or to participate in this conversation.