乐于分享
好东西不私藏

从源码看MySQL [2]|连接管理:max_connections为什么不能设太大

从源码看MySQL [2]|连接管理:max_connections为什么不能设太大

本系列:《高性能MySQL第三版》源码级伴读

实验环境:MySQL 8.0.46-debug

本文是 Phase 1 第 2 篇:连接层


一、场景:Too many connections 但 CPU 很低

❓ 场景:凌晨告警 ERROR 1040 (HY000): Too many connections,但 top 看 CPU 只有 30%。

为什么 CPU 不高,连接却爆了?因为 idle 连接在吃内存,不在吃 CPU。

我们这一篇就解决两个问题:

  1. 一个 idle 连接到底吃多少内存?
  2. max_connections
     到底该怎么设?

二、书上怎么说?——《高性能MySQL》关于连接管理的观点

《高性能MySQL第三版》在第3章关于"服务器设置"中明确指出:

「MySQL 默认采用 one-thread-per-connection 模型,每个连接对应一个线程。」

书里也提到几个关键点

  • 连接是有成本的(线程栈、内存等)
  • thread_cache_size
     影响新建连接的性能
  • wait_timeout
     决定空闲连接的存活时间

书没量化的是:

  • ❌ 一个连接真实吃多少内存(栈、THD结构、buffer等)
  • ❌ 多少连接是安全的(不是看 max_connections,是看 RAM 上限)
  • ❌ 线程缓存真正能省多少 ms

三、源码:MySQL 8.0 的连接模型

MySQL 8.0 默认是 one-thread-per-connection。源码入口在:

C++
"color: #6a737d; font-style: italic;">// sql/conn_handler/connection_handler_per_thread.cc:246staticvoid *handle_connection(void *arg) {"color: #6a737d; font-style: italic;">// 每个新连接分配一个线程,入口函数"color: #6a737d; font-style: italic;">// 内部调用 do_command() 循环处理客户端请求 }"color: #6a737d; font-style: italic;">// sql/conn_handler/connection_handler_manager.cc:65"color: #6a737d; font-style: italic;">// 枚举:SCHEDULER_ONE_THREAD_PER_CONNECTION(默认)

关键结构

  • THD
    (Thread Handler Descriptor):每个连接对应一个,存储会话状态、解析树等
  • Per_thread_connection_handler
    (connection_handler_impl.h:42):实际管理线程创建
  • thread_cache
    :线程复用,避免反复创建/销毁

内存开销估算(一个 idle 连接):

  • THD 结构本身:~50-100KB
  • 线程栈(DEFAULT_THREAD_STACK=1MB,见 include/my_thread.h:55):1MB
  • net_buffer_length
    (默认 16KB):16KB
  • 各种 cache(sort_bufferjoin_buffer 等):只在需要时分配
  • 总计:一个 idle 连接约 1-2MB
    (线程栈占大头)

四、实测:观测我们的实例

SQL
-- 查看当前连接配置SHOWVARIABLESWHERE Variable_nameIN ('max_connections','thread_cache_size','wait_timeout','max_connect_errors','thread_handling');-- 实验输出(在MySQL8.0.46-debug实例上实测):-- +--------------------+---------------------------+-- | Variable_name | Value |-- +--------------------+---------------------------+-- | max_connect_errors |100 |-- | max_connections |151 |-- | thread_cache_size |9 |-- | thread_handling | one-thread-per-connection |-- | wait_timeout |28800 |-- +--------------------+---------------------------+-- 查看连接实时状态SHOWSTATUSWHERE Variable_nameIN ('Threads_connected','Threads_created','Threads_cached','Threads_running','Max_used_connections');-- 实验输出(在MySQL8.0.46-debug实例上实测):-- +----------------------+-------+-- | Variable_name | Value |-- +----------------------+-------+-- | Max_used_connections |1 | ← 历史峰值-- | Threads_cached |7 | ← 当前缓存中的线程-- | Threads_connected |1 | ← 当前连接数-- | Threads_created |8 | ← 累计创建过的线程-- | Threads_running |2 | ← 当前正在运行的线程-- +----------------------+-------+-- 当前 mysqld 进程内存-- +------+----------+------------+----------------------------------+-- | PID | RSS(MB) | VSZ(MB) | CMD |-- +------+----------+------------+----------------------------------+-- |2671620 |447 |3348 | mysqld --basedir=/usr/local/mysql|-- +------+----------+------------+----------------------------------+

