thinkphp的save()写入从库是因为未正确配置读写分离或未显式指定主库;需设置'deploy'=>1且'rw_separate'=>true(5.1)或使用'master'/'slave'数组结构(6.x),并用->master(true)强制走主库。

ThinkPHP 连接主从库后 save() 为什么还是写到了从库?
因为 ThinkPHP 默认不自动识别读写分离场景下的“写操作路由”,save()、insert()、delete() 等方法不会主动切到主库,除非你显式指定或配置了正确的读写分离策略。
常见错误现象:Db::name('user')->where('id', 1)->update(['name' => 'test']) 执行后查不到更新,抓包发现请求发到了从库;或者主从延迟导致刚写完立刻 select 查不到新数据。
- 确认是否启用了读写分离:
'deploy' => 1和'rw_separate' => true必须同时存在,且主库配置在'master' => [...]下,从库在'slave' => [...]中 - ThinkPHP 6.x 要求主库必须是第一个连接配置(即
'connections' => ['mysql' => [...]]中的默认连接需为主库),否则rw_separate逻辑可能失效 - 手动强制走主库:在查询前加
->master(true),例如Db::name('user')->master(true)->update([...])
主从延迟下如何保证「写后立即读」的一致性?
这不是 ThinkPHP 能自动解决的问题,而是架构层要处理的——主从同步有天然延迟,TP 不会帮你等 binlog 回放完成。
典型使用场景:用户注册后跳转到个人页,页面直接查 Db::name('user')->find($id),结果查到旧数据甚至空记录。
- 最简单有效的方式:写操作后,显式用
->master(true)去读,绕过从库 - 避免全局禁用从库(如设置
'read_master' => true),这会让所有读都打到主库,失去读写分离意义 - 如果业务允许,可加一层缓存(如 Redis)标记刚写入的 ID,并在读之前先查缓存判断是否需要强读主库
- 注意:MySQL 的
SELECT ... FOR UPDATE或START TRANSACTION WITH CONSISTENT SNAPSHOT在 TP 中需手写原生 SQL,query()才可控
ThinkPHP 5.1 / 6.x 读写分离配置差异在哪?
5.1 和 6.x 在数据库配置结构和触发时机上不兼容,配错就等于没开读写分离。
关键参数差异:
- ThinkPHP 5.1:靠
'type' => 'mysql'+'deploy' => 1+'rw_separate' => true触发,从库列表放在'slave_no' => 2和'slave' => [ [...], [...] ] - ThinkPHP 6.x:改用
'connections' => ['mysql' => [ 'master' => [...], 'slave' => [[...], [...]] ]],且必须设'rw_separate' => true,'deploy'已废弃 - 6.x 中若只配了一个从库,仍需写成数组:
'slave' => [[...]],写成'slave' => {...}会静默失败 - 5.1 的
db_config.php是返回数组;6.x 的database.php是返回Connection实例配置,不能混用
双向同步不是 ThinkPHP 的事,别指望它自动做主主复制
ThinkPHP 没有、也不该提供数据库级的双向同步能力。所谓“双向同步”,实际是 MySQL 主主复制(MMM、MHA)或中间件(如 Canal + Kafka + 自研消费者)的事。
容易踩的坑:
- 误以为开启
rw_separate就能自动把从库变更反向写回主库——完全不能,TP 只做连接路由,不做数据同步 - 在从库执行
insert()报错Can't execute statement on a read-only MySQL server是正常现象,说明从库确实只读,但这也反向证明你没配错 - 真要双向,得在 MySQL 层配置
auto-increment-offset和auto-increment-increment避免主键冲突,TP 不参与这个过程
真正难的是延迟感知和冲突消解,这部分一旦出问题,TP 再怎么封装也兜不住。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











