tp6多租户动态切库核心是按租户绑定专属连接,须在请求最早期(如全局中间件)提取租户标识、动态注册配置并挂载到request;模型层须统一使用该连接实例,事务与关联查询需显式指定连接。

TP6在多租户模式下动态切换数据库连接,核心不是“临时切库”,而是“按租户绑定专属连接”,且必须在任何数据库操作前完成注册与挂载。写死 $connection 或在控制器里反复调 Db::connect() 都无效——前者无法适配不同租户,后者不改变模型默认行为,也不影响后续 Db::table() 调用。
租户识别与连接注册必须前置
框架在首次调用 Db::table() 或 Db::name() 时就初始化并缓存了默认连接。中间件里再调 Db::connect() 只是新增实例,不会重置已建立的默认连接。因此,动态切换的前提是:在请求生命周期最早期(如全局中间件)完成两件事:
- 从子域名(如
tenant1.example.com)、请求头(如X-Tenant-ID: tenant1)或 URL 参数中提取租户标识 - 基于该标识组装完整数据库配置(含
type、hostname、database、username等),调用Db::setConnectConfig('tenant1', $config)注册连接 - 再将该连接实例挂载到 Request 对象上,例如:
app('request')->db = Db::connect('tenant1');
模型层统一使用租户连接
业务代码不能依赖 Db::table(),而应统一从请求对象取连接。推荐做法是封装一个基类或服务方法:
- 在控制器或服务中,通过
$this->request->db获取已绑定的租户连接实例 - 所有查询都显式调用该实例,如:
$this->request->db->table('user')->select() - 若使用模型,可在基类
BaseModel中重写__callStatic,检查$this->request->tenant_id,自动追加->connect('tenant_' . $id);或更稳妥地,在具体模型中设protected $connection = 'tenant_123'(但需配合运行时注册)
配置必须预定义,不能运行时传数组
Db::connect() 只接受字符串连接名(如 'tenant1'),且该名称必须已在 config/database.php 的 connections 数组中预先定义。但多租户场景下租户数不确定,无法全部写死。因此正确路径是:
-
config/database.php中只保留基础 default 连接,用于存储租户元数据(如租户库地址) - 真正租户库的完整配置由中间件动态生成,并通过
Db::setConnectConfig()注入框架连接池 - 注意每个动态配置必须包含
type、hostname、database、username、password、charset等全量字段,缺一不可 - .env 文件无法定义多维 connections 结构,所以动态注册是唯一可行方式
事务与关联查询需单独处理
动态连接不自动传递到事务和关联模型:
- 事务只能作用于单个连接:
$this->request->db->startTrans()仅对该连接生效,跨租户无法合并提交 - 关联模型默认走各自模型的
$connection属性,若主模型用了租户连接,但关联模型没指定,就会查错库。必须在关联定义中显式声明:'connection' => 'tenant_123' - 迁移命令(
php think migrate:run)不识别运行时连接,只走database.default,租户库迁移需单独脚本触发











