Laravel嵌套查询默认全走读库,导致刚写入主库的数据在子查询中不可见;需显式指定写连接,如用DB::connection('mysql_write')→table()手动执行子查询并转数组传入whereIn,或重写fromSub强制绑定写连接,或用原生SQL绕过路由。

在Laravel中执行嵌套查询(如子查询、withCount、whereHas嵌套条件)时,ORM默认将整个查询路由到读库,但若嵌套内层需写库数据(例如刚插入的临时表、未同步的主库最新状态),读写分离会返回陈旧结果。必须显式控制嵌套层级的连接来源。
确认嵌套查询触发读库的默认行为
运行 DB::enableQueryLog() 后执行 $users = User::whereHas('posts', function ($q) { $q->where('status', 1); })->get();,查看日志发现内外层均走 read 连接。这是因为 Laravel 的 query builder 在构建嵌套时未继承外层连接配置,而是使用默认 connection 配置。
这会导致刚在主库插入的 post 记录,在 whereHas 子查询中查不到——因为子查询发往从库,而从库延迟尚未同步。
方法一:用 DB::table() 手动指定写连接执行子查询
第一步:在子查询中显式调用 DB::connection('mysql_write')→table()
第二步:将子查询结果作为数组传入外层 whereIn,避免 Builder 自动路由
第三步:外层 Eloquent 查询仍走读库,保证主查询性能
示例代码:DB::connection('mysql_write')->table('posts')->where('user_id', DB::raw('users.id'))->where('status', 1)->pluck('user_id') → 转为数组后用于外层约束,【必须用 pluck + toArray,不能直接嵌套 Builder 对象】,否则 Laravel 仍会尝试自动路由该子 Builder 到读库。
方法二:重写 QueryBuilder 的 fromSub 方法强制绑定连接
创建 App/Database/Concerns/ForcesWriteConnection.php:
在子查询构造前调用 $sub->getConnection()->setName('mysql_write'),再传入 fromSub;此方式侵入性低,无需改业务代码,只需在子查询处加一行 $sub->useWriteConnection() 封装调用。
注意:Laravel 9+ 的 fromSub 内部会 clone 子查询实例,必须在 clone 前设置连接名,否则 clone 后的副本仍用默认连接。
方法三:用原生 SQL + DB::select() 绕过 Builder 路由逻辑
方法一:拼接完整 SQL 字符串,用 ? 占位符防止注入,DB::connection('mysql_write')->select($sql, $bindings)
方法二:用 DB::raw() 包裹子查询字符串,直接塞进 whereRaw 第二个参数的 bindings 数组
这一步操作起来很简单,直接把子查询写成字符串就行,但【所有 ? 占位符必须与 bindings 数组严格一一对应,错一位就会报 BindingResolutionException】。


















