Laravel读写分离默认不保证强一致性,DB::select()等读操作直接走从库,即使主库刚写入;仅显式指定主库连接、事务中读、或含FOR UPDATE的查询才会强制走主库。

MySQL主从延迟时,DB::select() 为什么还是读到旧数据?
因为 Laravel 的读写分离默认不感知主从延迟,DB::select() 这类读操作直接走从库连接池,哪怕主库刚写入,从库还没同步完,它也照读不误。
这不是 Bug,是设计使然:Laravel 的 read/write 配置只做路由,不做一致性保障。真实场景中,用户注册后立刻查个人资料却 404,大概率就是这个原因。
- 只要没显式指定连接(如
DB::connection('mysql-write')),所有select、get、first都走read配置的从库 - 事务内执行的读操作会自动切到主库 —— 但仅限于「当前连接处于活跃事务中」,不是事务开始后所有后续读都绑定主库
-
DB::table()->where()->update()这种写操作强制走主库,但紧跟着的->first()若不在同一事务里,仍可能读从库
Laravel 中哪些操作能强制走主库?
只有三类明确信号会让 Laravel 把读请求发到主库:显式指定连接名、当前存在未提交事务、使用 DB::unprepared() 执行含写语义的查询(如 SELECT ... FOR UPDATE)。
注意:DB::transaction() 回调里的读操作确实走主库,但一旦事务 commit 或 rollback,下一次读立刻回归从库 —— 它不“粘性”,也不跨请求持久。
- ✅ 正确强制主库读:
DB::connection('mysql-write')->table('users')->where('id', 1)->first() - ✅ 事务内读:
DB::transaction(function () { return User::find(1); }); - ❌ 错误以为有效:
DB::beginTransaction(); User::find(1); DB::commit();——find()发生在事务开启后,但 Laravel 不会自动把该连接后续所有读都锁在主库 - ⚠️ 隐式主库读:
DB::select('SELECT * FROM users WHERE id = ? FOR UPDATE', [1]),因FOR UPDATE被识别为写意图
如何让「刚写完就查」自动走主库?
靠配置做不到,得加一层轻量路由逻辑。核心思路是:在写操作后的一小段时间窗口(比如 500ms)内,对同一数据的读请求临时升主库。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
推荐用缓存键标记「刚被修改的记录」,读之前检查是否存在该标记,存在则强制走主库。不依赖全局延迟检测,精准、低开销、无侵入。
- 写后打标:
Cache::put('user_updated_123', true, 60)(60 秒过期,按业务容忍延迟调整) - 读前判断:
if (Cache::has('user_updated_123')) { return DB::connection('mysql-write')->table('users')->find(123); } - 不要用数据库时间戳比对:从库时间可能不准,且增加一次额外查询
- 避免全表扫描式补偿:比如「所有用户资料读都升主库」—— 直接废掉读写分离价值
为什么不用 DB::reconnect() 或手动切换连接?
因为 DB::reconnect() 只重连当前连接,不改变连接归属(仍是读连接池里的某个从库实例);而手动 DB::disconnect() + DB::reconnect('mysql-write') 在 Laravel 9+ 会破坏连接池复用,引发连接数暴涨,尤其在高并发下容易打满 MySQL max_connections。
更隐蔽的问题是:Laravel 的连接是 request-scoped 的,你在中间件或 service 里改了连接,未必能影响到 controller 里后续的 Eloquent 调用 —— 模型静态方法用的是默认连接,不继承上下文。
- ❌
DB::reconnect()对读写分离路由无效 - ❌
DB::disconnect(); DB::reconnect('mysql-write')破坏连接池,生产环境慎用 - ✅ 唯一安全方式:显式传入连接名,或用
DB::connection('mysql-write')构造新查询实例 - ⚠️ Eloquent 模型可通过
protected $connection = 'mysql-write';绑定,但这是模型级硬编码,不适合动态场景
主从延迟补偿没有银弹。最可靠的永远是「写后读」场景里,主动控制读位置;而试图全局拦截所有 select 并智能路由,反而容易在边缘 case 里漏判或误判。业务代码里多一行 ->useWritePdo() 或一个缓存键,比堆一堆延迟探测逻辑更稳。










