laravel读写分离必须在config/database.php中将read和write嵌套于同一连接(如mysql)内,而非新增独立连接;read为数组(支持多从库轮询),write为单元素关联数组,共用配置须置于外层,sticky=true可防主从延迟导致的脏读。

DB::connection() 不能自动实现读写分离,Laravel 的读写分离必须显式配置在 config/database.php 的单个连接定义内,不是靠多个独立连接名(如 mysql_read / mysql_write)切换实现的。
read/write 子数组必须嵌套在同一个连接配置里
Laravel 的读写分离机制依赖于一个连接名(比如 mysql)内部的 read 和 write 两个子键,而不是新增两个连接名。你不能指望 DB::connection('mysql_read')->table(...) 这种写法生效——它压根不被框架识别为“读连接”。
- 正确结构是在
connections['mysql']下直接定义'read' => [...]和'write' => [...],两者都必须是完整连接配置(含host、database、username等) -
read数组支持多个 host,会自动轮询;write只允许一个 host(主库唯一) - 所有
DB::connection('mysql')→...查询,只要没显式调用useWritePdo(),默认走read配置;insert/update/delete等写操作会自动 fallback 到write - 如果只配了
read没配write,写操作会报错:SQLSTATE[HY000]: General error: 2013 Lost connection to MySQL server during query
模型查不到从库?检查是否用了 on() 或硬编码 connection
一旦启用了读写分离,Eloquent 默认仍走 mysql 连接名,但底层会按读/写语义自动分发到对应服务器。如果你在模型里写了 protected $connection = 'mysql',它没问题;但若用了 User::on('mysql'),也仍然有效——因为 on() 绑定的是连接名,不是物理实例。
- 真正导致“查不到从库”的常见原因是:在查询中混用了
DB::connection('mysql')->useWritePdo()->table(...),强制走写连接,绕过了读负载 - 或者模型里写了
$connection = 'mysql_slave'这类不存在的连接名,结果 fallback 到默认连接,而默认连接又没配read/write,就全走主库 -
DB::raw()不影响连接选择,它只是 SQL 字符串,执行库由外层查询决定
环境变量必须按 read/write 分组映射
你不能让 read 和 write 共享同一套 DB_HOST。Laravel 要求它们各自有独立的环境变量前缀,否则配置加载时会覆盖或缺失。
- 推荐在
.env中这样定义:DB_READ_HOST=10.0.1.10、DB_READ_DATABASE=main_db、DB_WRITE_HOST=10.0.1.5、DB_WRITE_DATABASE=main_db - 然后在
config/database.php中对应写:'read' => ['host' => env('DB_READ_HOST'), 'database' => env('DB_READ_DATABASE'), ...] - 漏掉任何一个字段(比如
port或username),该侧连接就会 fallback 到env()默认值,极可能连错库或认证失败 - 改完
.env后必须运行php artisan config:clear,否则缓存中的旧配置仍生效
事务和 Observer 会破坏读写分离逻辑
在事务中,所有查询(包括 select)都会强制走写连接,这是 Laravel 的硬性规则,无法绕过。Observer 里的关联查询也不会自动继承当前连接上下文。
-
DB::transaction(function () { User::find(1); Order::where('user_id', 1)->get(); });—— 即使Order是只读查询,也会走写库 - 如果模型
User的creatingObserver 里调用了Log::create(...),而Log模型绑定了$connection = 'mysql_log',那这次写日志仍走自己的连接,和当前事务无关 - 跨连接事务不被支持,所以不要在事务里混用
DB::connection('mysql')和DB::connection('pgsql_reporting')
read/write 字段缺失、环境变量映射错位、或误用 on() 时失效。最稳妥的做法是:先确认 config('database.connections.mysql.read') 在 tinker 中能输出完整数组,再测一条简单 DB::table('users')->first() 是否真走从库(看 MySQL slow log 或 SHOW PROCESSLIST)。











