Doctrine主从配置需手动定义connections,replica和master非关键字;读操作必须显式指定replica连接,事务中强制走主库,从库延迟需业务层规避。

Doctrine 配置里 replica 和 master 不是关键字,得靠 connections 手动定义
很多人以为 Symfony 的 Doctrine 配置支持开箱即用的主从标签,比如直接写 replica: true,结果发现查库永远走主库——因为 Doctrine 本身不识别这种语义,它只认你明确定义的连接名。
必须在 doctrine.dbal.connections 下显式声明至少两个连接(比如 default 和 replica),再通过 doctrine.orm.entity_managers.default.connection 指向主库,而读操作要手动切到 replica 连接。
- 不要依赖“自动路由”,Doctrine 默认不区分读写流量
-
connections下每个连接需独立配置url、host、user等,不能复用主库配置后只改host - 如果用环境变量,确保
DATABASE_URL_REPLICA和DATABASE_URL各自解析正确,否则启动时报ConnectionException
读操作必须显式指定 replica 连接,EntityManagerInterface 默认只连 default
注入的 EntityManagerInterface 默认绑定的是 default 连接(也就是主库),哪怕你配了 replica,$em->getRepository(User::class)->findAll() 依然走主库。
真正生效的方式只有两种:一是用 Connection 对象直连,二是换一个绑定了 replica 的 EntityManager 实例。
- 推荐方式:在服务配置里定义新 EM,如
doctrine.orm.replica_entity_manager,并设其connection为replica - 临时读取:用
$this->getDoctrine()->getConnection('replica')获取连接,再执行executeQuery() - 别试图用
$em->getConnection()->setHost()动态切 host —— 连接已初始化,改了也不生效
事务内所有查询强制走主库,replica 连接进事务会报 TransactionRequiredException
这是最容易踩的坑:你以为把查询切到 replica 就能减轻主库压力,但只要当前请求里有 @Transactional 注解、或手动调了 $em->beginTransaction(),Doctrine 就会拒绝使用 replica 连接执行任何 DQL 或原生查询。
错误信息典型是:TransactionRequiredException: No transaction is currently active,其实意思是“你拿 replica 连接想干事务里的事,不行”。
- 事务中读写都必须在同一连接上,Doctrine 不支持跨连接事务
- 如果业务逻辑里先写后读(比如创建订单后立即查),哪怕读的是只读字段,也得走主库
- 想规避?只能把读操作拆到事务外,或者用
Connection::fetch*()+ 显式关闭自动 commit,但风险高,不推荐
从库延迟导致数据不一致,read_after_write 场景下不能无脑切 replica
配置完主从,上线后突然发现“刚提交的数据查不到”,不是代码错,是 MySQL 从库延迟。Doctrine 不做延迟感知,replica 连接不会自动 fallback 到 master。
真实业务里,用户注册后跳转到个人页,这个“个人页”的读请求如果走 replica,就可能查不到刚插入的记录。
- 敏感读场景(如刚写完立刻读)建议白名单化:用自定义 Repository 方法,内部强制用
default连接 - 不要依赖数据库层的 semi-sync 复制来保实时性,网络抖动时照样延迟几百毫秒
- 监控
Seconds_Behind_Master是必须的,但告警阈值设成 0 不现实;更实用的是在关键路径加sleep(100)重试(仅调试用)
主从配置本身不难,难的是判断哪条 SQL 该走哪条路——没有银弹,只有根据一致性要求、延迟容忍度、接口幂等性一条条对齐。









