thinkphp 6 读写分离需手动配置 readwrite_type 并显式调用 read(true)/write(true) 或 master(true),不自动识别 sql 类型;从库权重仅在 balance 模式下生效,且不支持运行时动态调整。

ThinkPHP 6 的 database.php 怎么配读写分离
TP6 原生支持读写分离,但不是靠“自动识别 SELECT/INSERT”来切,而是靠配置里的 readwrite_type 和显式调用 db()->table()->read(true) 或 db()->table()->write(true) 控制流向。默认的 readwrite_type = 'random' 只是随机选一个从库,不带权重也不感知延迟。
实操建议:
- 在
config/database.php中定义'connections' => ['mysql' => [...]]时,把主库放'hostname'字段,从库全扔进'slave' => [...]数组里 -
'slave' => [ ['hostname' => '192.168.1.10', 'port' => 3306, 'weight' => 3], ['hostname' => '192.168.1.11', 'port' => 3306, 'weight' => 1] ]—— 权重只在readwrite_type = 'balance'下生效 - 别漏掉
'readwrite_type' => 'balance',否则weight字段完全被忽略 - TP6 不支持运行时动态调整从库权重,改完必须 reload 配置(如重启 Swoole 或清 config 缓存)
从库延迟导致查不到刚写的记录怎么办
这是读写分离最常踩的坑:用户注册后跳转到个人页,页面用 Db::name('user')->where('id', $id)->find() 查,结果返回 null。因为从库还没同步完。
实操建议:
- 对「写后立即读」场景,强制走主库:
Db::name('user')->master(true)->where('id', $id)->find()——master(true)是关键,它会跳过所有从库逻辑 - 不要依赖
lastInsertId()后立刻find(),除非加master(true) - 如果业务允许几秒延迟,可以加简单重试(比如查不到时 sleep(100) 再查一次),但别在事务里做
- TP6 没有内置的延迟检测机制,不能自动把延迟高的从库临时踢出列表
readwrite_type = 'balance' 和 'random' 的实际区别
表面看都是分发读请求,但底层行为差很多:random 真的就是 PHP array_rand() 随机挑;balance 是按权重做加权轮询(类似 Nginx 的 weight),且会缓存上一次选中的索引,下次接着轮。
实操建议:
- 高并发下
balance更稳,避免某台从库突然被打爆;random在从库性能差异大时容易失衡 - 权重值没有单位,只看相对大小。设成
[10, 1]和[100, 10]效果一样 - 权重为 0 的从库会被跳过,但 TP6 不校验这点,填了 0 就真不连了,连错误日志都不会报(静默失败)
- 如果你用的是 Swoole + 协程,注意
balance的轮询状态是协程隔离的,不同请求互不影响
为什么加了从库配置却始终只连主库
常见现象:slave 数组明明写了,SQL 日志里也全是 SELECT,但 strace 或 MySQL 的 show processlist 显示所有连接都打在主库上。
实操建议:
- 检查是否用了
Db::connect('mysql')但没传第二个参数true(即没启用读写分离)。正确写法是Db::connect('mysql', true) - 确认没在模型里写死
protected $connection = 'mysql';—— 这会绕过读写分离开关 - TP6 默认只对
select、find、value等读操作走从库,count()默认也走从库,但paginate()的 count 查询可能被优化掉,要看具体版本 - 开启
debug模式后,看日志里 SQL 前是否有[READ]或[WRITE]标记,这是最直接的判断依据
权重和延迟本身不难配,难的是意识到「TP6 的读写分离是声明式的,不是拦截式的」—— 它不会帮你分析 SQL 类型,也不会自动兜底。写错一个 master(true) 或漏掉一个 true 参数,整个链路就失效了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











