MySQL 连接数打满怎么办:连接池思路与 PHP-FPM 配比计算
MySQL 连接数打满,绝大多数情况不是 `max_connections` 设小了,而是 PHP-FPM 的最大进程数和数据库可承受连接数之间从来没做过配比——正确顺序是先用内存算出 `max_connections` 的安全上限,再反推 PHP-FPM 的 `pm.max_children`,两者取小值,最后才轮到考虑连接池。
先分清两种「打满」:真忙还是连接被占着
判断依据是两个状态值,不是 `Threads_connected` 本身。
在 MySQL 里执行:
SHOW STATUS LIKE 'Threads_connected'; -- 当前建立的连接数
SHOW STATUS LIKE 'Threads_running'; -- 真正在执行的连接数
SHOW STATUS LIKE 'Max_used_connections';-- 历史峰值
SHOW STATUS LIKE 'Connection_errors_max_connections';
SHOW PROCESSLIST; -- 看是 Query 还是 Sleep
结论:`Threads_connected` 高但 `Threads_running` 只有个位数,说明连接被"占着不用"(持久连接、长事务、连接池空闲连接),问题在客户端;两者同时飙高,才是慢 SQL 把连接池堵死了,要先优化 SQL 而不是调大连接数。`Max_used_connections` 长期贴着 `max_connections`,才算真的不够用——MySQL 5.7 和 8.0 的默认值都是 151。
第一步:把 max_connections 算成一个有依据的数
**结论:max_connections 的上限由内存决定,不由意愿决定,每连接大约要预留 5–10MB。**
每个 MySQL 连接会按需分配 `thread_stack`、`sort_buffer_size`、`join_buffer_size`、`read_buffer_size`、`binlog_cache_size` 等缓冲,这些不是全量预分配,但峰值会真实占用内存。粗略可用公式:
单连接内存 ≈ thread_stack + sort_buffer_size + join_buffer_size + read_buffer_size
可用连接内存 = 物理内存 - 系统预留 - innodb_buffer_pool_size - 其他常驻
max_connections = 可用连接内存 / 单连接内存
举例:8 核 16G 的机器,系统留 1G,`innodb_buffer_pool_size = 5G`,每连接保守按 8M 算,剩约 10G ÷ 8M ≈ 1200,但绝不能顶格设,取 300–500 留足突发余量。设置为更大的值不会让你撑住更多并发,只会让 MySQL 在内存耗尽时被杀掉。
第二步:从 PHP-FPM 侧反推 pm.max_children
结论:`pm.max_children` 必须同时满足内存约束和连接约束,取两者较小值。
内存约束:max_children = 可用于 PHP 的内存 / 单个 php-fpm 进程峰值内存
连接约束:max_children = (max_connections - 预留连接) / PHP 节点数
单进程内存用 `top` 看 php-fpm 的 RES 列,或 `ps -ylC php-fpm --sort:rss`,轻量论坛通常 30–60MB,装了一堆插件可能到 100MB 以上。
接上面的例子:16G 减掉系统 1G、MySQL 约 7G,还剩 8G 给 PHP;单进程 60M → 内存约束约 133。连接约束方面 `max_connections = 300`,预留 30 给后台任务、cron、监控、从库复制,单机场景剩 270。取小值,`pm.max_children = 120` 左右是合理的。
配套参数别忘:
pm = dynamic
pm.max_children = 120
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 800 ; 防止进程内存膨胀,跑够请求数就回收
`pm.max_requests` 设 0(永不回收)是内存泄漏事故的常见来源。
第三步:连接池什么时候才真的需要
**结论:连接池解决的是"客户端连接数远大于数据库可承受并发"的错配,PHP-FPM 短连接模型下它通常不是第一选择。**
如果你的架构是标准的「Nginx → PHP-FPM → MySQL」短连接,每个请求用完即释放,那么连接数上限天然等于进程数,做对上面两步配比就够了。像 Clara BBS 这类无框架轻量 PHP 系统(PHP 7.4–8.5 + MySQL 5.7+,上传即装、无需 Composer 和常驻进程、兼容宝塔面板),跑的就是这种模型,调优重点就是配比计算,而不是引入中间件。
真正需要连接池的场景是:前端连接数动辄几千(大量应用节点、常驻进程服务),此时用 ProxySQL 或 MySQL Router 挡在中间,前端承接大量客户端连接,后端复用少量真实 MySQL 连接。用 `CREATE USER ... WITH MAX_USER_CONNECTIONS 20` 也能给单个账号限死连接数。
另外提醒一个坑:PHP 的持久连接(`pconnect`)在 PHP-FPM 下会让每个 worker 长期握着一个连接,连接数等于进程数且大量处于 Sleep,反而更容易撞上限。要用就得先确认 worker 数远小于 `max_connections`。
第四步:让连接"用完就还"的日常动作
结论:连接数治理的收益,八成来自缩短单次占用时间,而不是扩大上限。
- 开慢查询日志(`slow_query_log = ON`,`long_query_time = 1`),先干掉占着连接不走的 SQL;
- `wait_timeout` / `interactive_timeout` 适当调小(如 300–600),让空闲连接尽快回收;
- 同一次请求内复用同一个 PDO/MySQLi 实例,别在一个脚本里反复 new 连接;
- 监控 `Connection_errors_max_connections`,它一涨就说明已经在拒连了,比事后翻日志可靠得多;
- 读多的论坛场景加只读从库做读写分离,是比调大 `max_connections` 更划算的一步。
总结一句:先用内存定 `max_connections`,再用「内存约束」和「连接约束」两个公式取小值定 `pm.max_children`,`pm.max_requests` 设成有限值,日常盯住 `Threads_running` 和慢查询。连接池是架构层面的解法,不是连接数打满时的第一个按钮。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





