Laravel Read/Write Splitting &amp; Sticky Reads | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel

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

 Learn how Laravel's database layer handles read/write splitting, when sticky reads matter, and how to pair connection pooling with PgBouncer or ProxySQL without introducing subtle data-consistency bugs.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 13 Jul 2026 · Updated 13 Jul 2026 · 4 min read

ShareCopy linkCopied

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

  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
---------------------------------------------------------------------

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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [database](https://www.msaied.com/public/articles?search=database)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [postgresql](https://www.msaied.com/public/articles?search=postgresql)
- [mysql](https://www.msaied.com/public/articles?search=mysql)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleService Container Deep Dive: Contextual Binding, Tagging, and Method Injection](https://www.msaied.com/public/articles/service-container-deep-dive-contextual-binding-tagging-and-method-injection) [Next articleUvora: A macOS Menu Bar App for Finding Laravel Projects Instantly](https://www.msaied.com/public/articles/uvora-a-macos-menu-bar-app-for-finding-laravel-projects-instantly)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
