ThinkPHP 5.1 读写分离下仍会脏读,因其采用语句级路由,不感知业务上下文,SELECT 默认走从库且不自动规避主从延迟;@readmaster 注解非原生支持,需手动实现连接切换;唯一可靠方式是显式使用 Db::connect('master') 或模型指定 connection。

ThinkPHP 5.1 读写分离下为什么还会脏读
不是配置了 'deploy' => 1 就自动解决一致性问题。主从延迟真实存在,Db::table('user')->where('id', 1)->find() 这类查询默认走从库,而刚在主库插入/更新的数据还没同步过去,结果就查到旧值或空记录。
根本原因在于:TP5.1 的读写分离是「语句级路由」,不感知业务上下文。它只看当前查询类型(SELECT/INSERT/UPDATE/DELETE),不判断这条 SELECT 是否紧跟着一个刚发生的写操作。
- 主从延迟不可忽略:即使
Seconds_Behind_Master显示为 0,网络抖动、从库慢查询、大事务回放都可能造成瞬时延迟 - 事务内读取仍可能走从库:TP5.1 不保证事务中的 SELECT 强制走主库,除非显式干预
- 模型和 Db 门面混用时连接不一致:比如
User::create()走主库,但后续Db::table('log')->select()可能走从库——它们压根不是同一个 PDO 实例
@readmaster 注解在 TP5.1 中根本不存在
别被网上某些“自定义注解教程”误导。@readmaster 不是 ThinkPHP 5.1 原生支持的语法,框架本身没有注解解析器,也不会扫描控制器方法上的这类标签并自动切换连接。
所谓“加个注解就强制读主库”,实际要靠你自己实现中间件或基类方法拦截,否则运行时直接忽略,毫无效果。
立即学习“PHP免费学习笔记(深入)”;
- TP5.1 没有内置注解处理器,
@xxx在 PHPDoc 中只是注释,不会触发任何逻辑 - 社区有第三方扩展(如
think-annotation),但需额外安装、配置,并且与 TP5.1 的连接管理不天然兼容 - 即使你写了注解,没配套的连接切换代码,查询照样发往从库
真正可用的强制读主库方式(TP5.1)
必须显式指定连接,绕过读写分离的自动路由。最可靠的是用 Db::connect('master') 或模型指定连接名。
- 原生查询强制走主库:
Db::connect('master')->table('user')->where('id', 1)->find() - 模型层强制走主库:在模型类中声明
protected $connection = 'master',或临时切换(new User())->connection('master')->find(1) - 事务内所有操作天然走主库:
Db::transaction()闭包里无需额外指定,但前提是事务启动前没执行过从库查询(否则连接已惰性初始化为从库) - 注意连接标识名:确认
database.php中主库配置的 key 是'master'还是'mysql',别写错导致连接失败
容易被忽略的坑:连接复用与惰性初始化
你以为 Db::connect('master') 每次都新建连接?不是。TP5.1 默认复用连接实例,但「复用」的前提是连接已创建且未关闭。如果前面某个操作触发了从库连接初始化,后续再调 Db::connect('master') 可能仍拿到旧连接(尤其在 Swoole 长连接场景下)。
- 连接惰性初始化:
Db::connect('master')第一次调用才真正建立 PDO,之后返回缓存实例 - 长连接污染风险:Swoole Worker 处理完请求后未重置连接上下文,下一个请求可能沿用上一个的从库连接
- 解决方案:在关键读主操作前加
Db::close('slave')(如果有从库连接名),或统一用Db::connect('master')->query(...)显式指定,避免依赖全局状态
Db::connect('master'),而不是寄希望于注解或配置自动生效。



















