Laravel Read/Write Splitting &amp; Sticky Reads | 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)    Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel        On this page       1. [  Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel ](#readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel)
2. [  Configuring Read/Write Connections ](#configuring-readwrite-connections)
3. [  The sticky Option and Why It Exists ](#the-codestickycode-option-and-why-it-exists)
4. [  Connection Pooling with PgBouncer ](#connection-pooling-with-pgbouncer)
5. [  Handling Replication Lag in Jobs ](#handling-replication-lag-in-jobs)
6. [  Takeaways ](#takeaways)

  ![Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel](https://cdn.msaied.com/418/4c0c54c81ac02b35d192ac992080bc8b.png)

  #laravel   #database   #performance   #postgresql   #mysql  

 Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel 
=======================================================================

     13 Jul 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel  ](#readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel)
2. [  02   Configuring Read/Write Connections  ](#configuring-readwrite-connections)
3. [  03   The sticky Option and Why It Exists  ](#the-codestickycode-option-and-why-it-exists)
4. [  04   Connection Pooling with PgBouncer  ](#connection-pooling-with-pgbouncer)
5. [  05   Handling Replication Lag in Jobs  ](#handling-replication-lag-in-jobs)
6. [  06   Takeaways  ](#takeaways)

 Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel
---------------------------------------------------------------------

Scaling a Laravel application's database layer is rarely about raw query optimisation alone. Once you add a read replica, you immediately inherit a class of subtle bugs caused by replication lag. Laravel ships with first-class support for read/write connections, but the defaults can surprise you in production.

### Configuring Read/Write Connections

Laravel's `config/database.php` accepts a `read` and `write` key inside any connection definition. Both accept an array of hosts, and Laravel picks one at random per request.

```php
'mysql' => [
    'driver' => 'mysql',
    'read' => [
        'host' => [
            env('DB_READ_HOST_1', '10.0.1.10'),
            env('DB_READ_HOST_2', '10.0.1.11'),
        ],
    ],
    'write' => [
        'host' => [env('DB_WRITE_HOST', '10.0.1.1')],
    ],
    'sticky' => true,
    'database' => env('DB_DATABASE', 'app'),
    'username' => env('DB_USERNAME'),
    'password' => env('DB_PASSWORD'),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
],

```

Every `SELECT` goes to a random read host; every `INSERT`, `UPDATE`, `DELETE`, and DDL goes to the write host.

### The `sticky` Option and Why It Exists

Without `sticky`, a user who just submitted a form might immediately read their own write from a replica that hasn't caught up yet — a classic "I just saved this, where did it go?" bug.

When `sticky => true`, Laravel records whether the write connection was used during the current request. If it was, all subsequent reads in that same request are routed to the write host instead of a replica.

```php
// Internally, Connection::$recordsModified drives this.
// After any write, Laravel sets it to true and useReadPdo() returns false.

```

This is a per-request flag, so it resets on the next HTTP request. It is not a session-level concept — long-running queue workers or CLI commands do not benefit from it automatically.

**Forcing a read from the write host explicitly:**

```php
$user = DB::connection('mysql::write')
    ->table('users')
    ->find($id);

// Or via Eloquent:
$user = User::on('mysql::write')->find($id);

```

Use this sparingly — it defeats the purpose of replicas — but it is the right tool after a critical write in a job.

### Connection Pooling with PgBouncer

Laravel opens a new PDO connection per worker process. Under Octane or high-concurrency FPM, this means hundreds of connections hitting your database simultaneously. PgBouncer (PostgreSQL) and ProxySQL (MySQL) sit in front of the database and multiplex many application connections onto a smaller pool.

**PgBouncer in transaction-mode** is the most efficient but breaks `SET` statements, advisory locks, and prepared statements. Configure Laravel to disable server-side prepared statements:

```php
'pgsql' => [
    'driver' => 'pgsql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '6432'), // PgBouncer port
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true, // avoids named prepared statements
    ],
    // ...
],

```

Alternatively, run PgBouncer in **session mode** if you rely on advisory locks or `LISTEN/NOTIFY` — you lose some multiplexing efficiency but retain full feature compatibility.

### Handling Replication Lag in Jobs

Queue workers are long-lived processes. `sticky` does not help them. After a job writes data and then reads it back, it may hit a stale replica.

```php
class ProcessOrderJob implements ShouldQueue
{
    public function handle(): void
    {
        // Write
        $order = Order::create([...]);

        // Explicitly read from write host to avoid lag
        $fresh = Order::on('mysql::write')->find($order->id);

        // Or simply use the model already in memory:
        $fresh = $order->refresh(); // still hits read by default!
        // Better:
        DB::connection()->recordsModified(); // check flag — not reliable in jobs
    }
}

```

The cleanest pattern: pass the already-hydrated model or its ID into subsequent steps rather than re-querying immediately after a write.

### Takeaways

- Enable `sticky => true` to prevent users from reading stale data after their own writes within the same HTTP request.
- Use `DB::connection('mysql::write')` explicitly in jobs and CLI commands where `sticky` has no effect.
- Disable PDO prepared statements (`ATTR_EMULATE_PREPARES`) when routing through PgBouncer in transaction mode.
- Prefer session-mode PgBouncer if your application uses advisory locks, `LISTEN/NOTIFY`, or `pg_temp` schemas.
- Avoid re-querying immediately after a write in queue workers; pass hydrated models or use the write connection explicitly.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Freadwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-4&text=Read%2FWrite+Splitting%2C+Connection+Pooling%2C+and+Sticky+Reads+in+Laravel) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Freadwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-4) 

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

  3 questions  

     Q01  Does Laravel's `sticky` option work inside queue jobs?        `sticky` is a per-request, in-memory flag on the Connection object. Queue workers are long-lived processes that handle many jobs sequentially, so the flag is not reset between jobs reliably. Always use `DB::connection('mysql::write')` or pass already-loaded models when you need to read immediately after a write inside a job. 

      Q02  Why do prepared statements break with PgBouncer in transaction mode?        PgBouncer in transaction mode can route consecutive transactions to different backend connections. Named prepared statements are tied to a specific backend connection, so a statement prepared on connection A is not available on connection B. Setting `PDO::ATTR_EMULATE_PREPARES =&gt; true` makes PDO interpolate parameters client-side, avoiding server-side named statements entirely. 

      Q03  How do I verify which connection (read or write) a query actually used?        Enable the query log with `DB::enableQueryLog()` and inspect `DB::getQueryLog()`. For deeper inspection, attach a `DB::listen()` listener that logs the connection name alongside the SQL. In Telescope or Debugbar, the connection name is shown per query in the database tab. 

  Continue reading

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

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

 [ ![New in Laravel 13: First-Party Image Manipulation](https://cdn.msaied.com/427/504aed7f9f1c4a1cd411c7c4a4b6bc7c.png) Laravel 13 Image Manipulation PHP 

### New in Laravel 13: First-Party Image Manipulation

Laravel 13 introduces a first-party image manipulation feature, removing the need for third-party packages for...

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

 15 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/new-in-laravel-13-first-party-image-manipulation) [ ![Filament v4 Testing with Pest: Resources, Actions, and Form Assertions](https://cdn.msaied.com/426/a81430a378b2849b6fbecb8235b9119f.png) filament pest testing 

### Filament v4 Testing with Pest: Resources, Actions, and Form Assertions

A practical guide to testing Filament v4 panels using Pest — covering resource page assertions, table action d...

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

 15 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/filament-v4-testing-with-pest-resources-actions-and-form-assertions) [ ![Laravel Legacy Bridge: Carry Authenticated Sessions from a Legacy PHP App into Laravel](https://cdn.msaied.com/429/5abced4ee695d6c03e3ae41bbf2fe4bb.png) Laravel PHP Session Management 

### Laravel Legacy Bridge: Carry Authenticated Sessions from a Legacy PHP App into Laravel

Laravel Legacy Bridge is a package that reads a legacy session cookie, decodes the payload from the legacy dat...

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

 15 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-legacy-bridge-carry-authenticated-sessions-from-a-legacy-php-app-into-laravel) 

   [  ![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)
