thinkphp报sqlstatehy000 connection timed out,八成是连接池等待可用连接超时,而非网络不通;需检查break_reconnect与pdo::attr_timeout是否同时配置、事务是否未正确提交/回滚导致连接泄漏。

ThinkPHP 报 SQLSTATE[HY000] [2002] Connection timed out,八成不是 MySQL 没开或网络不通,而是框架连接池卡在等连接——得先分清是「连不上」还是「等不到」。
查清错误来源:Connection timed out 到底是谁在超时
这个错误名极具误导性。它通常不是 PDO TCP 连接失败(那会报 php_network_getaddresses: getaddrinfo failed 或直接卡死),而是 ThinkPHP 的连接管理器在 Db::connect() 时,反复尝试从连接池取一个可用连接,等了太久就抛出这个异常。
- 先看日志里是否伴随
MySQL server has gone away:有,说明连接曾建上但被 MySQL 主动断开了(wait_timeout生效) - 如果首次请求就报错,且
host配的是域名(如db.example.com),立刻换成 IP:DNS 解析慢会直接拖垮PDO::ATTR_TIMEOUT的计时起点 -
host写localhost也要小心:Linux 下可能走 Unix socket,绕过connect_timeout设置,改用127.0.0.1
配对生效的两个关键配置:break_reconnect 和 ATTR_TIMEOUT
break_reconnect => true 不是独立开关,它只在连接已建立、执行 SQL 时断开才触发重连;而 PDO::ATTR_TIMEOUT 控制的是初始建连阶段的秒级等待。二者必须同时存在,缺一不可。
- 在
database.php的对应连接配置里(比如'mysql'),显式写:'params' => [ \PDO::ATTR_TIMEOUT => 3 ], 'break_reconnect' => true, 'break_match_str' => ['2006', 'MySQL server has gone away'] - 自定义连接(如
Db::connect('log'))必须单独配,不会继承default的params - 别用
Db::connect(['dsn' => '...'])临时创建连接:这种写法无法传入params,PDO 默认无限等待
长连接失效的真因:persistent 不等于永不断开
'persistent' => true 只让 PDO 复用底层 socket,但 ThinkPHP 的 Connection 类在请求结束时仍会调用 $pdo->close(),导致 persistent 形同虚设。
- FPM 模式下,同一 worker 进程内复用才有效;Apache prefork 基本无效
- 更可靠的做法是关掉
persistent,改用连接池预热:启动时用think\Cache存几个活跃连接,再通过Db::setConnection()注入 - 队列任务(如 thinkphp-queue)必须额外注意:
wait_timeout默认 8 小时,但实际空闲 60 秒就可能被 MySQL 断开——务必配break_reconnect,否则下次取连接直接报错
连接泄漏高发区:事务和闭包里藏着定时炸弹
事务嵌套、Db::transaction(function () { ... }) 是连接泄漏重灾区。一旦闭包里抛未捕获异常、return 提前退出、或用了 exit,连接对象就滞留在内存中,不再归还池子。
- 用
Db::startTrans()时,必须配try/catch/finally,确保Db::commit()或Db::rollback()总被执行 - 避免在事务闭包里调用
die()、exit(),或 throw 未 catch 的Exception - 检查自定义模型的
initialize():如果里面调了Db::connect()却没保存引用,每次实例化都在新建连接
最易被忽略的一点:所有连接配置项(params、break_reconnect、wait_timeout)都按「连接名」隔离,复制粘贴时漏掉任意一项,那个连接就处在裸奔状态。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











