laravel读写分离需在config/database.php中将read和write作为子键嵌套于同一连接(如mysql)内,纯select走从库,事务、显式连接或eloquent默认全走主库;read必须为数组格式,write必须为单元素关联数组,共用配置须置于外层,sticky=true可防主从延迟导致的脏读。

DB::connection() 默认永远走 default 连接,而读写分离不是靠“自动识别 SQL 类型”触发的,是靠你配对了 read 和 write 子键后,框架在查询构造器(如 DB::table()->get())内部做路由决策——纯读操作才可能走从库;一旦进事务、显式指定连接、或用 Eloquent 但没干预,默认全主库。
config/database.php 里 mysql 连接必须嵌套 read/write
不能只加两个独立连接(比如 mysql_master 和 mysql_slave),那只是手动切换,不叫读写分离。Laravel 的自动路由逻辑只认一个连接名下同时存在 read 和 write 键。
-
read必须是数组,哪怕只有一个从库也要写成'read' => [['host' => '192.168.1.10']],不能是'read' => ['host' => '...'] -
write必须是关联数组,且只能有一个节点:'write' => ['host' => '192.168.1.5'],不能是['192.168.1.5'] - 共用配置(
database、username、password、charset等)必须提到外层,写在read/write内部会被忽略 - 从库建议设
'strict' => false,避免因 MySQL 版本或 SQL mode 差异报错
DB::transaction() 里所有查询强制走主库
这是最常被误踩的坑:只要进了 DB::transaction() 或手动 beginTransaction(),后续所有查询——包括 DB::table('users')->where('id', 1)->first()——都会无视是否是 SELECT,一律发到 write 连接。
- 原因不是 Laravel 故意限制,而是事务要求 ACID,读必须和写在同一个连接上才能保证一致性
- 试图在事务里调
on('mysql_read')会直接抛出Cannot execute queries while other uncommitted transactions are active - 如果只是“先查再写”,且查的数据不参与事务逻辑(比如查配置、查用户权限),就把
select提到事务外 - 别为了省一次连接硬塞进事务,反而让从库完全空转
Eloquent 默认不走读库,必须主动干预
User::all()、User::find(1) 这些看似是读操作,但 Eloquent 模型默认把整个连接当成可写连接处理,所以仍走主库。
- 最稳妥的是在模型里加
protected $connection = 'mysql_read';,并单独定义一个mysql_read连接(指向从库)——但这绕过了 Laravel 原生读写路由,得自己管故障转移 - 更推荐用
User::on('mysql')->where(...)->get(),前提是你的mysql连接已正确定义read/write - 注意:
withTrashed()、with()预加载会重置连接,可能意外切回主库;on()不继承模型的$casts或访问器
sticky=true 是防主从延迟的关键开关
默认 sticky 是 false,开了才能解决“刚写完立刻查不到”的问题。它不是性能优化项,是数据一致性兜底机制。
- 开启后,当前请求中只要执行过一次写(
insert、update、delete),后续所有读都会强制走主库 - 只作用于单次请求生命周期,不影响其他请求
- 如果你的业务能容忍几秒延迟(比如后台报表页),可以关掉来压测从库吞吐,但前台用户场景强烈建议开
- 别指望靠缓存或重试掩盖主从延迟——
sticky是第一道防线











