ThinkPHP 8.0 读写分离必须在 config/database.php 的 'mysql' 连接配置内部通过 Env::get() 动态读取 DB_DEPLOY 和 DB_RW_SEPARATE,并用 (int) 和 filter_var() 正确转换类型;deploy=1 时 read 必须为非空二维数组,灰度需通过 Db::event('before_select') 手动路由。

ThinkPHP 8.0 中,Env 配置读写分离必须在 config/database.php 的具体连接配置内部读取,否则开关不生效、从库永不接入。
DB_DEPLOY 和 DB_RW_SEPARATE 必须在 mysql 连接内用 Env::get()
TP8 不会自动把 .env 变量注入到数据库配置中。你不能只在 .env 里写:
DB_DEPLOY=1 DB_RW_SEPARATE=true
还必须在 config/database.php 的 'connections' => ['mysql' => [...]] 内部显式调用:
-
'deploy' => (int)Env::get('DB_DEPLOY', 0)—— 注意强制转int,因为 TP8 底层判断的是整型1,不是布尔或字符串 -
'rw_separate' => filter_var(Env::get('DB_RW_SEPARATE', 'false'), FILTER_VALIDATE_BOOLEAN)—— 直接传字符串'true'会被判为false,必须用filter_var - 这两个键**不能**放在
connections外层、也不能放在default或全局配置位置,否则整个读写分离逻辑不会初始化
DB_DEPLOY=0 时 read 可为空,但启用时 read 必须是二维数组
灰度阶段常需动态关闭读写分离。当 DB_DEPLOY=0,框架会忽略 read 配置,此时 'read' => [] 是合法的;但一旦 DB_DEPLOY=1,'read' 就必须是非空二维数组(哪怕只有一个从库):
立即学习“PHP免费学习笔记(深入)”;
- ✅ 正确:
'read' => [['hostname' => '192.168.1.11', 'database' => 'myapp', ...]] - ❌ 错误:
'read' => ['hostname' => '192.168.1.11'](一维,TP8 报错Call to a member function query() on null) - ❌ 错误:
'read' => [](空数组 +DB_DEPLOY=1→ 框架找不到读库,所有查询 fallback 到主库且无提示)
灰度路由要用 Db::event('before_select'),不能只靠配置
仅靠 DB_DEPLOY 和 DB_RW_SEPARATE 开关只能控制「是否启用读写分离机制」,无法实现「部分请求走从库、部分仍走主库」的灰度效果。你需要手动干预路由:
- 在
app/common/EventServiceProvider.php或启动文件中注册:Db::event('before_select', function ($query) { if (shouldUseSlave()) { $query->useReadConnection(); } }); -
shouldUseSlave()可基于 Header、User ID、URL 路径、AB 测试分桶等自定义逻辑 - 注意:事务中、锁操作、连贯调用含
update()后紧跟select()等场景,before_select仍会被框架拦截并强制回主库——这是 TP8 的安全策略,不是 bug
最易被忽略的一点:TP8 的 read 配置不支持环境变量拼接主机名(如 env('SLAVE_HOST_1')),必须在 read 数组里直接写死或通过 PHP 变量组装;若要完全动态,得在 before_select 里用 Db::connect() 手动切库,但要注意连接复用和缓存清理。



















