laravel读写分离需在config/database.php中同一连接名下嵌套read(数组格式)和write(单元素关联数组),启用sticky=true防主从延迟,事务内所有操作强制走主库。

要在 Laravel 环境中真正落地数据库读写分离,光配对主从地址远远不够。核心在于两件事:一是让框架按规则自动分发读/写请求,二是避免因主从延迟或事务逻辑引发数据不一致。配置本身不复杂,但细节错一点,从库就形同虚设。
主从连接必须嵌套在同一个连接名下
Laravel 的读写路由机制只认一种结构:在 config/database.php 的某个连接(如 mysql)内部,同时存在 read 和 write 子数组。这不是添加两个独立连接(比如 mysql_master 和 mysql_slave),而是把它们收进一个逻辑连接里。
-
read必须是数组,哪怕只有一个从库也要写成[['host' => '192.168.1.10']] -
write必须是单元素关联数组,如['host' => '192.168.1.5'],不能是索引数组 - 共用配置(
database、username、password、charset等)要提至外层,写在read或write内部会被忽略 -
driver必须显式声明在外层,且保持为mysql
sticky 是防主从延迟的关键开关
默认 sticky => false,意味着一次请求中先执行 User::create(),紧接着 User::find(1) 可能落到还没同步完的从库,查不到刚写入的数据——这不是 bug,是预期行为。
- 设为
sticky => true后,只要当前请求周期内发生过任何写操作(insert、update、save、DB::statement('INSERT')),后续所有读操作都会强制走主库 - 这个标记在事务结束后仍保留,属于设计而非缺陷,但需注意它可能影响后续非关键读请求
- 它不是性能优化项,而是数据一致性兜底机制,生产环境建议始终开启
事务与 Eloquent 的行为陷阱
很多问题其实不出在配置,而出在对 Laravel 路由逻辑的误解。
- 一旦进入
DB::transaction()或手动调用beginTransaction(),所有查询——包括DB::table('users')->first()——一律走write连接,无法绕过 - Eloquent 模型默认不走从库:
User::all()、User::find(1)都会命中主库;想让它走读库,得用User::on('mysql')->get()(前提是mysql已正确定义read/write) -
with()预加载和withTrashed()会重置连接上下文,可能导致意外切回主库 - 若只是“查配置 + 写日志”,应把查配置提前到事务外,别为了省一次连接硬塞进去
从库负载均衡与连接池配合
多个从库不是摆设,Laravel 支持按权重轮询分发读请求,但需配合连接池策略才能发挥实效。
- 在
read数组中为每个节点添加'weight' => 3等整数值,Laravel 运行时按总和随机选取,无需额外代码 - 从库建议设
'strict' => false,避免因 MySQL 版本或 SQL mode 差异导致连接失败 - 连接池层面,可结合 PDO 的
PDO::ATTR_PERSISTENT或应用层连接复用(如 Laravel Octane 的 Swoole 连接池),减少握手开销 - 验证是否生效:在
AppServiceProvider@boot中启用查询日志,执行DB::table('users')->first()后检查日志中 host 地址是否来自从库列表











