thinkphp 8.0读写分离需三步:一是在.env中配置db_deploy=1和db_rw_separate=true;二是在database.php的mysql连接内注入deploy和rw_separate(后者须用filter_var转布尔);三是配置二维read数组或动态解析从库地址,并通过before_select事件实现灰度路由。

在ThinkPHP 8.0中实现读写分离,必须通过.env动态控制开关、在database.php连接配置内精准注入Env值,并绑定before_select事件做灰度路由——硬编码或放错配置层级会导致整个机制静默失效。
启用deploy和rw_separate开关
第一步:在项目根目录的.env文件中添加两行配置:
DB_DEPLOY=1
DB_RW_SEPARATE=true
第二步:打开config/database.php,找到'connections' => ['mysql' => [...]]结构,在'mysql'数组内部插入以下两行(必须嵌套在mysql连接配置里,不能写在connections外层或default下):
'deploy' => (int)Env::get('DB_DEPLOY', 0),
'rw_separate' => filter_var(Env::get('DB_RW_SEPARATE', 'false'), FILTER_VALIDATE_BOOLEAN)
注意:rw_separate必须用filter_var转布尔,否则传入字符串'true'会被TP8判定为false,读写分离完全不生效。
配置主库与从库地址
方法一:使用二维read数组(推荐,TP8原生支持)
在mysql连接配置中,确保'write'是单维数组(仅一个主库),'read'是二维数组(至少一个从库):
'write' => ['host' => 'master.example.com', 'username' => 'root', 'password' => 'xxx'],
'read' => [['host' => 'slave1.example.com', 'username' => 'ro_user', 'password' => 'yyy'], ['host' => 'slave2.example.com']]
read必须是非空二维数组,写成'read' => []或'read' => ['host' => '...'](一维)将导致所有SELECT fallback到主库。
方法二:通过环境变量拼接从库地址(适合多环境差异化部署)
例如在.env中定义:
DB_SLAVE_HOSTS=192.168.1.10,192.168.1.11
然后在database.php中解析:
$slaves = array_map('trim', explode(',', Env::get('DB_SLAVE_HOSTS', '')));
'read' => array_map(fn($host) => ['host' => $host, 'port' => 3306], $slaves)
实现灰度路由(按用户ID分流)
第一步:创建app/service/DbRouter.php,内容如下:
namespace app\service;
use think\db\exception\DbException;
use think\db\Query;
class DbRouter
{
public static function init(): void
{
db()->event('before_select', [self::class, 'onBeforeSelect']);
}
public static function onBeforeSelect(Query $query): void
{
$userId = session('user_id') ?: (request()->header('X-User-ID') ? request()->header('X-User-ID') : 0);
if ($userId && $userId % 100 $query->useReadConnection(true);
}
}
}
第二步:在app/common.php或服务提供者中调用初始化:
app\service\DbRouter::init();
第三步:在.env中添加灰度比例(整数!):
READ_SLAVE_RATE=30
READ_SLAVE_RATE必须是整数,若写成30.5,PHP取模运算会出错,导致路由逻辑崩溃。
验证是否生效
在从库执行SHOW PROCESSLIST,过滤应用服务器IP,确认有SELECT连接进来;
不要依赖getLastSql()判断——它只显示SQL文本,不反映真实执行库;
事务内或Db::master()->table()->select()链式调用会强制走主库,这是正常行为。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











