不能只用一个全局mysql连接,因mysql协议不支持多路复用,协程并发执行query会覆盖响应缓冲区,导致数据错乱、事务冲突及“mysql has gone away”错误。

为什么不能只用一个全局MySQL连接
单连接看似省资源,实际在协程环境下会引发严重数据错乱和事务冲突。MySQL协议本身不支持多路复用,同一连接上并发执行多个 query() 时,后发起的请求可能覆盖前一个的响应缓冲区,导致结果错位或 MySQL has gone away 错误。
更关键的是事务隔离:如果协程 A 启动事务并执行 UPDATE,协程 B 紧接着在同一连接上执行另一条 UPDATE,B 的操作会直接写入 A 的事务上下文,最终提交时只有最后一条生效,A 的变更被静默丢弃。
- 所有协程共享连接 → 无法保证语句执行顺序与事务边界
- 连接状态(如
AUTOCOMMIT、@variables)会被不同协程反复覆盖 - 一旦某个协程异常中断(未
rollback或close),整个连接进入不可预测状态
Swoole\Coroutine\MySQL 单次连接的典型误用场景
直接在协程里每次 new Swoole\Coroutine\MySQL() 并 connect(),看似“干净”,但每秒数百次握手开销会迅速拖垮数据库。实测在 100 QPS 下,平均响应延迟从 8ms 涨到 42ms,Threads_connected 持续飙升至 MySQL max_connections 上限。
这种模式本质是短连接滥用,完全没利用 Swoole 协程的生命周期优势。问题不是“能不能连上”,而是“连得太多太碎”。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 每次
connect()触发完整 TCP 三次握手 + MySQL 认证 + 初始化会话变量 - PHP-FPM 下该问题被进程隔离掩盖;Swoole 常驻进程下暴露无遗
- 错误日志中频繁出现
Connection refused或Too many connections
连接池如何真正复用连接
连接池不是“缓存一个连接”,而是维护一组可互换、状态隔离的连接实例。Swoole 的 PDOPool 或手写 Channel 池,核心逻辑是:获取 → 使用 → 归还 → 复位。归还前会主动执行 RESET CONNECTION(或等效清理),确保下次取出时是干净会话。
官方 PDOPool 还内置断线重连:当 get() 取出的连接已失效,池会自动新建一个补位,业务层无感知。而单连接一旦断开,整个服务就卡死。
- 池容量建议设为并发峰值的 1.2–1.5 倍,而非盲目堆高(如 64 连接池在 200 QPS 下反而因争抢加剧延迟)
- 必须设置
pop()超时(如$pool->pop(500)),避免协程无限等待空池 - 事务中获取的连接,必须在
commit/rollback后显式put(),不能依赖脚本结束自动释放
ThinkPHP6 里误配连接池的常见陷阱
TP6 的 database.php 根本不识别 'pool' => [...] 配置项,这类字段会被框架完全忽略。你看到连接数没降、慢查询照旧,不是池“没生效”,而是压根没走池逻辑。
真要启用池,必须满足两个条件:① 使用 topthink/think-swoole v4+;② 在 config/swoole.php 中配置 'db_pool' => true。否则哪怕代码里写了 Db::pool(),也会 fallback 到默认单次连接。
- 删掉
database.php里所有pool、maxActive、wait_timeout字段,它们只会干扰判断 - 开启
PDO::ATTR_PERSISTENT是另一条路,但它不解决并发事务冲突,仅减少握手——且必须搭配SET SESSION清理语句使用 - 监控是否真走池:查 MySQL
SHOW PROCESSLIST,连接来源应稳定在固定几个 Worker PID,而非每个请求一个新 ID










