真正可控的多数据库切换必须预定义连接标识符并复用连接池,不可临时调用db::connect();自定义连接池需继承重写connectionpool类;跨库事务必然失败,须严格隔离读写或分库边界;动态添加连接须调用setconfig()并立即clearconnection()。

多数据库切换不能只靠 Db::connect() 临时调用
直接在业务里反复写 Db::connect(['dsn' => $dsn]) 看似能切库,但会绕过 ThinkPHP 的连接池管理,每次都是新建 PDO 实例,连接数暴涨、超时风险高,且事务无法跨库传播。
真正可控的切换必须绑定到连接标识符(name),而不是每次都传完整配置:
- 在
config/database.php中预定义多个连接,比如'mysql_read'、'mysql_write'、'pgsql_log' - 运行时用
Db::name('user')->connect('mysql_read')切换,底层复用已初始化的连接池 - 避免在循环里调用
connect(),应提前获取连接实例并复用:$readDb = Db::connect('mysql_read');
自定义连接池需重写 ConnectionPool 类而非仅改配置
ThinkPHP 默认的连接池(think\db\connector\ConnectionPool)是单例+固定大小,不支持按库名隔离、无空闲连接回收策略、也不感知数据库健康状态。硬改配置项如 'pool_size' 或 'max_idle_time' 无效,因为底层没实现对应逻辑。
实操上要真正扩展连接池,必须:
- 继承
think\db\connector\ConnectionPool,重写get()和put()方法,加入库名前缀隔离(例如 key 为"mysql_read_192.168.1.10") - 在
get()中增加连接可用性检测(执行PING或SELECT 1),失败则丢弃并新建 - 把自定义类注册进容器:在
app/provider.php加ConnectionPool::class => \app\extend\MyConnectionPool::class
Db::transaction() 跨库操作必然失败,别试图绕过
MySQL/PostgreSQL 的分布式事务不是靠 PHP 层能协调的。一旦你在事务中混用 Db::connect('mysql_a') 和 Db::connect('mysql_b'),commit() 只会对最后一个连接生效,其余连接早已自动提交或回滚——你看到的“成功”,其实是多个独立事务的错觉。
真实场景下必须明确边界:
- 读写分离:写用
mysql_write,读用mysql_read,但读不参与写事务 - 分库分表:同一业务数据必须落在同一个物理库,靠路由规则(如
UserModel绑定mysql_shard_01)保证,而非运行时切换 - 日志类异步写入:用消息队列解耦,不要塞进主事务
动态添加数据库连接必须调用 Db::setConfig() 并清空连接缓存
运行时新增一个数据库连接(比如租户制系统中按 tenant_id 动态加载 DB 配置),不能只 new 一个 PDO 就完事。ThinkPHP 会缓存连接实例,不主动清理会导致旧连接被复用、新配置失效。
正确流程只有三步:
- 构造完整配置数组,含
'type'、'hostname'、'database'、'username'等必要字段 - 调用
Db::setConfig($config, 'mysql_tenant_' . $tenantId)注册新连接名 - 立刻执行
Db::clearConnection('mysql_tenant_' . $tenantId),否则下次connect()拿到的还是空缓存或旧连接
漏掉第三步,是线上查不到数据却死活不报错的最常见原因。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