解读

  • 当前只用了 447MB 内存(Buffer Pool 默认 128MB + 基础开销)
  • 如果把 max_connections 设为 1000,并且真的有 1000 个 idle 连接:

- 1000 × 1MB(仅线程栈)= 1GB 仅连接开销

- 每个连接如果再用了 sort_buffer / join_buffer(各 256KB),瞬间就吃 1.5GB+


五、实战启示:max_connections 该怎么设?

1. 反推公式

最大可设置 = (可用RAM - MySQL自身开销 - Buffer Pool) / 每连接最大内存

举例:服务器 64GB,Buffer Pool 32GB,系统预留 4GB,MySQL其他开销 2GB:

可用 = 64 - 32 - 4 - 2 = 26GB 每连接按 4MB 算:26000/4 = 6500 实际生产建议:max_connections = 2000-3000

2. 连接风暴怎么防?

SQL
--1. 缩短短闲连接存活时间(默认8小时太长)SETGLOBAL wait_timeout =300;-- 5分钟--2. 调大线程缓存SETGLOBAL thread_cache_size =100;-- 减少新线程创建--3. 监控 Threads_created 增长SHOWSTATUSLIKE'Threads_created';-- 如果这个值增长很快 → 线程缓存不够--4. 看 Max_used_connectionsSHOWSTATUSLIKE'Max_used_connections';-- 这是历史峰值,应该 < max_connections 的80%

3. 应用层用连接池

永远不要让应用直连 MySQL。连接池(HikariCP、Druid)保持 10-50 个连接,应对 1000 个并发请求。


六、本篇对应解决的运维问题

问题
答案
Too many connections
 告警
先看 Max_used_connections 是否真的满了,再看 RAM
CPU 低但连接数满
idle 连接吃内存不占 CPU,调大 max_connections 前先加内存
应用启动慢
thread_cache_size
 太小,频繁创建新线程
内存被连接吃光
用连接池 + wait_timeout

七、关键参数速查

参数
默认值
调优建议
风险
max_connections
151
根据 RAM 算上限(公式见上)
太大易 OOM
thread_cache_size
auto
监控 Threads_created 增长
太大占内存
wait_timeout
28800(8h)
300-600s
太短会误断应用
max_connect_errors
100
内网可设 10000
太小会误封 IP

八、本篇作业 & 下期预告

作业

  1. 执行 SHOW VARIABLES LIKE 'max_connections'; 看你的设置
  2. 算一下你机器的 max_connections 合理上限(用第 5 节的公式)
  3. 用 ps -o rss -p $(pgrep mysqld) 看当前 RSS,按公式反推"还能再开多少连接"

下期预告

Phase 1 第 3 篇|SQL 解析:你的 SQL 是怎么变成语法树的——连接建立后,SQL 文本就送到解析器。我们会看到 Yacc/Bison 生成的解析器干了什么,以及预编译语句为什么快(用真实验证)。


如果觉得有帮助,请点赞、在看、转发三连。下一站,我们进入解析器。


⚖️ 版权声明

  • 本文为原创技术分析文章,非官方授权翻译或转载
  • 对《高性能MySQL第三版》的引用仅限简短概括,用于评论和教学,符合《著作权法》第24条合理使用
  • MySQL 源码引用遵循 GPL v2 开源协议,用于教育目的的代码片段分析不构成"分发"
  • "MySQL"为 Oracle Corporation 注册商标,本文使用仅为技术描述
  • 建议购买原书深入学习:《高性能MySQL》第三版,电子工业出版社
  • 未经作者许可,禁止转载或用于商业用途