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 save you from replication lag bugs, and how to layer PgBouncer or ProxySQL connection pooling without breaking your app.

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

ShareCopy linkCopied

 ![Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel](https://cdn.msaied.com/232/957c64b845fcbcfced4036abb0409225.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 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
---------------------------------------------------------------------

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.

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

   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.

   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.

   ![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 articleLaravel Service Container: Contextual Binding, Tagging, and Method Injection](https://www.msaied.com/public/articles/laravel-service-container-contextual-binding-tagging-and-method-injection) [Next articleLaravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware](https://www.msaied.com/public/articles/laravel-pipeline-pattern-building-custom-pipelines-beyond-middleware)  

   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)

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