ThinkPHP 6 读写分离需手动配置 write/read 独立连接池,通过 useReadConnection()/useWriteConnection() 显式切换;不自动识别 SQL 类型,default 设为写库,deploy=1 且 read_master=false 才生效;多从库需自行实现轮询或权重逻辑。

ThinkPHP 6 的 database.php 怎么配读写分离
ThinkPHP 6 原生支持读写分离,但不是靠“自动识别 SELECT/INSERT”来实现的,而是靠显式指定连接池名 + 查询时手动切换。配置核心是把 write 和 read 当作两个独立连接池,再通过 useWriteConnection() 或 useReadConnection() 控制走向。
常见错误是直接在 database.php 里只写一个 connections 数组,没拆出 write 和 read 子键;或者误以为只要配置了 read_host 就能自动分流——实际上 TP6 不解析 SQL,不判断语句类型。
-
connections下必须定义至少两个连接:比如'mysql_write'和'mysql_read',各自带完整 host/user/database 配置 - 主配置项
default应设为'mysql_write'(默认走写库) - 启用读写分离需开启
'deploy' => 1,并设置'read_master' => false(否则读操作仍可能打到主库) - 如果用 PDO,注意
mysql_read连接里要加'slave_no_delay' => true,避免从库延迟导致查不到刚写的记录
模型里怎么强制走读库或写库
TP6 模型默认走 default 连接(即写库),读库必须显式声明。最常用的是 useReadConnection(),但它只对当前查询生效,不影响后续链式调用。
容易踩的坑是:在事务中调用 useReadConnection(),结果发现还是走写库——因为事务期间所有操作强制绑定到事务起始时的连接,读库切换会被忽略。
立即学习“PHP免费学习笔记(深入)”;
- 简单读取:用
UserModel::useReadConnection()->select() - 关联查询时,关联模型不会继承主模型的读库设置,需单独调用
with(['profile' => function ($q) { $q->useReadConnection(); }]) - 想全局让某个模型默认走读库?重写它的
getConnectionName()方法,返回'mysql_read' - 批量更新、删除等写操作,别误用
useReadConnection(),否则报错PDOException: cannot execute INSERT in read-only mode
为什么 Db::connect('mysql_read') 查不到刚写入的数据
这不是 TP 的问题,是 MySQL 主从同步延迟的真实体现。即使配置正确,Db::connect('mysql_read') 拿到的也是从库连接,而从库数据有秒级甚至更长延迟。
典型场景:用户注册后立刻跳转到个人页,页面用读库查 User::useReadConnection()->find($id),结果查不到——因为写库刚插入,从库还没同步完。
- 强一致性要求的场景(如注册后立即查看),必须用
Db::connect('mysql_write')或不指定连接(走 default) - 不想改代码?可在写操作后加
sleep(0.1)(不推荐)或用SHOW SLAVE STATUS检查Seconds_Behind_Master再决定是否等待 - 更合理的做法是前端加 loading,后端提供“最终一致性”提示,比如“信息稍后同步”
- 某些云数据库(如阿里云 RDS)提供
wait_uncommitted机制,TP 无法直接利用,需自己封装原生查询
多个从库怎么轮询或权重分配
TP6 原生不支持多从库自动负载均衡。所谓“读库列表”,只是把多个从库地址塞进 read_host 数组,但底层仍只随机选一个,并不轮询也不按权重分发。
如果你看到文档说 “支持一主多从”,那是指可以配置多个 read_host,但实际每次查询只连其中一个,且无法控制选择逻辑。
- 真要轮询:得自己写连接池管理器,继承
think\db\Connection,重写getReadConnection()方法,维护一个下标计数器 - 按权重分配:需要解析
read_host配置里的 weight 字段(TP6 不识别该字段),自行实现加权随机算法 - 更稳妥的做法是用中间件层做 DNS 轮询(如用 LVS 或云厂商的读写分离代理),让 TP 认为只有一个读库地址
- 注意:TP6 的
read_host是字符串数组,不能嵌套配置,所以['192.168.1.10:3306', '192.168.1.11:3306']是合法的,但[['host'=>'192.168.1.10','weight'=>2]]会直接报错
读写分离不是开个开关就完事的事。配置只是第一步,真正难的是业务层对一致性边界的理解和妥协——比如什么时候必须读主库,什么时候能接受延迟,以及怎么向用户解释“正在同步中”。这些没法靠框架自动解决。


















