net_buffer_length仅控制MySQL服务端每个连接的初始网络缓冲区大小(默认16KB),不决定查询结果缓存限制;真正影响结果完整返回的是max_allowed_packet,而GROUP_CONCAT()等函数受group_concat_max_len约束。

net_buffer_length 决定单次内存峰值
MySQL Server 层不缓存完整结果集,而是用一块固定大小的内存缓冲区暂存待发送数据,这块区域叫 net_buffer,大小由参数 net_buffer_length 控制(默认 16KB)。只要客户端能及时消费,这块内存就反复清空复用。
常见错误现象是误以为“查 10GB 表 = 占用 10GB 内存”,其实真正被 Server 层独占的查询内存上限就是 net_buffer_length 值。哪怕你 SELECT * 扫描 200GB 表,Server 层最多只吃掉这 16KB(或你调大的那个值)。
- 可通过
SHOW VARIABLES LIKE 'net_buffer_length';查看当前值 - 该参数不能动态修改,需重启生效;线上建议设为 64K–256K,视网络吞吐和客户端接收能力权衡
- 注意:它只管“发出去前”的那一小段,不包含排序、JOIN 或临时表等其他内存消耗
socket send buffer 满了就停,不是硬塞
当 net_buffer 写满后,MySQL 调用底层 socket 的 send() 发送数据。如果内核 socket send buffer 已满(默认通常 212992 字节,即约 208KB),系统会返回 EAGAIN 或 WSAEWOULDBLOCK,此时 MySQL 主动暂停读取新行,进入等待状态。
这意味着:不是“服务器拼命往网卡塞数据”,而是“发不出去就歇着”。所以即使客户端挂起或网络慢,内存也不会持续增长——流程卡在 I/O 等待上,而非内存分配上。
- 可通过
cat /proc/sys/net/core/wmem_default查 Linux 默认值 - 这个 buffer 是内核级的,MySQL 无法直接配置,但可通过
net.core.wmem_defaultsysctl 调整(需 root 权限) - 若看到
SHOW PROCESSLIST中状态长期为sending to client,基本就是卡在这一步
SSCursor 流式游标绕过客户端内存陷阱
很多内存爆炸实际发生在客户端,比如 Python 用 cursor.fetchall() 把全部结果加载进本地内存。PyMySQL 的 SSCursor(Server-Side Cursor)能解决这个问题:它让 MySQL 保持结果集在服务端,客户端按需逐行 fetch,避免一次性 hold 全量数据。
关键不是“MySQL 不爆”,而是“客户端别自己爆”。普通 Cursor 会在执行 execute() 后立刻把全部结果拉到本地;SSCursor 则只建通道,fetchone() 才触发一次网络读取。
- 初始化时用
pymysql.connect(..., cursorclass=pymysql.cursors.SSCursor) - 必须配合
fetchone()或迭代游标使用,fetchall()会失效并报错 - 注意:SSCursor 不支持
scroll()和部分元数据操作,且事务中慎用(结果集生命周期绑定连接)
Buffer Pool 淘汰机制隔离冷热数据
InnoDB 的 innodb_buffer_pool_size 确实可能被大查询冲击,但它的 LRU 链表不是朴素实现——而是划分为 young(5/8)和 old(3/8)两个子链。全表扫描读入的数据首先进入 old 区,只有再次访问且间隔超 1 秒,才晋升到 young 区;否则很快从 old 尾部被淘汰。
这就防止日志表、归档表这类冷数据“一把清空”业务热数据。但要注意:如果大查询本身带 WHERE 条件命中索引,走的是点查路径,数据直接进 young 区,影响更大。
- 通过
SHOW ENGINE INNODB STATUS查看Buffer pool hit rate,低于 95% 就值得警惕 - 不要盲目调大
innodb_buffer_pool_size,超过物理内存 80% 可能引发系统级 OOM - 对已知冷表做统计或归档,可先
SET SESSION innodb_old_blocks_time = 0加速淘汰(仅会话级)


















