刚写入查不到主因是读写分离导致查询发至未同步的从库,非缓存问题;应显式用Db::master()指定主库查询,或合理使用read_master配置与事务保障一致性。

刚写入就查不到,不是缓存问题,是读库选错了
常见现象:Db::name('order')->insert($data) 后立刻 Db::name('order')->where('sn', $sn)->find() 返回空。这不是 Redis 缓存没刷新,而是 TP 默认把 find() 发到了从库,而主库刚写的那条记录还没同步过去。
根本原因在于:读写分离策略只看 SQL 类型,不感知业务上下文。它不会因为你刚 insert 过,就自动把下一条 select 切回主库。
- 别急着清缓存或设缓存过期时间——先确认查询到底走的哪台库。用
getLastSql()看执行日志,如果显示连接的是从库地址,说明就是路由问题 - 不要在模型里加
->cache(true)试图绕过延迟——缓存只会让“查不到”的结果更持久 - 最直接的办法是显式指定主库:用
Db::master()->name('order')->where('sn', $sn)->find()
ThinkPHP 的 read_master 配置容易被误用
配置项 'read_master' => true 看起来像“写完自动切主库读”,但实际行为是:**只要对某张表执行过写操作,当前请求后续对该表的所有读,都强制走主库**。
这听着合理,但有两个隐蔽坑:
立即学习“PHP免费学习笔记(深入)”;
- 它只对“同一张表”生效。比如你
insert了user表,再查user_profile表,依然会走从库 - 它按请求生命周期生效,不是按事务。如果一个请求里有多个无关写操作,整张表的读都会被锁死在主库,可能把本该分流的读压回主库
- 它不解决跨请求一致性。比如用户 A 注册后跳转到详情页,详情页是新请求,
read_master不起作用
缓存层怎么配合读写分离才不放大延迟
用 Redis 做二级缓存时,如果缓存 key 和数据库读路由不协同,反而会让延迟更难察觉。
典型错误做法:写完主库后只删缓存,但下次读仍发给从库,查到旧数据又写回缓存——形成“脏缓存闭环”。
- 强一致场景(如订单详情),缓存 key 应绑定主库读逻辑。例如:查
order:123时,先走Db::master(),再写缓存;而不是删缓存后任由下一次读从从库加载 - 避免用“查库 → 写缓存”这种懒加载模式处理关键路径。应改为“写主库 → 写缓存 → (可选)异步更新从库对应缓存”
- 缓存失效时间不要简单设为“略大于同步延迟”。MySQL 的
Seconds_Behind_Master是瞬时值,网络抖动、大事务都可能导致尖峰,建议结合监控动态调整
事务内读写天然一致,但别指望它覆盖所有场景
Db::transaction(function () { Db::name('user')->insert($u); $u = Db::name('user')->where('id', $id)->find(); }) —— 这段代码里的 find() 必定走主库,且能查到刚插入的数据。
但它只在事务块内有效,而且有代价:
- 事务会占用主库连接,高并发下容易打满连接池
- 不能用于跨服务调用。比如支付回调写主库,之后通过 HTTP 请求订单服务查单,事务已结束,
read_master或master()都不生效 - TP 的事务不支持跨库。如果你在事务里混用主从连接(比如写主库 + 查从库字典表),会出错或行为未定义
真正难处理的,永远是那些“写在一个请求,读在另一个请求”的链路。这时候得靠业务标识(如订单号)、请求级上下文透传,或者干脆接受最终一致性,用消息队列补漏——缓存只是其中一环,不是银弹。



















