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 manager handles read/write splitting, when sticky reads matter, and how to layer PgBouncer or ProxySQL without breaking your application's consistency guarantees.

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

ShareCopy linkCopied

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

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.

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

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

  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.

   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.

   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.

   ![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-2) [Next articleThe Pipeline Pattern in Laravel: Building Custom Pipelines Beyond Middleware](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-building-custom-pipelines-beyond-middleware-1)  

   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)

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