Laravel 的 DB::connection() 不自动重连,需显式捕获 PDOException 错误码 2006/2013 后调用 DB::reconnect() 并重试查询,且避免在事务中使用;全局重试需封装函数或自定义 Builder,优先优化 MySQL wait_timeout 和连接池。

连接断开时 DB::connection() 不自动重连
默认情况下,Laravel 的 PDO 连接一旦因网络抖动、MySQL 服务重启或 wait_timeout 超时而断开,后续查询会直接抛出 PDOException: SQLSTATE[HY000] [2006] MySQL server has gone away,不会尝试重连。这不是 Laravel “忘了重试”,而是 PDO 本身不维护连接活性,Laravel 也未在底层封装自动重连逻辑。
实操建议:
- 不要依赖 DB facade 或模型静态方法的“隐式重连”——它们没有这种能力
- 重连必须显式触发,常见做法是在查询前检查连接状态,或在异常捕获后手动重连
- 注意:调用
DB::reconnect()仅重置当前连接实例,若已存在活跃事务(DB::transaction()中),重连会导致事务上下文丢失,直接报错
DB::reconnect() 怎么安全调用
这个方法只在连接真正断开后才有效,且必须在查询失败后、下一次查询前调用。它不会自动检测是否需要重连,也不会帮你重试原 SQL。
实操建议:
- 在
try/catch中捕获PDOException,判断错误码是否为2006或2013(常见断连标识) - 调用
DB::reconnect()后,需重新执行原操作(不能靠它“续上”上次失败的查询) - 避免在循环中无条件调用
DB::reconnect(),它会新建 PDO 实例,频繁调用可能触发 MySQL 的max_connections限制 - 示例片段:
try { return DB::table('users')->where('id', 1)->first(); } catch (\PDOException $e) { if (in_array($e->getCode(), ['2006', '2013'])) { DB::reconnect(); return DB::table('users')->where('id', 1)->first(); } throw $e; }
想全局生效?绕不开 DB::listen() 和自定义 Query Builder
Laravel 没有内置的“连接失败自动重试 N 次”开关。要实现接近“自动”的效果,得在查询生命周期里插桩,但要注意:监听器本身不能改写执行逻辑,只能记录或触发副作用。
实操建议:
- 用
DB::listen()可以捕获所有查询,但它在查询**之后**触发,无法拦截并重试失败查询 - 真正可行的是封装一个带重试的查询函数,比如
retryQuery(fn() => DB::table(...)->get()),内部用for循环 +catch+DB::reconnect() - 如果用 Eloquent,可覆盖模型的
newQuery()方法,返回自定义 builder,把重试逻辑下沉到底层 - 注意:重试次数建议 ≤ 3,避免雪崩;重试间隔应加
usleep(10000)避免瞬时重压
配置文件里 'options' 加 PDO::ATTR_ERRMODE 没用
有人试图在 config/database.php 的 MySQL 配置里加 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,以为能“激活重连”。其实这个选项只控制错误是否抛异常,和连接管理完全无关。PDO 的连接复用、断开、重连由 PHP 生命周期和连接池机制决定,不是靠属性开关控制的。
实操建议:
- 确认 MySQL 服务端
wait_timeout值(通常 28800 秒),如果应用空闲时间常超 8 小时,优先调大该值,比代码重连更治本 - 生产环境用连接池(如 PgBouncer / ProxySQL)或长连接代理,比在 PHP 层做重连更稳定
- 如果用 Laravel Octane,注意 Swoole 进程常驻特性会让 PDO 连接长期存活,反而更容易遇到过期连接问题——此时必须主动 ping 或定期 reconnect
show processlist 和慢日志,再动手加重试。


















