Laravel读写分离需手动配置mysql_write和mysql_read两个独立连接,各含完整参数(driver、host、username等),通过模型$connection属性或DB::connection()显式指定,事务内不可混用,迁移需用--database参数显式指定目标连接。

读写分离不是 Laravel 内置的自动功能,它依赖你手动配置多个连接(比如 mysql_read 和 mysql_write),再通过代码逻辑或中间件控制查询路由到哪个连接。Laravel 本身不解析 SQL 类型来决定走读库还是写库,这点必须提前认清。
怎么在 config/database.php 里配读写两个 MySQL 连接
关键不是“配两个库”,而是让两个连接语义清晰、参数完整、互不干扰。读库和写库通常用相同驱动但不同 host/port/database,也可能权限不同(读库用户无写权限)。
- 在
config/database.php的'connections'数组里加两项:'mysql_write'和'mysql_read',不能只复制一份改host就完事——driver、username、password、charset等字段一个都不能少 - 环境变量要用不同前缀,比如
DB_WRITE_HOST/DB_READ_HOST,避免和默认DB_*冲突;如果共用密码,也建议拆开,方便后期权限收窄 - 别删掉默认的
'mysql'连接——Laravel 的php artisan migrate、通知队列、缓存表等内部组件默认依赖它,删了会报错 - 改完记得运行
php artisan config:clear,否则缓存里的旧配置还在生效
模型里怎么自动走读库或写库
没“自动”这回事。Eloquent 不会根据 get() 或 save() 自动切库。你得自己控制:写操作强制走 mysql_write,读操作尽量走 mysql_read,但得留后手——比如读库挂了要能 fallback 到写库。
- 对写模型(如
User),设protected $connection = 'mysql_write',保证所有create、update、delete都落在主库 - 对只读模型(如
ReportSummary),设protected $connection = 'mysql_read',但建议在booting或构造器里加异常兜底:捕获QueryException后临时切回mysql_write - 不要在模型里写
on('mysql_read')链式调用——它只影响当次查询,下一行又回到默认连接,容易漏,维护成本高
DB::table() 查询时如何指定读库
这是最灵活也最容易出错的方式。适用于临时查报表、日志、统计类数据,不希望拖慢主库。
- 用
DB::connection('mysql_read')->table('logs')->where(...)->get(),明确指定连接名 - 避免用
DB::table('logs')->on('mysql_read')->get()——这个on()方法只在 Eloquent 模型上有效,对DB::table()无效,会静默忽略或报错 - 如果用了 Query Builder 的
fromSub()或联合查询(JOIN 多表),确保所有表都在同一库——跨库 JOIN 在 MySQL 里不支持,PostgreSQL 虽支持但性能极差,别碰 - 事务内禁止混用读写连接:
DB::connection('mysql_write')->transaction(function () { User::create(...); Log::on('mysql_read')->create(...); });这种写法里,Log操作根本不在事务里,数据一致性无法保障
迁移和 Seeder 怎么跑读库
迁移默认只跑 DB_CONNECTION 指定的连接(通常是 mysql 或 mysql_write)。读库一般不需要跑迁移,但如果真有需求(比如读库也是独立实例、需要建视图或物化表),必须显式指定。
- 跑读库迁移:执行
php artisan migrate --database=mysql_read,注意--database值必须和config/database.php里键名完全一致,大小写敏感 - Seeder 同理:
php artisan db:seed --database=mysql_read,或者在 Seeder 文件里手动调用DB::connection('mysql_read') -
php artisan migrate:status --database=mysql_read可查该连接当前已执行的迁移列表,避免重复执行 - 千万别在生产环境直接对读库跑
migrate:fresh——它会删库重建,而读库的数据来源可能是主库同步,删了就断链了
最易被忽略的点是连接名硬编码和事务边界。写死 'mysql_read' 字符串散落在十几个模型和 Service 里,一旦哪天要加第三个读库或改名,就得全局 grep + 替换,极易遗漏。更稳妥的是定义常量或绑定到容器,比如 config('database.connections.mysql_read') 本身可读,但连接名作为字符串传参时,最好抽成 DatabaseConnection::READ 这样的枚举类,编译期就能发现拼写错误。


















