mysql内存碎片源于innodb频繁分配/释放row_log、sort_buffer等动态内存块,由glibc ptmalloc分配器固有特性导致,非sql错误或mysql bug;表现为线程级high_number_of_bytes_used远高于current_number_of_bytes_used、res高而%mem稳定等。

MySQL执行SQL时内存碎片怎么产生的
内存碎片不是SQL写错了才出现,而是InnoDB在频繁分配/释放row_log、sort_buffer、join_buffer这类动态内存块时自然积累的。尤其在OLTP场景下,大量短生命周期的小查询反复申请几KB~几百KB内存,glibc堆管理器(ptmalloc)容易留下无法合并的间隙——这不是MySQL Bug,是通用内存分配器的固有行为。
常见错误现象:SHOW STATUS LIKE 'Performance_schema_memory_classes_total';里innodb/buf_buf_pool以外的innodb/*项持续增长但不回落;top看RES高而%MEM稳定,说明物理内存被占着但没被有效利用。
- 别盯着
innodb_buffer_pool_size调大就以为能缓解——它管的是页缓存,和执行时的临时内存碎片无关 -
sort_buffer_size设成2M比16M更易碎片化:小块多、释放频次高,ptmalloc更难复用 - 使用
tmp_table_size和max_heap_table_size控制内存临时表上限,超限自动转磁盘,反而避开内存碎片堆积
怎么确认当前SQL是否受内存碎片影响
直接看执行过程中的内存实际使用路径,而不是查全局状态。重点盯performance_schema里线程级内存事件,因为碎片是按线程堆独立发生的。
实操建议:
- 打开内存监控:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_statements_current';+UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%'; - 查具体SQL的内存轨迹:
SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED, HIGH_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_by_thread_by_event_name WHERE THREAD_ID = (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_INFO LIKE '%your_sql%') AND EVENT_NAME LIKE 'memory/sql/%'; - 如果
HIGH_NUMBER_OF_BYTES_USED远高于CURRENT_NUMBER_OF_BYTES_USED,说明该线程曾峰值占用大量内存但未及时归还——典型碎片前兆
innodb_use_sys_malloc对内存碎片的影响
这是最容易被忽略的开关。MySQL默认用innodb_use_sys_malloc=ON,即让InnoDB直接调malloc()/free(),完全依赖glibc;设为OFF则启用InnoDB自研内存池(ut_allocator),内部做内存块预分配+重用,大幅降低碎片率。
但代价明确:
- 开启
innodb_use_sys_malloc=OFF后,innodb_buffer_pool_size必须是innodb_additional_mem_pool_size的整数倍(后者已废弃,实际由innodb_buffer_pool_chunk_size隐式控制) - 5.7+版本中
innodb_use_sys_malloc已被标记为只读,只能编译时决定;8.0彻底移除,改用innodb_allocator统一管理 - 云上容器环境(如Docker)若启用了
--memory限制,用系统malloc更容易触发OOM killer,而InnoDB内存池会主动节制
为什么重启mysqld不一定清掉内存碎片
因为Linux的malloc默认不把内存还给OS,只是标记为“可重用”。即使MySQL进程退出,glibc的brk或mmap区域可能仍被保留在进程地址空间里,直到OS内存压力大到触发trim。
验证方法:cat /proc/$(pidof mysqld)/smaps | awk '/^Size:/ {t+=$2} /^MMUPageSize:/ {m+=$2} END {print "total:", t, "mmap:", m}' —— 如果total远大于mmap,说明大量内存卡在ptmalloc的fastbins/unsorted_bins里没释放。
真正有效的缓解动作:
- 在业务低峰期执行
SET GLOBAL innodb_buffer_pool_dump_now = ON;+SET GLOBAL innodb_buffer_pool_load_now = ON;,强制触发一次内存整理(仅限8.0+) - 避免在连接池里长期空闲连接,它们持有的
thread_stack和net_buffer_length内存不会自动收缩 - 升级到MySQL 8.0.30+,其
innodb_allocator引入了memcached-styleslab分配器逻辑,对read_view、trx_t等高频小对象做了专门优化
内存碎片问题不在SQL本身,而在执行上下文的生命周期管理和底层分配器选择。越想靠调参绕过它,越容易撞上glibc堆的隐形天花板。










