在 config/database.php 的 connections 数组中添加 mysql_secondary 键,结构同 mysql 但配置独立 host、database 等参数,注意 prefix、charset(推荐统一 utf8mb4)及 strict/engine 兼容性;model 需显式设 protected $connection = ‘mysql_secondary’ 或用 ::on() 切换;迁移须用 –database=mysql_secondary;db::raw() 不绑定连接,执行库由外层查询决定;事务、observer、子查询均不自动继承连接,需手动指定。

config/database.php 里怎么加第二个 MySQL 连接
直接在 connections 数组里追加一个键名(比如 mysql_secondary),结构和默认的 mysql 一致,但填上另一套 host、database、username 等。别漏掉 prefix 和 charset —— 如果主库用了 utf8mb4 而新库是 utf8,查中文可能乱码或报错 SQLSTATE[HY000]: General Error: 1366 Incorrect String value。
常见错误:复制了整个 mysql 配置块却没改 host 或 database,结果两个连接指向同一库,切来切去还是查的同一个地方。
- 键名必须全小写、无下划线以外的符号(laravel 的 DB facade 内部用字符串匹配,
mysql2可以,mysql-2会报InvalidArgumentException: Database [mysql-2] not configured - 如果新库用的是不同版本 MySQL(比如 5.7 vs 8.0),记得检查
strict和engine设置是否兼容 -
read/write分离配置不要和多连接混着写——那是另一套机制,强行叠在一起会导致DB::connection('xxx')->table('y')->get()偶尔走错连接
DB::connection(‘xxx’) 切换后为什么 Model 还走默认库
Model 默认绑定的是 protected $connection = 'mysql',手动调用 DB::connection('mysql_secondary') 只影响该次查询,对 Eloquent 没用。想让某个 Model 固定走副库,得显式指定:
class Report extends Model { protected $connection = 'mysql_secondary'; }
容易踩的坑:在控制器里写了 DB::connection('mysql_secondary')->table('reports')->get(),顺手又写了 Report::all(),后者仍走 mysql —— 两个查询实际连了不同库,数据对不上还查不出问题。
- 临时切换 Model 的连接,可以用
Report::on('mysql_secondary')->get(),比改$connection更安全 - 如果 Model 有 Observer 或 Accessor 里调了其他 Model,那些关联 Model 不会自动继承当前连接,得各自指定
- 使用
DB::transaction()时,所有语句都必须在同一连接上,跨连接事务不生效,也不会报错,只会在副库提交、主库回滚,造成数据不一致
迁移文件怎么指定写到哪个数据库
运行 php artisan migrate --database=mysql_secondary,关键在 --database 参数值必须和 config/database.php 里 connections 的键名完全一致。漏掉这个参数,哪怕 Model 指定了 $connection,迁移也只会跑在默认库上。
注意:迁移文件本身不感知连接,它只是 SQL 模板;真正决定执行位置的是命令行参数或 database.php 中的 default 值。
- 多库共用一套迁移?危险。比如
create_users_table在主库建了,在副库再跑一次会报SQLSTATE[42S01]: Base table or view already exists,但 Laravel 默认忽略这个错误,看起来“成功”了,其实副库表结构可能没更新 - 想让某些迁移只在特定库执行,得在
up()里加判断:if ($this->connection === 'mysql_secondary') { ... },但更稳妥的做法是拆成两套 migration 目录,用--path分开跑
DB::raw() 和查询构造器在多连接下要注意什么
DB::raw() 本身不绑定连接,它只是生成一段原生 SQL 字符串。真正决定执行位置的是它被挂在哪条查询链上。比如:
DB::connection('mysql_secondary') ->table('orders') ->select(DB::raw('COUNT(*) as cnt')) ->get(); // ✅ 走副库
DB::table('orders') ->select(DB::raw('COUNT(*) as cnt')) ->get(); // ❌ 走默认库,DB::raw 不改变连接
性能影响:如果副库是只读从库,但你在 DB::raw() 里写了 INSERT 或 UPDATE,Laravel 不会拦截,MySQL 直接报错 SQLSTATE[HY000]: General error: 1290 The MySQL server is running with the --read-only option。
-
DB::statement()、DB::insert()等写操作,必须显式指定连接,否则永远走默认库 - 子查询里嵌套
DB::raw()时,外层连接决定了执行库,内层不会“自动切换” - 如果副库字段类型和主库不一致(比如主库
json字段,副库是text),DB::raw('JSON_EXTRACT(...)')在副库会直接报错,而不是静默返回 NULL
复杂点在于连接不是全局开关,而是每个查询实例的属性;容易被忽略的是迁移、事务、Observer 这三处,它们表面看和连接无关,实则每一步都依赖连接上下文是否被正确传递。