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 Read Problem ](#the-sticky-read-problem)
4. [  Forcing a Connection Explicitly ](#forcing-a-connection-explicitly)
5. [  Connection Pooling with PgBouncer ](#connection-pooling-with-pgbouncer)
6. [  Transactions Always Use the Write Connection ](#transactions-always-use-the-write-connection)
7. [  Key Takeaways ](#key-takeaways)

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

  #laravel   #database   #performance   #postgresql   #mysql  

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

     18 Jun 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 Read Problem  ](#the-sticky-read-problem)
4. [  04   Forcing a Connection Explicitly  ](#forcing-a-connection-explicitly)
5. [  05   Connection Pooling with PgBouncer  ](#connection-pooling-with-pgbouncer)
6. [  06   Transactions Always Use the Write Connection  ](#transactions-always-use-the-write-connection)
7. [  07   Key Takeaways  ](#key-takeaways)

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

Laravel ships with first-class support for read/write connection splitting, but the defaults hide several sharp edges that only surface under replication lag or high-concurrency workloads. This article walks through the full picture: configuration, sticky-read semantics, and layering a connection pooler without breaking transactions.

---

### Configuring Read/Write Connections

In `config/database.php`, any connection can declare `read` and `write` keys. Laravel merges the top-level keys into both, so you only override what differs:

```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_HOST', '10.0.1.10'),
    ],
    'sticky' => true,
    'database' => env('DB_DATABASE', 'app'),
    'username' => env('DB_USERNAME', 'app'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => 'utf8',
    'prefix' => '',
    'schema' => 'public',
],

```

When multiple hosts are listed under `read`, Laravel picks one at random per request — a simple client-side load balancer. All `SELECT` statements route to a read host; everything else goes to `write`.

---

### The Sticky Read Problem

Without `'sticky' => true`, a write followed immediately by a read in the same request can return stale data because the read hits a replica that hasn't caught up yet. The `sticky` flag tells Laravel: *if this request has already used the write connection, keep using it for reads too*.

```php
// Without sticky: this can return the pre-update row
$user = User::find(1);
$user->update(['name' => 'Alice']);
$fresh = User::find(1); // may hit replica — stale!

// With sticky => true: the second query also uses the write connection

```

Sticky reads are scoped to the **current request lifecycle**. They do not persist across requests, so read replicas still get the bulk of your SELECT traffic.

**When to disable sticky:** Long-running queue workers that perform a write and then immediately read back the result should either use `DB::connection('write')` explicitly or rely on sticky. Disabling sticky globally to squeeze more replica usage is a common source of subtle race-condition bugs.

---

### Forcing a Connection Explicitly

For critical reads after a write — think payment confirmation pages — be explicit:

```php
$order = DB::connection('pgsql::write')
    ->table('orders')
    ->where('id', $orderId)
    ->first();

```

Or use the Eloquent `onWriteConnection()` method available on query builders:

```php
$order = Order::onWriteConnection()->find($orderId);

```

This is clearer than relying on sticky and documents intent at the call site.

---

### Connection Pooling with PgBouncer

Laravel opens a new PDO connection per worker process. Under Octane or high-concurrency FPM, this can exhaust PostgreSQL's `max_connections` quickly. PgBouncer in **transaction mode** sits between Laravel and Postgres and multiplexes connections.

**Critical caveat:** PgBouncer transaction mode breaks anything that relies on session-level state — `SET LOCAL`, advisory locks, `LISTEN/NOTIFY`, and prepared statements. Disable prepared statements in Laravel:

```php
// config/database.php
'options' => [
    PDO::ATTR_EMULATE_PREPARES => true,
],

```

With `ATTR_EMULATE_PREPARES`, PDO sends plain SQL rather than using the PostgreSQL wire-protocol prepare/execute cycle, which is incompatible with PgBouncer transaction mode.

For MySQL, ProxySQL fills the same role. It also supports query routing rules, so you can push `SELECT` statements to replicas at the proxy layer rather than relying on Laravel's config — useful when you want to centralise routing logic outside the application.

---

### Transactions Always Use the Write Connection

Laravel automatically routes all queries inside `DB::transaction()` to the write connection, regardless of the sticky flag. This is correct behavior — never wrap a read-replica query in a transaction expecting it to stay on the replica.

```php
DB::transaction(function () {
    // Both queries hit the write connection
    $balance = Account::lockForUpdate()->find(42);
    $balance->decrement('amount', 100);
});

```

---

### Key Takeaways

- Enable `sticky => true` in production to avoid replication-lag read-after-write bugs.
- Use `onWriteConnection()` or `DB::connection('pgsql::write')` when you need a guaranteed fresh read.
- PgBouncer transaction mode requires `PDO::ATTR_EMULATE_PREPARES => true` — missing this causes cryptic errors.
- Laravel always routes transactions to the write connection; don't fight it.
- ProxySQL can centralise read/write routing at the infrastructure layer, reducing per-app config drift across services.

 Found this useful?

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

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

  3 questions  

     Q01  Does Laravel's sticky option affect queue workers?        Yes. If a queue worker processes a job that writes and then reads within the same job execution, sticky will route that read to the write connection. For long-lived workers this is usually desirable, but you can call DB::purge() between jobs to reset connection state if needed. 

      Q02  Can I use PgBouncer session mode instead of transaction mode to avoid the prepared-statement issue?        Session mode preserves session state and works with prepared statements, but it provides far less connection multiplexing benefit because a backend connection is held for the entire client session. For most Laravel workloads, transaction mode with emulated prepares is the right trade-off. 

      Q03  How do I verify which connection a query is actually using?        Enable the query log with DB::enableQueryLog() and inspect DB::getQueryLog(), or listen to the Illuminate\Database\Events\QueryExecuted event, which exposes the connection name. You can also log DB::getDefaultReadConnection() and DB::getDefaultWriteConnection() to confirm routing. 

  Continue reading

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

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

 [ ![PostgreSQL CTEs, Recursive Queries, and Lateral Joins in Laravel](https://cdn.msaied.com/241/32858f9c67eae0649999c32a6d31818f.png) laravel postgresql query-builder 

### PostgreSQL CTEs, Recursive Queries, and Lateral Joins in Laravel

Go beyond basic Eloquent with raw PostgreSQL power: composable CTEs, recursive tree traversal, and LATERAL joi...

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

 19 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/postgresql-ctes-recursive-queries-and-lateral-joins-in-laravel) [ ![PostgreSQL Window Functions in Laravel: Ranking, Running Totals, and Gap Detection](https://cdn.msaied.com/239/f588e7cbf8e6d3317a581ce0fa27140d.png) laravel postgresql eloquent 

### PostgreSQL Window Functions in Laravel: Ranking, Running Totals, and Gap Detection

Window functions let you compute rankings, running totals, and gaps directly in SQL without pulling rows into...

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

 19 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/postgresql-window-functions-in-laravel-ranking-running-totals-and-gap-detection) [ ![Custom Eloquent Casts: Encapsulating Domain Logic Inside Model Attributes](https://cdn.msaied.com/238/8e843e57a34f81f853eedefae629c09b.png) laravel eloquent domain-driven-design 

### Custom Eloquent Casts: Encapsulating Domain Logic Inside Model Attributes

Custom Eloquent casts let you push value-object logic directly into model attributes, keeping controllers and...

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

 19 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/custom-eloquent-casts-encapsulating-domain-logic-inside-model-attributes) 

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