系统无法分配所需内存_Innodb内存管理解析

本文主要介绍innodb的内存管理,涉及基础的内存分配结构、算法以及buffer pool的实现细节,提及change buffer、自适应hash index和log buffer的基本概念和内存基本配比,侧重点在内存的分配和管理方式。本文所述内容基于mysql8.0版本。

基础内存分配

在5.6以前的版本中,innodb内部实现了除buffer pool外的额外内存池,那个时期lib库中的分配器在性能和扩展性上表现比较差,缺乏针对多核系统优化的内存分配器,像linux下最通用的ptmalloc的前身是Doug Lea Malloc,也是因为不支持多线程而被弃用了。所以innodb自己实现了内存分配器,使用额外的内存池来响应那些原本要发给系统的内存请求,用户可以通过设置参数innodb_use_sys_malloc 来选择使用innodb的分配器还是系统分配器,使用innodb_additional_mem_pool_size参数设定额外内存池的大小。随着多核系统的发展,一些分配器对内部实现进行了优化和扩展,已经可以很好的支持多线程,相较于innodb特定的内存分配器可以提供更好的性能和扩展性,所以这个实现在5.6版本已经弃用,5.7版本删除。本文所讨论的内容和涉及到的代码基于mysql8.0版本。

innodb内部封装了基础分配释放方式malloc,free,calloc,new,delete等,在开启pfs模式下,封装内部加入了内存追踪信息,使用者可传入对应的key值来记录某个event或者模块的内存分配信息,这部分信息通过pfs内部表来对外展示,以便分析内存泄漏、内存异常等问题。

innodb内部也提供了对系统基础分配释放函数封装的allocator,用于std::*容器内部的内存分配,可以让这些容器内部的隐式分配走innodb内部封装的接口,以便于内存信息的追踪。基础的malloc/calloc等封装后也会走allocator的接口。

基础封装:

非UNIV_PFS_MEMORY编译模式
#define UT_NEW(expr, key) ::new (std::nothrow) expr
#define UT_NEW_NOKEY(expr) ::new (std::nothrow) expr
#define UT_DELETE(ptr) ::delete ptr
#define UT_DELETE_ARRAY(ptr) ::delete[] ptr
#define ut_malloc(n_bytes, key) ::malloc(n_bytes)
#define ut_zalloc(n_bytes, key) ::calloc(1, n_bytes)
#define ut_malloc_nokey(n_bytes) ::malloc(n_bytes)
...

打开UNIV_PFS_MEMORY
#define UT_NEW(expr, key)                                                
  ::new (ut_allocator<byte>(key).allocate(sizeof expr, NULL, key, false, 
                                          false)) expr
#define ut_malloc(n_bytes, key)                         
  static_cast<void *>(ut_allocator<byte>(key).allocate( 
      n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, false, false))

#define ut_zalloc(n_bytes, key)                         
  static_cast<void *>(ut_allocator<byte>(key).allocate( 
      n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, true, false))

#define ut_malloc_nokey(n_bytes)               
  static_cast<void *>(                         
      ut_allocator<byte>(PSI_NOT_INSTRUMENTED) 
          .allocate(n_bytes, NULL, UT_NEW_THIS_FILE_PSI_KEY, false, false))
  ...

可以看到在非UNIV_PFS_MEMORY编译模式下,直接调用系统的分配函数,忽略传入的key,而UNIV_PFS_MEMORY编译模式下使用ut_allocator分配,下面有ut_allocator的介绍,比较简单的封装。

memory heap

12784e7d0fd4f442645b759332d820d5.png

主要管理结构为mem_heap_t,8.0中的实现比较简单,内部维护block块的链表,包含指向链表开始和尾部的指针可以快速找到链表头部和尾部的节点,每个节点都是mem_heap_t的结构。mem_heap在创建的时候会初始一块内存作为第一个block,大小可由使用者指定。mem_heap_alloc响应基本的内存分配请求,先尝试从block中切分出满足请求大小的内存,如果不能满足则创建一个新的block,新的block size至少为上一个block的两倍(last block),直到达到规定的上限值,新创建的block总是链到链表的尾部。mem_heap_t中记录了block的size和链表中block的总size以及分配模式(type)等信息,基本结构如下图:

ee9629fa5b9d1261d7eccb7901594ecc.png

innodb在内部定义了三种分配block模式供选择:

    1. MEM_HEAP_DYNAMIC 使用malloc动态分配,比较通用的分配模式
    2. MEM_HEAP_BUFFER size满足一定条件,使用buffer pool中的内存块
    3. MEM_HEAP_BTR_SEARCH 保留额外的内存,地址保存在free_block中

一般的使用模式为MEM_HEAP_DYNAMIC,也可以使用b|c的模式,在某些情况下使用free_block中的内存。mem heap链表中的block在最后统一free,按照分配模式走不同的free路径。

基本思想也是使用一次性分配大内存块,再从大内存块中切分来响应小内存分配请求,以避免多次调用malloc/free,减少overhead。实现上比较简单,内部只是将多个大小可能不一的内存块使用链表链起来,大多场景只有一个block。没有内存归还、复用和合并等机制,使用过程中不会将内存free,会有一定程度的内存浪费,但有效减少了内存碎片,比较适用于短周期多次分配小内存的场景。

基础allocator

ut_allocator

innodb内部提供ut_all

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值