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. [  What sticky Actually Does ](#what-codestickycode-actually-does)
4. [  Transactions Always Use the Write Connection ](#transactions-always-use-the-write-connection)
5. [  Connection Pooling: PgBouncer in Transaction Mode ](#connection-pooling-pgbouncer-in-transaction-mode)
6. [  Sticky Reads Under Laravel Octane ](#sticky-reads-under-laravel-octane)
7. [  Takeaways ](#takeaways)

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

  #laravel   #database   #postgresql   #performance  

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

     4 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   What sticky Actually Does  ](#what-codestickycode-actually-does)
4. [  04   Transactions Always Use the Write Connection  ](#transactions-always-use-the-write-connection)
5. [  05   Connection Pooling: PgBouncer in Transaction Mode  ](#connection-pooling-pgbouncer-in-transaction-mode)
6. [  06   Sticky Reads Under Laravel Octane  ](#sticky-reads-under-laravel-octane)
7. [  07   Takeaways  ](#takeaways)

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

Scaling a Laravel application past a single database node almost always means introducing a replica. Laravel's database layer has first-class support for this, but the details around *sticky reads*, connection pooling, and transaction safety trip up even experienced engineers. This article walks through each layer concretely.

---

### Configuring Read/Write Connections

Laravel accepts a `read` / `write` key inside any connection definition. Both keys accept an array of hosts, and the framework picks one at random per request.

```php
// config/database.php
'pgsql' => [
    'driver' => 'pgsql',
    'read' => [
        'host' => [
            env('DB_READ_HOST_1', '10.0.1.11'),
            env('DB_READ_HOST_2', '10.0.1.12'),
        ],
    ],
    'write' => [
        'host' => env('DB_WRITE_HOST', '10.0.1.10'),
    ],
    'sticky' => true,
    'database' => env('DB_DATABASE', 'app'),
    'username' => env('DB_USERNAME'),
    'password' => env('DB_PASSWORD'),
    'charset'  => 'utf8',
    'prefix'   => '',
    'schema'   => 'public',
],

```

Keys defined outside `read`/`write` are merged into both, so you only override what differs.

---

### What `sticky` Actually Does

When `sticky` is `true`, Laravel records whether the write connection was used during the current request. If it was, *all subsequent reads in that same request* are also routed to the write host.

This prevents the classic race condition:

1. User submits a form → `INSERT` on the primary.
2. Redirect → `SELECT` on a replica that hasn't replicated yet.
3. User sees a blank page or stale data.

The flag is stored on the `DatabaseManager` instance, which is a singleton per request lifecycle. Under Octane (shared worker), you must reset it yourself — more on that below.

```php
// Force a read from the write host explicitly when you need it
$user = DB::connection('pgsql')
    ->table('users')
    ->useWritePdo()
    ->find($id);

```

`useWritePdo()` is available on the query builder and bypasses the sticky logic entirely — useful in console commands where no write has occurred but you need fresh data.

---

### Transactions Always Use the Write Connection

Laravel automatically routes all queries inside `DB::transaction()` to the write PDO. You don't need to think about this, but it's worth knowing so you don't add `useWritePdo()` calls inside transactions unnecessarily.

```php
DB::transaction(function () use ($dto) {
    $order = Order::create($dto->toArray());
    // This SELECT also hits the write host — correct behaviour
    $inventory = Inventory::lockForUpdate()->find($dto->productId);
    $inventory->decrement('stock', $dto->quantity);
});

```

---

### Connection Pooling: PgBouncer in Transaction Mode

PHP opens a new PDO connection per request. At scale this exhausts PostgreSQL's `max_connections`. PgBouncer in **transaction mode** multiplexes many PHP connections onto a small pool.

**Gotcha:** PgBouncer transaction mode breaks prepared statements and `SET` session variables. Laravel uses PDO prepared statements by default.

Disable them per connection:

```php
'options' => [
    PDO::ATTR_EMULATE_PREPARES => true,
],

```

With `ATTR_EMULATE_PREPARES`, PDO sends plain SQL strings rather than server-side prepared statements, which is safe through a transaction-mode pooler.

For MySQL with ProxySQL, the same principle applies: ProxySQL rewrites queries to route `SELECT` to replicas and writes to the primary, but Laravel's own read/write config and ProxySQL can conflict. Pick one layer to own routing — usually ProxySQL at scale, with Laravel's `read`/`write` disabled (single `host`).

---

### Sticky Reads Under Laravel Octane

Octane reuses the same application container across requests. The `DatabaseManager` singleton retains the `$recordsModified` flag between requests, meaning a write in request N can cause request N+1 to read from the primary unnecessarily.

Register a terminating callback to reset it:

```php
// In a service provider
public function boot(): void
{
    if (app()->bound('octane')) {
        app('events')->listen(
            \Laravel\Octane\Events\RequestTerminated::class,
            function () {
                app('db')->forgetRecordModificationState();
            }
        );
    }
}

```

`forgetRecordModificationState()` was added in Laravel 10. On earlier versions, resolve the `DatabaseManager` and call `recordsHaveBeenModified(false)` directly.

---

### Takeaways

- `sticky => true` prevents post-write stale reads within a single request; it is not a global setting.
- `useWritePdo()` on the query builder gives explicit control without relying on the sticky flag.
- Transactions always use the write PDO — no extra configuration needed.
- PgBouncer transaction mode requires `PDO::ATTR_EMULATE_PREPARES => true` to avoid prepared-statement errors.
- Under Octane, reset `recordsHaveBeenModified` on each `RequestTerminated` event to avoid sticky-read bleed between requests.
- Let one layer own replica routing: either Laravel's config or a proxy like ProxySQL, not both.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Freadwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-3&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-3) 

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

  3 questions  

     Q01  Does Laravel's sticky option affect queue workers?        No. Each job is a fresh dispatch with its own request lifecycle. The sticky flag resets between jobs, so replicas are used for reads unless a write occurs within the same job execution. 

      Q02  Can I use PgBouncer session mode instead of transaction mode to avoid the prepared-statement issue?        Yes. Session mode assigns a server connection for the full client session, so prepared statements work normally. The trade-off is lower multiplexing efficiency — you need more server connections, which partially defeats the purpose of pooling at high concurrency. 

      Q03  How do I verify which PDO connection a query actually used?        Enable the query log with DB::enableQueryLog() and inspect DB::getQueryLog(). For deeper inspection, use Laravel Telescope or Debugbar — both show which connection (read vs write) executed each query. 

  Continue reading

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

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

 [ ![Laravel Service Container: Contextual Binding, Tagging, and Method Injection](https://cdn.msaied.com/357/6ee56a71454e019ced2bd70790e9ca60.png) laravel service-container dependency-injection 

### Laravel Service Container: Contextual Binding, Tagging, and Method Injection

Go beyond basic singleton registration. Learn how contextual binding, container tags, and method injection let...

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

 4 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-service-container-contextual-binding-tagging-and-method-injection-2) [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Schema-less Columns Done Right](https://cdn.msaied.com/356/592e07bf3b46443cb950d9bb31725ba6.png) laravel postgresql eloquent 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Schema-less Columns Done Right

JSONB columns can replace entire pivot tables or EAV nightmares — if you index them correctly and query throug...

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

 3 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-schema-less-columns-done-right) [ ![Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel](https://cdn.msaied.com/355/3a6df23a2c16b740843260134fad7c63.png) laravel eloquent performance 

### Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel

Offset pagination breaks under large datasets. Learn how cursor pagination, chunked iteration, and lazy collec...

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

 3 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/cursor-pagination-chunked-iteration-and-lazy-collections-at-scale-in-laravel-1) 

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