db::connect()需传配置键名而非数据库名,模型$connection仅首次加载生效,中间件切换须早于db操作并显式使用新实例,多租户需复用连接防泄漏与事务断裂。

Db::connect() 传错参数导致切换失败
最常见错误是把数据库名、模型名或 DSN 字符串直接传给 Db::connect(),比如 Db::connect('user_db') 却没在 config/database.php 的 connections 数组里定义键名为 user_db 的配置。TP8 会直接抛出 Connection not found: user_db 异常。
必须确保:
- 传入的是配置文件中
connections下的**键名**(如'admin'或'user'),不是数据库名本身 - 该键名对应配置里必须包含完整字段:
'type'、'hostname'、'database'、'username'、'password' -
'database'值拼写正确且 MySQL 中真实存在,Linux 环境下大小写敏感 - 不要传数组给
Db::connect('xxx')—— 那是Db::connect($array)的用法,和路由切换无关
模型类 $connection 属性不生效的典型场景
在模型里写 protected $connection = 'admin'; 看似简单,但容易忽略两个硬约束:
- 这个值只在模型类首次加载时读取一次,后续运行时修改
config/database.php或重载配置,它不会刷新 - 关联查询(如
UserModel::with('profile')->find(1))中,ProfileModel仍走它自己的$connection,不会继承UserModel的设置 - 如果
ProfileModel没设$connection,它就走默认库,哪怕UserModel明确绑定了'admin'
想让整条链都走同一库,每个关联模型都得单独声明 protected $connection = 'admin';,或者在关联定义里加 'connection' => 'admin' 参数。
中间件中动态切换连接却查到旧库数据
根本原因不是代码写错,而是 ThinkPHP 的连接初始化时机太早:只要任何地方调过 Db::table()、Db::name() 或模型查询,全局默认连接就已缓存并锁定。中间件里再调 Db::connect(),只是新建一个实例,对已缓存的连接毫无影响。
要让它真正起作用,必须满足:
- 中间件执行顺序必须在所有数据库操作之前(检查
app/middleware.php中注册顺序) - 不能依赖
Db::table(),而要把新连接挂到请求对象上,例如:$request->db = Db::connect($tenantConfig); - 后续所有查询必须显式使用
$request->db->table('log')->select(),而非Db::table() - 模型类也得改:在
initialize()里手动注入,$this->connection = $request->db;
多租户高频切换下的连接泄漏与事务断裂
每次调 Db::connect() 都新建 TCP 连接,租户量大时容易触发 MySQL 的 max_connections 限制,show processlist 里能看到大量 Sleep 状态连接堆积。
更隐蔽的风险是事务失效:你在 $db1->startTrans() 后,不小心用了 $db2->insert(),事务根本不会覆盖那条记录——两个连接完全隔离。
应对策略很实际:
- 复用连接变量,别在循环里反复调
Db::connect() - 配置中设
'deploy' => 0和'pool_size' => 5控制单实例最大连接数 - 事务操作必须严格限定在同一个
$db实例内完成 - 租户超百时,别硬扛,该上 MyCat 或 ShardingSphere 就上
动态路由切换的本质不是“换库”,而是“绕过默认连接池,强制走指定通道”。一旦忘了这个前提,所有看似正确的代码都会查到错库的数据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











