关键在于接管连接解析逻辑而非重写eloquent,通过监听db.resolve事件动态注册租户连接并缓存,避免db::purge()热替换无效问题,且需显式指定模型连接或重写newquery(),禁用全局db::connection()。

如何让 Eloquent 自动切换租户连接而不改模型代码
关键不是重写 Eloquent,而是接管连接解析逻辑。Laravel 的 DB::connection() 默认只认配置名,但租户连接必须动态生成并缓存——否则每次查询都重建连接,性能直接崩。
实操建议:
- 在服务提供者中监听
db.resolve事件,用租户标识(如tenant_id)拼接连接名,例如"mysql_tenant_123" - 提前在
config/database.php中定义一个模板连接(比如mysql_tenant_template),不填database和host,只设驱动和通用参数 - 事件处理器里调用
DB::extend()动态注册新连接,用Configuration::replace($config)注入租户专属的database、host、username - 务必加内存缓存(如
static $connections = [];),避免重复注册同名连接导致 Laravel 报Connection [xxx] is not registered.
为什么不能靠中间件 + DB::purge() 切换连接
常见错误是:中间件里执行 DB::purge('mysql') 再 Config::set('database.connections.mysql.database', 'tenant_db'),以为能“热替换”。实际行不通——Eloquent 模型已绑定原始连接实例,Config::set 只影响后续新建连接,老连接仍连着旧库。
更糟的是,Laravel 的查询构建器(DB::table())会复用连接池里的连接对象,而这些对象内部缓存了数据库名、字符集等不可变属性,改配置根本无效。
正确做法:
- 所有租户相关模型必须显式指定连接:
protected $connection = 'mysql_tenant_123'; - 或统一继承基类,在
newQuery()中强制返回对应租户连接的查询构建器:return $this->getConnection()->table($this->getTable()); - 绝对不要在运行时调用
DB::purge()后依赖全局DB::connection(),它不会自动重建租户连接
Composer 库选型:tenancy/tenancy vs. staudenmeir/laravel-cte 的取舍
tenancy/tenancy 是完整多租户方案,自带数据库隔离、队列上下文、HTTP 中间件,但它强耦合于特定租户识别方式(如域名或请求头),且升级频繁破坏兼容性;staudenmeir/laravel-cte 只解决“同库多 schema”场景,不碰连接管理。
如果你的租户数据物理隔离(每租户独立数据库),别装 tenancy/tenancy——它默认走共享库+schema 模式,硬切数据库要重写 DatabaseManager,反而增加复杂度。
推荐组合:
- 轻量级需求:直接用 Laravel 原生连接扩展机制(上文所述),配合自定义 Service Provider
- 已有租户上下文(如
Auth::user()->tenant_id):封装一个TenantConnectionResolver类,由AppServiceProvider绑定到容器 - 需要跨租户查询(如后台统计):保留一个专用的
system连接,禁止通过租户解析器访问,避免权限混淆
迁移命令怎么跑不同租户的数据库
php artisan migrate 默认只跑 default 连接,租户库迁移必须手动指定。但 --database=mysql_tenant_456 要求连接名已在配置中静态存在,而租户连接是动态生成的。
解决方案是重写迁移命令:
- 复制
Illuminate\Database\Console\Migrations\MigrateCommand到本地,修改getDatabaseName()方法,从请求/参数提取tenant_id,再调用你的连接解析器获取真实连接名 - 迁移文件里避免写死表名,用
$this->schema->create('users', ...)而非DB::statement("CREATE TABLE tenant_users..."),否则跨租户时表名冲突 - 生产环境严禁用
php artisan migrate --force批量跑所有租户——应结合租户列表脚本逐个执行,失败立即中断,防止某租户迁移卡住拖垮全部
租户连接的生命周期管理比想象中敏感:连接名拼错、缓存未清、迁移未同步,任何一个点出问题都会导致查到别的租户数据。最隐蔽的坑是数据库连接复用——PHP-FPM 子进程可能缓存了上一个请求的租户连接,务必在请求结束前显式断开或重置连接状态。











