读写分离必须在连接层配置,模型层不参与路由;事务内所有操作强制走主库;关联查询默认不继承主模型连接策略;主从延迟需业务层按一致性要求选择master/slave。

读写分离配置必须在数据库连接层完成,模型层不参与路由决策
ThinkPHP 的读写分离不是靠模型自动识别 select 或 insert 语句来路由的,而是由底层连接池根据当前操作类型(query / execute)和配置规则,动态选择主库或从库。模型层只负责拼 SQL,不决定走哪条连接。
常见错误是试图在模型里重写 db() 或拦截 get() 方法手动切库——这会绕过框架的连接复用和事务控制,导致从库查到脏数据、主从延迟时读不到刚写的记录,甚至事务失效。
- 主库配置必须设
'write' => true,从库设'read' => true,否则框架无法识别角色 - 多个从库时,
'read' => ['host1', 'host2']是轮询,不是随机;如需权重,得自定义ReadBalance类 - 显式强制走主库:用
$model->master()->select();强制走从库:$model->slave()->select()
事务内所有查询都走主库,无法“读主库+写从库”
一旦开启事务(Db::startTrans() 或模型中调用 transaction()),后续所有查询(包括 find()、where()->select())都会绑定到事务起始时获取的主库连接。这时候即使配置了从库,slave() 也会被忽略。
典型踩坑场景:用户下单后立即查订单详情,以为加个 ->slave() 能缓解主库压力,结果报错“事务中不能切换连接”或直接查到旧数据——因为事务还没提交,从库没同步。
- 事务中需要“读己所写”,必须用主库,这是强一致性要求,没法妥协
- 想减轻主库压力?把非关键查询(如统计、日志)拆到事务外,再用
->slave() - 跨库事务(主+从)在 TP 中不支持,也不该支持;MySQL 本身也不允许
关联查询(with)默认走主库,需显式指定从库策略
with('profile') 这类关联加载,底层会分别执行主表查询和关联表查询。默认情况下,两个查询都走主库,哪怕你对主模型调用了 ->slave(),关联模型仍按自身配置走——而默认配置是主库。
这不是 bug,是设计:关联模型有自己的数据库配置,不会继承主模型的连接策略。容易误以为“加了 slave 就全走从库”,结果监控发现从库流量几乎为零。
- 让关联也走从库:在关联定义里加
'connection' => 'slave',例如hasOne('Profile')->connection('slave') - 更稳妥的做法:统一在数据库配置里给从库起别名(如
'slave'),然后所有关联都显式指定该连接名 - 注意
withCount和withAvg等聚合关联,同样受此规则约束,不指定就走主库
主从延迟导致数据不一致,模型层无法自动兜底
TP 不提供延迟检测、等待同步或重试机制。比如用户注册后跳转到个人页,->slave()->find() 可能查不到刚插入的记录,因为从库还没同步完。这不是框架缺陷,是 MySQL 主从复制本身的特性。
业务代码如果依赖“写完立刻能读”,又没做任何补偿,就会出现页面空白或 404。有人试图在模型基类里加 sleep(1) 或 while 循环查,这只会拖垮接口响应,且治标不治本。
- 强一致性读(写后即读):一律用
->master(),别省那点主库压力 - 最终一致性读(如列表页、评论流):接受几秒延迟,用
->slave()即可 - 极少数场景需折中:写完后主动触发一次主库
SELECT做确认,再放行后续从库请求
主从延迟不是配置问题,也不是代码能绕过去的。能做的只有分清哪些读必须强一致,哪些可以妥协——这点比怎么写 with 还重要。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










