symfony 7.0 不内置读写分离,需手动配置 doctrine 多连接并显式路由:default 用于写和事务,replica 仅用于纯读;事务中不可切换连接;sticky 行为、主从延迟、故障降级等均需业务层自行实现。

Symfony 7.0 本身不内置读写分离支持,必须靠 Doctrine DBAL 手动配置多连接 + 自定义路由逻辑,否则所有查询都走默认连接。
Doctrine 配置多个连接而非“读写分离”开关
Symfony 的 doctrine.yaml 不提供 read/write 嵌套语法(那是 Laravel 的机制)。你得显式定义两个独立连接,比如 default(主库)和 replica(从库),并确保它们使用不同 host/port/credentials:
-
default连接用于写操作和事务内所有查询 -
replica连接仅用于纯读请求,需在代码中显式调用doctrine.dbal.replica_connection服务 - 不能只改环境变量或 URL —— 必须在
config/packages/doctrine.yaml中完整声明两套参数 - 若用 DSN,
replica的 DSN 必须指向从库地址,且不能与default共享同一 DSN 变量
手动路由 SELECT 到 replica 连接
Doctrine 不会自动解析 SQL 类型来选连接。你必须在业务代码里主动决定用哪个连接:
- 写操作:始终用
doctrine.dbal.default_connection(即default) - 读操作:显式获取
doctrine.dbal.replica_connection,再调用executeQuery()或构造QueryBuilder - Eloquent 风格的自动路由不存在 ——
UserRepository::findAll()默认仍走default,除非你重写方法并注入replica连接 - 事务中禁止切换连接:一旦
beginTransaction()被调用,后续所有查询(包括SELECT)必须在同一连接上执行,否则抛出DriverException
sticky 行为需自行实现
Laravel 的 sticky 是框架层特性,Symfony + Doctrine 没有等价配置项。如果业务需要“写后立刻可读”,你得自己维护请求级状态:
- 在写操作后(如
INSERT或UPDATE)设一个请求作用域的 flag,比如存入RequestStack或Session(注意无 session 场景) - 后续读请求检查该 flag,若存在则跳过
replica,强制走default - 这个 flag 必须在请求结束前清除,否则影响下一个请求
- 队列任务、CLI 命令不适用此逻辑 —— 它们没有 HTTP 请求上下文
主从延迟与一致性风险必须由业务兜底
Doctrine 不处理同步延迟问题,也不会自动回退或重试。这意味着:
- 从库查不到刚写入的数据是正常现象,不是配置错误
- 关键路径(如订单创建后立即查状态)应直接读
default,别依赖replica - 不能指望
replica连接具备故障转移能力 —— 它挂了就是ConnectionException,需捕获并降级到default(但会加重主库压力) - 监控从库延迟(如
SHOW SLAVE STATUS的Seconds_Behind_Master)必须独立做,Doctrine 不暴露该指标
最易被忽略的是:Doctrine 的 connection service 名称必须与 yaml 中定义的完全一致,且 replica 连接要单独启用 driver 和 url(或拆解后的各字段),漏掉任意一项都会导致连接初始化失败,而不是静默 fallback。











