ThinkPHP 8.0 读写分离需同时满足三要素:在 connections['mysql'] 内配置 deploy=1 和 rw_separate=true(后者须 filter_var 转布尔),且 read 必为二维数组、write 必为单数组,缺一不可。

ThinkPHP 8.0 配置数据库读写分离,核心是三件事:在 mysql 连接配置内启用部署开关、正确组织主从地址结构、用环境变量安全控制开关状态。它不靠“写了 read 就自动走从库”,而是严格依赖配置层级和数据格式。
必须写在 connections['mysql'] 内部的两个开关
deploy 和 rw_separate 必须同时存在且为真值,否则整个读写分离逻辑不会初始化。
-
'deploy' => (int)Env::get('DB_DEPLOY', 0)—— 这是总开关,值为 1 才启动多节点支持 -
'rw_separate' => filter_var(Env::get('DB_RW_SEPARATE', 'false'), FILTER_VALIDATE_BOOLEAN)—— 必须用filter_var转布尔,直接写Env::get('DB_RW_SEPARATE')会传入字符串,判断恒为false - 这两个键绝不能放在
default或connections外层,否则 Db::name('user')->select() 仍默默走主库,且无任何提示
read 和 write 的数组结构不能错
TP8 强制要求 read 是二维数组(哪怕只有一个从库),write 是单个完整配置数组。格式错误会导致连接对象为 null,报 Call to a member function query() on null。
- ✅ 正确:
'read' => [['hostname' => '192.168.1.11', 'database' => 'myapp', 'username' => 'r1', 'password' => 'xxx']] - ❌ 错误:
'read' => ['hostname' => '192.168.1.11'](一维,解析失败) - ✅ 正确:
'write' => ['hostname' => '192.168.1.10', 'database' => 'myapp', 'username' => 'w1', 'password' => 'xxx'] - ❌ 错误:
'write' => [['hostname' => 'm1'], ['hostname' => 'm2']](报 Invalid write database config) - 所有节点的
database名必须完全一致,否则跨库 JOIN 或事务会出错
通过 .env 控制灰度启用与请求级分流
.env 中只需定义开关:
立即学习“PHP免费学习笔记(深入)”;
DB_DEPLOY=1DB_RW_SEPARATE=true
但仅靠环境变量无法实现“部分用户走从库”。若需灰度(例如只对 ID 为偶数的用户启用读库),必须在应用层拦截:
- 监听
Db::event('before_select') - 在回调中解析当前用户 ID(如从 token 或 session)
- 满足条件时调用
$event->useReadConnection(true)
不推荐直连 MySQL,建议搭配 ProxySQL 使用
TP8 自身不处理节点健康检查、故障剔除或轮询负载均衡。真实生产环境应让 TP 连 ProxySQL(如 192.168.1.100:6033),由 ProxySQL 管理后端 MySQL 主从节点:
- TP 的
hostname填 ProxySQL 地址,username/password填 ProxySQL 中定义的前端用户(非 MySQL 账号) - ProxySQL 根据后端 MySQL 的
read_only状态自动归类主从,无需 TP 解析 SQL 类型 - 事务语句(
BEGIN/COMMIT)会被 ProxySQL 强制路由到主库组,保障一致性 - 避免出现 “Access denied” 报错——那是 TP 用 MySQL 账号去连了 ProxySQL 的 6033 端口



















