Laravel refreshForUpdate() Pessimistic Locking | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Pessimistic Locking in Laravel Eloquent with refreshForUpdate()        On this page       1. [  What Is refreshForUpdate()? ](#what-is-coderefreshforupdatecode)
2. [  How It Works Under the Hood ](#how-it-works-under-the-hood)
3. [  Before and After ](#before-and-after)
4. [  Before Laravel 13.27 ](#before-laravel-1327)
5. [  After Laravel 13.27 ](#after-laravel-1327)
6. [  Key Takeaways ](#key-takeaways)

  ![Pessimistic Locking in Laravel Eloquent with refreshForUpdate()](https://cdn.msaied.com/605/c67ac0ef4197be20a45afdbc4f26f670.png)

 [  Laravel ](https://msaied.com/articles?category=laravel) [  Tips &amp; Tricks ](https://msaied.com/articles?category=tips-tricks)  #Laravel   #Eloquent   #Pessimistic Locking   #Database Transactions   #Laravel 13  

 Pessimistic Locking in Laravel Eloquent with refreshForUpdate() 
=================================================================

     28 Aug 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   What Is refreshForUpdate()?  ](#what-is-coderefreshforupdatecode)
2. [  02   How It Works Under the Hood  ](#how-it-works-under-the-hood)
3. [  03   Before and After  ](#before-and-after)
4. [  04   Before Laravel 13.27  ](#before-laravel-1327)
5. [  05   After Laravel 13.27  ](#after-laravel-1327)
6. [  06   Key Takeaways  ](#key-takeaways)

 What Is `refreshForUpdate()`?
-----------------------------

Laravel has long provided two separate tools for working with database rows inside transactions:

- `refresh()` — reloads a model's attributes from the database.
- `lockForUpdate()` — appends `FOR UPDATE` to a query, preventing other transactions from reading or modifying the row until the lock is released.

What was missing was a way to do **both on an existing model instance** without rebuilding the query manually. Laravel 13.27 fills that gap with `refreshForUpdate()`, contributed by [@stevebauman](https://github.com/stevebauman) in [\#61247](https://github.com/laravel/framework/pull/61247).

---

How It Works Under the Hood
---------------------------

The implementation is concise:

```php
public function refreshForUpdate()
{
    if (! $this->exists) {
        return $this;
    }

    return $this->refreshUsingQuery(
        $this->newQueryWithoutScopes()->lockForUpdate()
    );
}

```

It delegates to `refreshUsingQuery()` — the same internal helper that powers `refresh()` — but passes a query that already has `lockForUpdate()` applied. That helper:

1. Scopes the query to the model's primary key.
2. Routes the query through `useWritePdo()` so a read replica cannot serve a lock you are about to depend on.
3. Calls `firstOrFail()` to fetch the row.
4. Replaces the model's raw attributes in place.
5. Reloads any relations that were already eager-loaded.
6. Syncs the original attribute state.

The only addition `refreshForUpdate()` makes over a plain `refresh()` is the `FOR UPDATE` lock.

---

Before and After
----------------

### Before Laravel 13.27

To safely decrement stock while preventing concurrent overselling, you had to re-query the model explicitly inside the transaction:

```php
public function purchase(Product $product): Response
{
    DB::transaction(function () use ($product) {
        $product = Product::query()
            ->lockForUpdate()
            ->findOrFail($product->getKey());

        if ($product->stock === 0) {
            throw new RuntimeException('The product is out of stock.');
        }

        $product->decrement('stock');
    });

    // ...
}

```

This works, but it requires you to reassign `$product` and remember to call `lockForUpdate()` every time.

### After Laravel 13.27

With `refreshForUpdate()` you call the method directly on the model instance:

```php
public function purchase(Product $product): Response
{
    DB::transaction(function () use ($product) {
        $product->refreshForUpdate();

        if ($product->stock === 0) {
            throw new RuntimeException('The product is out of stock.');
        }

        $product->decrement('stock');
    });

    // ...
}

```

The model is refreshed with the latest database values **and** locked for the duration of the transaction — in one readable line.

---

Key Takeaways
-------------

- `refreshForUpdate()` is available from **Laravel 13.27** onward.
- It combines `refresh()` and `lockForUpdate()` into a single Eloquent method call.
- The query is always routed to the **write PDO connection**, preventing stale reads from a replica.
- If the model does not exist (`$this->exists === false`), the method returns early without querying.
- It must be called **inside a `DB::transaction()` block**; a `FOR UPDATE` lock has no effect outside a transaction.
- Already-loaded Eloquent relations are refreshed automatically alongside the model's own attributes.

---

*Source: [Pessimistic Locking in Laravel Eloquent with refreshForUpdate() — Laravel News](https://laravel-news.com/laravel-refresh-for-update)*

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fpessimistic-locking-in-laravel-eloquent-with-refreshforupdate&text=Pessimistic+Locking+in+Laravel+Eloquent+with+refreshForUpdate%28%29) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fpessimistic-locking-in-laravel-eloquent-with-refreshforupdate) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does refreshForUpdate() work outside a database transaction?        No. The FOR UPDATE lock it applies is a database-level construct that only holds for the duration of an open transaction. Calling refreshForUpdate() outside a DB::transaction() block will still execute the query, but the lock will be released immediately and provide no concurrency protection. 

      Q02  Will refreshForUpdate() reload eager-loaded relationships?        Yes. It delegates to the same refreshUsingQuery() helper used by refresh(), which reloads any relations that were already loaded on the model instance before the call. 

      Q03  What happens if the model does not exist in the database?        If $this-&gt;exists is false, refreshForUpdate() returns the model instance immediately without issuing any query, so it is safe to call even on unsaved models. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel Starter Kits Now Ship with Vite+](https://cdn.msaied.com/604/ff70a112664fcb8b68719ab94a842145.png) Laravel Vite+ Starter Kits 

### Laravel Starter Kits Now Ship with Vite+

All Laravel starter kits now use Vite+, the unified toolchain that replaces ESLint and Prettier with Oxlint an...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-starter-kits-now-ship-with-vite) [ ![Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation](https://cdn.msaied.com/602/fcffaaa5442f84486d6059eaa4106d26.png) laravel queues reliability 

### Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation

Beyond basic queue workers: learn how to implement backpressure signals, dead-letter queues, and graceful degr...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-queues-at-scale-backpressure-dead-letter-queues-and-graceful-degradation) [ ![Mask Query Bindings in Laravel Exception Messages](https://cdn.msaied.com/603/3011313796d00cd5c4e1ead00e1e9ba1.png) Laravel Security QueryException 

### Mask Query Bindings in Laravel Exception Messages

Laravel 13.27 adds a per-connection option to prevent bound query values from appearing in QueryException mess...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 27 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/mask-query-bindings-in-laravel-exception-messages) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
