Workerman 中必须复用数据库连接而非每次回调 new PDO,应在 Worker::onWorkerStart 初始化并用 ping 检测重连;禁用持久连接,避免跨回调事务和复用 prepare,不适用协程式异步查库。

Workerman 回调里直接 new PDO 会出问题
Workerman 是常驻内存的进程,每个连接回调都在同一个 PHP 进程里反复执行。如果每次回调都 new PDO,不显式关闭,连接数会持续累积,很快打满 MySQL 的 max_connections,然后报错 SQLSTATE[HY000] [1040] Too many connections。
这不是“连不上”,而是“连太多没释放”。常见于用 onMessage 处理请求时随手 new 一个 PDO 实例就查库的写法。
- 必须复用连接:PDO 实例应作为全局或 Worker 实例属性,在
Worker::onWorkerStart中初始化,而非在onMessage/onConnect中创建 - 避免使用
PDO::ATTR_PERSISTENT => true:持久连接在 Workerman 下反而加剧连接泄漏,PHP-FPM 的生命周期模型和 Workerman 完全不同 - 注意连接失效:MySQL 默认 wait_timeout=28800(8 小时),但长连接空闲超时后,下次 query 会抛
MySQL server has gone away,需加简单重连逻辑
用 PDO 做连接池太重,推荐 mysqli + 长连接复用
Workerman 场景下,不需要完整连接池(如 Swoole 的 Coroutine\MySQL),但需要轻量、可控的复用。mysqli 比 PDO 更容易控制底层 socket 生命周期,且原生支持 MYSQLI_OPT_CONNECT_TIMEOUT 等关键选项。
实操建议:
- 在
Worker::onWorkerStart中创建$this->db = new mysqli(...),并设置$this->db->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3) - 每次查询前用
$this->db->ping()检测连接是否存活,失败则重新 new 一次覆盖旧实例 - 不要在回调里调
$mysqli->close():关了就真没了,下次还得重连,违背复用初衷 - 避免在
onClose或onWorkerStop中 close:Workerman 进程退出时 PHP 自动释放资源,手动 close 反而可能触发 warning
事务和 prepare 在 onMessage 里要格外小心
Workerman 的回调是并发执行的,但单个 Worker 进程是同步阻塞模型——这意味着如果你在 onMessage 里开启事务却不提交或回滚,后续同一进程里的其他请求会卡在 START TRANSACTION 后面,直到超时或进程重启。
典型错误现象:Lock wait timeout exceeded; try restarting transaction 或大量请求延迟突增。
- 事务必须成对出现:有
begin_transaction()就必须有commit()或rollback(),且不能跨回调边界(比如 begin 在 onMessage,commit 在另一个 onMessage) - prepare 语句不能复用跨请求:
$stmt = $mysqli->prepare(...)创建的 stmt 对象绑定的是当前连接上下文,不能存为属性反复 execute;每次都要 prepare → bind → execute → close(stmt close 不影响连接) - 如果确实需要预编译优化,改用
mysqli_query("INSERT INTO t VALUES (?, ?)", [$a, $b])这类模拟 prepare 的方式(依赖 mysqlnd),更安全
协程风格写法在 Workerman 里不成立
看到 Swoole 或 Hyperf 里用 go(function () { db()->query(...) }),就以为 Workerman 也能“异步查库”——这是根本性误解。Workerman 没有协程调度器,mysqli_query 或 PDO::query 全是同步阻塞调用,哪怕你用 pcntl_fork 或 curl_multi 手动模拟,也只会让问题更难排查。
真正能做的只有两件事:
- 把耗时 DB 操作控制在 100ms 内:靠索引、精简字段、避免 JOIN、用缓存兜底
- 把 DB 密集型业务拆出去:用 Redis 队列 + 单独的 CLI 脚本消费,Workerman 只负责收发消息和轻量校验
- 别碰
stream_select+mysql_real_connect手写异步 MySQL:workerman/mysql 已废弃,且 TCP 层状态极难维护,线上踩坑成本远高于收益
连接复用这件事,看着简单,实际要同时盯住进程模型、MySQL 状态变量、PHP 扩展行为三块。漏掉 ping、忘了重连、误关连接、跨回调开事务——任一环节松动,都会在高并发下突然崩掉。










