doctrine 不会自动重连,pdo 默认连接失败即抛异常;需通过 database_url 添加超时参数或在 doctrine.yaml 中配置 pdo 选项启用 mysqlnd 自动重连,并在应用层手动捕获 driverexception 实现重试逻辑。

Doctrine 本身不提供连接层重试机制,Symfony Messenger 的 retry_strategy 也不作用于数据库连接——它只管消息消费失败后的重试。想让 Doctrine 在 MySQL 连接闪断时自动重连,得靠底层 PDO 配置和应用层兜底。
Doctrine 连接失败时会重试吗?
不会。PDO 默认行为是「连不上就抛异常」,Doctrine 不做任何重试封装。哪怕网络抖动导致 PDOException(如 SQLSTATE[HY000] [2002] Connection refused),也会直接冒泡到控制器或命令行,不会自动重连。
- 常见现象:Docker 启动顺序错乱(PHP 容器先跑、MySQL 容器还没 ready),首次请求报错后整个请求失败
- 根本原因:Doctrine 的
connection是单例,一旦初始化失败就卡死,后续请求仍用同一个失败连接 - 注意:
doctrine.dbal.url配置里的serverVersion或charset错误,会导致连接建立阶段就失败,这类错误也无法靠重试绕过
如何在 PDO 层启用连接重试
只能通过 options 手动注入 PDO 构造参数,利用 MySQL 自身的 mysqlnd 重连能力(需 PHP 编译时开启 --enable-mysqlnd)。
在 config/packages/doctrine.yaml 中添加:
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
options:
# 启用 mysqlnd 自动重连(仅对 MySQL 有效)
1002: 'SET NAMES utf8mb4'
1015: true # PDO::ATTR_EMULATE_PREPARES
1000: true # PDO::ATTR_PERSISTENT → 不推荐,易引发连接泄漏
# 关键:启用自动重连(MySQL 5.6.2+ / 8.0+ 支持)
1017: true # PDO::MYSQL_ATTR_USE_BUFFERED_QUERY → 无关
# 实际生效的是这个(PDO::MYSQL_ATTR_INIT_COMMAND 不是重试开关)
# 真正起作用的是:在连接字符串里加 ?connect_timeout=5&read_timeout=5&write_timeout=5
更可靠的做法是把重试逻辑交给连接字符串参数:
- 在
.env的DATABASE_URL后追加超时与重试相关参数:mysql://user:pass@127.0.0.1:3306/db?serverVersion=8.0&connect_timeout=5&read_timeout=5&write_timeout=5 -
connect_timeout控制 TCP 握手超时,设为 5 秒可避免卡住;但「重试次数」仍由 MySQL 客户端库决定,PDO 不暴露该开关 - 不要依赖
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION来触发重试——它只是让错误变明显,不改变连接行为
应用层兜底:手动重试查询逻辑
当必须容忍短暂 DB 不可用(如蓝绿部署、主从切换),就得在业务代码里显式捕获并重试。Doctrine 不提供 retryableTransaction() 这类工具,得自己写。
示例(在 Repository 或 Service 中):
public function fetchWithRetry(string $id, int $maxAttempts = 3): ?Product
{
for ($i = 0; $i entityManager->find(Product::class, $id);
} catch (DriverException $e) {
if ($i === $maxAttempts - 1) {
throw $e; // 最后一次失败才抛出
}
usleep(500000); // 500ms 后重试
}
}
<pre class="brush:php;toolbar:false;">return null;}
- 只捕获
Doctrine\DBAL\Exception\DriverException及其子类(如ConnectionException),别 catchException—— 会吞掉逻辑错误 - 重试间隔建议用指数退避(
usleep(pow(2, $i) * 100000)),避免雪崩 - 事务内重试要格外小心:已 begin 的事务不能简单重试,必须 rollback 后重新开始
真正容易被忽略的点:Doctrine 的连接池(如果用了 Swoole 或 RoadRunner)和连接复用机制,会让「重试」变得不可预测——连接对象可能已被其他协程污染。这种场景下,重试前必须调用 $this->entityManager->getConnection()->close() 强制释放连接。否则你 retry 的还是那个坏连接。











