ThinkPHP数据库集群配置:从入门到实战的四步拆解
先说一个核心结论:ThinkPHP的数据库集群,说白了,不是简单堆叠几个IP地址就完事。它需要一套清晰的主从结构、预定义连接和显式路由,三者协同才能跑得稳。配置上稍有偏差,轻则读写错乱,重则连接失败或中文乱码,坑不少。

一、必须用 connections 预定义全部节点
所有参与集群的数据库——主库、从库、日志库、报表库——都必须在 config/database.php 的 connections 数组中完整声明,不能靠运行时拼接。每个连接需要独立的键名,且只允许小写字母和下划线,比如 mysql_master、mysql_slave1、report_db。规矩很严格:
- 每个连接必须显式写明 'type' => 'mysql',漏掉会触发驱动误判,直接报 Class 'PDO' not found。
- 必需字段一个不能少:hostname、database、username、password、charset,缺一不可。
- 字符集必须统一,比如主库设 'charset' => 'utf8mb4',所有从库也得一致,否则 emoji 插入会失败。
- 禁止使用点号(如 log.db)或大写字母(如 LogDB),否则 Db::connect('log.db') 直接抛异常。
二、主从分离需 deploy + rw_separate 双开关
读写分离不是自动识别 SQL 类型,必须手动打开机制,而且参数要严格配对:
- 在主连接(如 mysql_master)配置块中添加 'deploy' => 1,这是总闸门,缺了整个读写分离逻辑不生效。
- 同时设置 'rw_separate' => true,但注意,它只在 deploy === 1 时才起作用。
- write 必须是一维数组,比如 ['hostname' => '192.168.1.10'];如果写成二维,框架启动即报错。
- read 必须是二维数组,比如 [['hostname' => '192.168.1.11'], ['hostname' => '192.168.1.12']];如果写成一维,框架会静默回退到主库。
- 所有读操作必须主动调用 useReadConnection(),或者显式使用 Db::connect('mysql_slave1'),否则默认走主库。
三、多业务库建议分键管理,避免混用
不同业务场景——比如用户主数据、操作日志、统计报表——应该各自拥有专属连接键名,在代码中显式调用,不依赖全局默认:
- 定义 mysql_log 专用于日志写入,可以加 'write_master' => false 防止意外触发主库。
- 定义 report_cluster 指向只读报表集群,它的 charset 和 prefix 可以和主库不同。
- 跨库查询不支持自动 JOIN,比如查用户加上其登录日志,需要手动分两次:
Db::connect('mysql_master')->table('user')->select()
Db::connect('mysql_log')->table('login_log')->where('user_id', 'in', $ids)->select()
然后在 PHP 层合并,注意重命名同名字段,比如两个表都有 id。
四、.env 仅作变量注入,不替代 connections 结构
.env 文件无法定义数组或嵌套结构,它只能提供扁平变量,用来填充 connections 中的值:
- .env 写法正确示例:
database2_hostname=192.168.1.11
database2_database=log_db - database.php 中对应引用:
'hostname' => env('database2_hostname', '127.0.0.1')
'database' => env('database2_database', '') - 禁止在 .env 中写 DATABASE2.HOSTNAME=... 或 connections[log_db][hostname]=...,这些语法无效。
理解这四个步骤,就掌握了 ThinkPHP 集群配置的核心套路。实际项目中,坚决按规范来,能省去很多排查时间。