Laravel主从读写分离需在database.php中将read/write配置为同一连接名的子键,读操作自动走从库仅限纯SELECT且未显式指定连接、非事务内;写操作、事务、显式连接等一律走主库。

Laravel 原生支持主从读写分离,但不是“开箱即用”的自动路由,必须显式配置连接、指定读写意图,且需注意查询构造器与 Eloquent 的行为差异——否则所有查询仍走主库。
如何配置 database.php 中的主从连接组
Laravel 不是靠“添加两个独立连接”实现读写分离,而是把多个从库(read)和一个主库(write)归入同一个逻辑连接名下。关键在 'connections' 数组中使用 'read' 和 'write' 子键:
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::MYSQL_ATTR_SSL_CERT => env('MYSQL_ATTR_SSL_CERT'),
PDO::MYSQL_ATTR_SSL_KEY => env('MYSQL_ATTR_SSL_KEY'),
]) : [],
'read' => [
'host' => [
env('DB_READ_HOST_1', '192.168.1.10'),
env('DB_READ_HOST_2', '192.168.1.11'),
],
],
'write' => [
'host' => env('DB_WRITE_HOST', '192.168.1.5'),
],
],
-
read.host是数组,Laravel 会轮询选择其中一个从库(非负载均衡,无健康检查) -
write.host必须是单个字符串,不能是数组;写操作只发往此处 - 所有其他连接参数(如
username、password、database)若未在read/write内单独覆盖,则继承外层配置 - 如果从库需要不同账号,必须在
read下显式写出'username'和'password'
哪些查询会自动走从库?哪些不会?
只有「纯读操作」且未显式指定连接时,Laravel 才可能路由到从库。但以下情况一律走主库:
- 任何含
insert()、update()、delete()、increment()等写方法的查询构造器调用 - Eloquent 模型调用
save()、create()、delete()、update() - 事务内所有查询(即使只是
select),因为事务必须在单一连接上执行 - 使用
DB::connection('mysql')显式获取连接后,再调用table()->get()—— 此时 Laravel 无法推断意图,强制走主库 -
DB::select()、DB::statement()等原生方法不参与读写分离,始终走默认连接(即主库)
真正能触发从库路由的是:DB::table('users')->where('id', 1)->first() 或 User::find(1) 这类未包裹在事务、未显式绑定连接、且语法明确为只读的操作。
如何强制走主库或从库?
当自动路由不符合预期(比如刚写入就要立刻读取,避免从库延迟),必须手动指定连接:
- 强制走主库:
DB::connection('mysql')->table('users')->first()—— 注意:这里传的是连接名'mysql',不是'mysql_write';Laravel 在该连接配置下会主动选write分支 - 强制走从库:
DB::connection('mysql')->usingConnection('read')->table('users')->first()(Laravel 9+);旧版本需用DB::connection('mysql.read'),但该写法已被弃用且不稳定 - Eloquent 中强制主库:
User::on('mysql')->find(1);强制从库:User::on('mysql')->withWrite(false)->find(1)(withWrite(false)是 Laravel 8.34+ 引入的明确标记)
注意:on('mysql.read') 是错误写法,'mysql.read' 并不是一个真实连接名,只是配置里的嵌套结构。
常见踩坑点和验证方式
主从分离配置后没生效,大概率卡在这几个地方:
-
环境变量未生效:检查
.env中DB_READ_HOST_1是否拼写正确,是否被php artisan config:clear清除缓存后重载 - 从库权限不足:MySQL 账号需有
SELECT权限,且bind-address允许远程连接;用mysql -h 192.168.1.10 -u read_user -p手动测试连通性 - 忘记关闭
strict模式导致从库报错:某些从库因复制延迟,执行SELECT ... FOR UPDATE会失败,而 Laravel 在某些场景下会隐式加锁 - 没有验证实际走哪个库:在
DB::listen()回调里打印$event->connectionName和$event->sql,或在数据库侧开启general_log查看请求来源 IP
最易被忽略的是:Eloquent 的 withCount()、withSum() 等聚合关系方法,在关联模型未指定连接时,会回退到主库执行子查询——这意味着一次列表页可能混合走了主从,却不自知。










