ThinkPHP 6.0 主从配置必须同时启用 'deploy' => 1 和 'rw_separate' => true,且须置于具体连接配置(如'mysql')内;仅配read/write无效,漏任一开关则全走默认连接;write仅支持单数组,read支持多从轮询或加权;带锁、事务内及->master(true)操作强制走主库;Db::name()受连接配置控制,Db::connect()则绕过分离逻辑直连指定库;主从延迟需业务层主动兜底。

ThinkPHP 6.0 的数据库主从不是配完 read 和 write 就自动生效的,漏掉 'deploy' => 1,整个读写分离逻辑根本不会启动——所有查询都走默认连接,白配。
database.php 里必须显式启用 deploy 和 rw_separate
TP6 不会自动识别你写了 read 数组就开启主从,它只认两个开关:'deploy' => 1(开启分布式部署模式)和 'rw_separate' => true(开启读写自动分离)。这两个缺一不可,且必须放在具体连接配置(比如 'mysql')内部,不能放在顶层或 default 里。
-
default只是连接别名,真正起作用的是它指向的那个连接配置(如'mysql'),所以'deploy'和'rw_separate'必须写在'mysql'配置块里 -
write只能是单个数组,写成数组套数组会直接报错Invalid write database config -
read可以是单个数组(一个从库),也可以是数组套数组(多个从库,TP 默认轮询) - 如果要用加权轮询,得加
'master_balance' => ['weight' => [1, 2]],权重数量必须和read数组长度一致
哪些操作强制走主库?不是所有 SELECT 都能甩给从库
TP6 不解析 SQL,而是靠方法语义和上下文判断路由。看似是读,也可能被拉回主库:
- 调用
->lock(true)、->forUpdate()或原生 SQL 带FOR UPDATE/LOCK IN SHARE MODE→ 强制主库(从库不支持行锁) - 任何事务内执行的查询,哪怕只是
Db::table('user')->find(1)→ 全部走主库(避免主从延迟导致事务内读不到刚写数据) - 手动调用
->master(true)或Db::connect('mysql')->master()→ 明确指定主库 -
Db::execute()、insert()、update()、delete()等写方法 → 无条件走write
Db::name() 和 Db::connect() 的行为差异很关键
Db::name('user') 永远基于 default 所指的连接(比如 'mysql'),它的读写路由完全由那个连接里的 rw_separate 控制;而 Db::connect('mysql_slave') 是绕过内置路由的硬指定,后续所有操作都固定走那个连接,不再轮询或分离。
立即学习“PHP免费学习笔记(深入)”;
- 想临时查某个特定从库(比如做健康检查或灰度流量),用
Db::connect('slave1'),但这个连接必须在connections里单独定义,不能靠read数组动态生成 -
Db::name('user')->select()不会“自动挑从库”,它只看你'mysql'配置里rw_separate是否为true,以及当前是否处于事务/带锁等强制主库场景 - 不要试图用
Db::name('user')->useReadConnection()这类不存在的方法——TP6 没提供这种 API
主从延迟是业务层必须兜底的问题
TP6 不检测延迟,也不提供“强一致性读”开关。一旦出现“刚 INSERT 完立刻 SELECT”,从库很可能还没同步,结果就是查不到或查到旧数据。
- 用户注册后跳转个人页、支付成功后查订单状态、登录后立即读权限字段——这些场景必须主动加
->master(true) - 关联子查询、视图、存储过程展开后的实际 SQL 可能是非确定性的,ROW 格式 binlog 在从库重放时也可能出偏差,这种读也建议走主库
- 别依赖注释(如
/*+ USE_SLAVE */),PDO 和 TP 都不解析它,纯属无效



















