连接失败重试需手动实现,非框架默认行为;必须开启PDO异常模式、新建连接对象、避免雪崩重连,事务中需重建事务上下文,长连接场景应优先健康检查而非重试。
ThinkPHP 或原生 PDO/mysqli 都不会在连接失败后自动重试。所谓“重试”是业务层逻辑,不是底层驱动能力。你写的 Db::connect() 或 new PDO() 失败了,就真的失败了——除非你自己包一层 try/catch 并重试。
常见错误现象:代码里写了 'break_reconnect' => true 却没生效;或用 Swoole 长连接时,连接断开后请求直接 500;又或者事务中报 MySQL server has gone away 后没重连就抛异常。
SQLSTATE[HY000] [2006])有意义,语法错误或主键冲突重试毫无作用usleep(100000)(100ms),避免雪崩式重连$pdo 或 $conn
break_reconnect 基本失效,得监听 onClose 事件手动重建PDO::ATTR_ERRMODE 必须设为 PDO::ERRMODE_EXCEPTION,否则连接失败只返回 false,catch 根本捕不到异常,重试逻辑形同虚设。
典型配置漏项:charset=utf8mb4 写在 DSN 里,但 PDO::ATTR_ERRMODE 没设;或用了 mysql:host=localhost,结果 PHP 尝试走 Unix socket 却路径不对,报错 No such file or directory,你以为是密码错,其实只是 host 解析问题。
立即学习“PHP免费学习笔记(深入)”;
mysql:host=127.0.0.1;port=3306;,绕过 localhost → socket 的歧义mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=test
caching_sha2_password 会导致 PDO 连接失败且不报具体原因事务中连接断开,框架会自动 rollback,但事务状态已破坏。此时若直接重试原 Db::transaction() 回调,会报 There is no active transaction。
正确做法是把整个事务逻辑包进循环,每次失败都新建事务上下文。
for ($i = 0; $i < 3; $i++) { try { Db::transaction(function () { Db::table('order')->where('id', 123)->update(['status' => 'paid']); }); break; } catch (thinkdbexceptionPDOException $e) { if ($i === 2) throw $e; usleep(100000); }}
break_reconnect 在事务中不触发重连,它只作用于单条查询执行前的连接探活$pdo->beginTransaction() 和 $pdo->commit() 也得在每次循环里重新调用PHP-FPM 场景下每次请求都是新进程,连接失败重试尚可接受;但在 Swoole Worker 或 Laravel Octane 中,一个连接复用几十秒甚至几分钟,break_reconnect 不起作用,靠重试反而掩盖真实问题。
真正该做的是:连接空闲超 30 秒后主动 ping,失败则 close 并重建;或在每次查询前加 $pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS) 判断是否活跃(部分驱动支持)。
mysqli_ping(),它在某些 MySQL 版本和 mysqli 驱动组合下返回 true 却实际不可用SELECT 1,并设置 PDO::ATTR_TIMEOUT 防卡死wait_timeout 和 interactive_timeout MySQL 参数必须与应用层探活间隔匹配,否则刚 ping 完就断wait_timeout、Swoole 的 worker_num、以及你自己的业务 SLA 里。