{"id":144,"date":"2025-10-14T10:46:17","date_gmt":"2025-10-14T08:46:17","guid":{"rendered":"https:\/\/georgekolios.psy.auth.gr\/?p=144"},"modified":"2025-10-14T11:04:35","modified_gmt":"2025-10-14T09:04:35","slug":"understanding-lockforupdate-and-sharedlock-in-laravel","status":"publish","type":"post","link":"https:\/\/georgekolios.psy.auth.gr\/index.php\/2025\/10\/14\/understanding-lockforupdate-and-sharedlock-in-laravel\/","title":{"rendered":"Understanding lockForUpdate and sharedLock in Laravel"},"content":{"rendered":"\n<p class=\"has-text-align-center\"><a href=\"https:\/\/rennokki.hashnode.dev\/understanding-lockforupdate-and-sharedlock-in-laravel\" data-type=\"link\" data-id=\"https:\/\/rennokki.hashnode.dev\/understanding-lockforupdate-and-sharedlock-in-laravel\" target=\"_blank\" rel=\"noreferrer noopener\">By  Alex Renoki<\/a><\/p>\n\n\n\n<p>Another source: <a href=\"https:\/\/laravel-news.com\/managing-data-races-with-pessimistic-locking-in-laravel\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/laravel-news.com\/managing-data-races-with-pessimistic-locking-in-laravel<\/a><\/p>\n\n\n\n<p>Transactional databases are fast and reliable, but not all the time. When you want to have high transaction numbers, you sacrifice something important for speed. That something important is atomicity. And here\u2019s a real-world example that\u2019s way more straightforward than a game \u2014 a banking system.<\/p>\n\n\n\n<p>Let\u2019s consider we have a very poor banking system in the pre-crypto era where our customers can easily transact between them. We are not really experienced with ledgers, so we choose to go with MySQL or Postgres (yes, even Postgres might go back if we don\u2019t use locking).<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Storing users\u2019 details and the current balance is easy, right?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>namespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass User extends Model\n{\n    protected $fillable = &#91;\n        'name',\n        'balance',\n    ];\n    protected $casts = &#91;\n        'balance' =&gt; 'float',\n    ];\n}<\/code><\/pre>\n\n\n\n<p>Our bank is going to be the intermediary, so we can act as a class too:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Bank\n{\n    public static function sendMoney(User $from, User $to, float $amount)\n    {\n        if ($from-&gt;balance &lt; $amount) {\n            return false;\n        }\n\n        $from-&gt;update(&#91;'balance' =&gt; $from-&gt;balance - $amount]);\n        $to-&gt;update(&#91;'balance' =&gt; $to-&gt;balance + $amount]);\n\n        return true;\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>And we can send money between the accounts:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$alice = User::find(1); \/\/ 'balance' =&gt; 100\n$bob = User::find(2); \/\/ 'balance' =&gt; 0\n\nBank::sendMoney($alice, $bob, 100); \/\/ true\n<\/code><\/pre>\n\n\n\n<p>You may ask now \u2014 okay, what\u2019s wrong with it? It seems to do what it is supposed to, right?<\/p>\n\n\n\n<p>Well, not quite. There are two major implementation issues that need to be addressed unless we want to have a double-spending issue with this centralized system.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"heading-database-transactions\">Database Transactions<\/h2>\n\n\n\n<p>The first major issue consists of the two lines where we update the balance of both customers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$from-&gt;update(&#91;'balance' =&gt; $from-&gt;balance - $amount]);\n$to-&gt;update(&#91;'balance' =&gt; $to-&gt;balance + $amount]);\n<\/code><\/pre>\n\n\n\n<p>The major issue is that this is not a single point of failure. Meaning that if the first statement commits and the second one doesn\u2019t, we will just <strong>magically<\/strong> erase some money from our system. We will subtract the money, but not add the money to Bob\u2019s account.<\/p>\n\n\n\n<p>To fix this, we will run <a target=\"_blank\" href=\"https:\/\/laravel.com\/docs\/8.x\/database#database-transactions\" rel=\"noreferrer noopener\">atomic transactions<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use Illuminate\\Support\\Facades\\DB;\n\nclass Bank\n{\n    public static function sendMoney(User $from, User $to, float $amount)\n    {\n        if ($from-&gt;balance &lt; $amount) {\n            return false;\n        }\n\n        return DB::transaction(function () {\n            $from-&gt;update(&#91;'balance' =&gt; $from-&gt;balance - $amount]);\n            $to-&gt;update(&#91;'balance' =&gt; $to-&gt;balance + $amount]);\n            return true;\n        });\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>In this particular case, the transaction will run, and in case it encounters any exception, it will roll back every single statement from before.<\/p>\n\n\n\n<p>If the second transaction fails, it will roll back the first one, to make sure that Alice gets her money back, the money that was not able to get into Bob\u2019s account.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"heading-pessimistic-locking\">Pessimistic Locking<\/h2>\n\n\n\n<p>We fixed the flaw of magically altering the inflation levels, but we face another issue: what if the balance changes mid-transaction? To better show the issue, we will have a new person that also needs money from Alice. Meet Charlie!<\/p>\n\n\n\n<p>In this specific example, because we have a web app with thousands of HTTP web requests (slightly exaggerated, but banks do encounter this), we will ignore the fact that PHP is a blocking-IO programming language in the examples.<\/p>\n\n\n\n<p>Let\u2019s say that Alice has a fast sleight of hand, and both transactions where Alice sends money to Bob and Charlie are done, like at the same time, in the matter of microseconds. This means that if the odds are just right, the database will pull the records at the very same time, and send money at the very exact time.<\/p>\n\n\n\n<p>If this scenario occurs, you will merely become stunned how from $100, you turned a total of $200 in the bank, $100 for each account.<\/p>\n\n\n\n<p>The issue here is that we don\u2019t have a locking mechanism in place for our database.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FIRST REQUEST\n$alice = User::find(1); \/\/ 'balance' =&gt; 100,\n$bob = User::find(2); \/\/ 'balance' =&gt; 0,\nBank::sendMoney($alice, $bob, 100); \/\/ true\n\n\/\/ SECOND REQUEST\n$alice = User::find(1); \/\/ 'balance' =&gt; 100,\n$charlie = User::find(3); \/\/ 'balance' =&gt; 0,\nBank::sendMoney($alice, $charlie, 100); \/\/ true, but should have been false\n<\/code><\/pre>\n\n\n\n<p>This happens because if both queries run the SELECT statements (the ones defined by <code>find()<\/code>) at the same time, both requests will read that Alice has $100 in her account, which can be false because if the other transaction has already changed the balance, we remain with a reading saying she still has $100.<\/p>\n\n\n\n<p>In this particular case, this is what might happen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Request1: Reads Alice balance as $100\nRequest2: Reads Alice balance as $100\nRequest1: Subtract $100 from Alice\nRequest2: Subtract $100 from Alice\nRequest1: Add $100 to Bob\nRequest2: Add $100 to Charlie\n<\/code><\/pre>\n\n\n\n<p>The ideal situation would be this one:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Request1: Reads Alice balance as $100\nRequest1: Subtract $100 from Alice.\nRequest1: Add $100 to Bob\nRequest2: Reads Alice balance as $0\nRequest2: Don't allow Alice to send money\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"heading-implementing-pessimistic-locking\"><a href=\"https:\/\/rennokki.hashnode.dev\/understanding-lockforupdate-and-sharedlock-in-laravel#heading-implementing-pessimistic-locking\"><\/a><\/h2>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"heading-implementing-pessimistic-locking\">\u2705 Implementing Pessimistic Locking<\/h2>\n\n\n\n<p>Laravel has a neat way to tackle this issue with the help of queries. Databases (like MySQL) have a thing called <a target=\"_blank\" href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/innodb-deadlocks.html\" rel=\"noreferrer noopener\">deadlock<\/a>. Deadlocks permit the one who runs a query to specifically describe whose rows can be selected or updated within a specific query.<\/p>\n\n\n\n<p>Laravel has its own <a target=\"_blank\" href=\"https:\/\/laravel.com\/docs\/8.x\/queries#pessimistic-locking\" rel=\"noreferrer noopener\">documentation section about deadlocks<\/a>, but it was hard to digest which does what, so we have this awesome banking example.<\/p>\n\n\n\n<p>Laravel documentation says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>A \u201cfor update\u201d lock prevents the selected records from being modified or from being selected with another shared lock.<\/p>\n<\/blockquote>\n\n\n\n<p>This is what we want. If we run <code>lockForUpdate<\/code> in our <code>find()<\/code> statements, they will not be selected by another shared lock.<\/p>\n\n\n\n<p>And for the shared lock:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>A shared lock prevents the selected rows from being modified until your transaction is committed.<\/p>\n<\/blockquote>\n\n\n\n<p>Is this also what we want? Of course, if we apply this to the <code>find()<\/code> queries, the rows (in the first one Alice &amp; Bob, in the second one Alice &amp; Charlie) will not be read, nor modified until our <code>update<\/code> transaction got committed successfully.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FIRST REQUEST\nDB::transaction(function () {\n    $alice = User::lockForUpdate()-&gt;find(1); \/\/ 'balance' =&gt; 100\n    $bob = User::lockForUpdate()-&gt;find(2); \/\/ 'balance' =&gt; 0\n\n    Bank::sendMoney($alice, $bob, 100); \/\/ true\n});\n\n\/\/ SECOND REQUEST\nDB::transaction(function () {\n    $alice = User::lockForUpdate()-&gt;find(1); \/\/ 'balance' =&gt; 0\n    $charlie = User::lockForUpdate()-&gt;find(3); \/\/ 'balance' =&gt; 0\n\n    Bank::sendMoney($alice, $charlie, 100); \/\/ false\n});\n<\/code><\/pre>\n\n\n\n<p>Obviously, having a <code>lockForUpdate<\/code> would be just enough, because, by definition, any rows selected by it will never be selected by another shared lock, either <code>lockForUpdate()<\/code> or <code>sharedLock()<\/code>.<\/p>\n\n\n\n<p>Alternatively, just like the official Laravel writes, you may use <code>sharedLock()<\/code> just so other queries won\u2019t select the same rows until the transaction is finished. The use case would be for strong read consistency, making sure that if another transaction may be in process, to not get outdated rows.<\/p>\n\n\n\n<p>Thanks to Laravel and deadlocks, we can now avoid any inflation.\ud83d\udc4f<\/p>\n\n\n\n<p>But if you decide to run your bank in Laravel, you should use <a target=\"_blank\" href=\"https:\/\/spatie.be\/docs\/laravel-event-sourcing\/v5\/introduction\" rel=\"noreferrer noopener\">Event Sourcing<\/a>, you definitely don\u2019t want to play with the market. \ud83e\udd28<\/p>\n","protected":false},"excerpt":{"rendered":"<p>By Alex Renoki Another source: https:\/\/laravel-news.com\/managing-data-races-with-pessimistic-locking-in-laravel Transactional databases are fast and reliable, but not all the time. When you want to have high transaction numbers, you sacrifice something important for speed. That something important is atomicity. And here\u2019s a real-world example that\u2019s way more straightforward than a game \u2014 a banking system. Let\u2019s consider we [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10,11],"tags":[],"class_list":["post-144","post","type-post","status-publish","format-standard","hentry","category-interesting","category-laravel"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/posts\/144","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/comments?post=144"}],"version-history":[{"count":7,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/posts\/144\/revisions"}],"predecessor-version":[{"id":153,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/posts\/144\/revisions\/153"}],"wp:attachment":[{"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/media?parent=144"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/categories?post=144"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/georgekolios.psy.auth.gr\/index.php\/wp-json\/wp\/v2\/tags?post=144"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}