`
jzy996492849
  • 浏览: 128872 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

MySQL • 源码分析 • 内存分配机制

 
阅读更多
摘要: 前言 内存资源由操作系统管理,分配与回收操作可能会执行系统调用(以 malloc 算法为例,较大的内存空间分配接口是 mmap, 而较小的空间 free 之后并不归还给操作系统 ),频繁的系统调用必然会降低系统性能,但是可以最大限度的把使用完毕的内存让给其它进程使用,相反长时间占有内存资源可以减少系统调用次数,但是内存资源不足会导致操作系统频繁换页,降低服务器的整体性能。

前言

内存资源由操作系统管理,分配与回收操作可能会执行系统调用(以 malloc 算法为例,较大的内存空间分配接口是 mmap, 而较小的空间 free 之后并不归还给操作系统 ),频繁的系统调用必然会降低系统性能,但是可以最大限度的把使用完毕的内存让给其它进程使用,相反长时间占有内存资源可以减少系统调用次数,但是内存资源不足会导致操作系统频繁换页,降低服务器的整体性能。

数据库是使用内存的“大户”,合理的内存分配机制就尤为重要,上一期月报介绍了 PostgreSQL 的内存上下文,本文将介绍在 MySQL 中又是怎么管理内存的。

基础接口封装

MySQL 在基本的内存操作接口上面封装了一层,增加了控制参数 my_flags

void *my_malloc(size_t size, myf my_flags)
void *my_realloc(void *oldpoint, size_t size, myf my_flags)
void my_free(void *ptr)
my_flags 的值目前有:

MY_FAE /* Fatal if any error */
MY_WME /* Write message on error */
MY_ZEROFILL /* Fill array with zero */
MY_FAE 表示内存分配失败就退出整个进程,MY_WME 表示内存分配失败是否需要记录到日志中,MY_ZEROFILL 表示分配内存后初始化为0。

MEM_ROOT

基本结构

在 MySQL 的 Server 层中广泛使用 MEM_ROOT 结构来管理内存,避免频繁调用封装的基础接口,也可以统一分配和管理,防止发生内存泄漏。不同的 MEM_ROOT 之间互相没有影响,不像 PG 中不同的内存上下文之间还有关联。这可能得益于 MySQL Server 层是面向对象的代码,MEM_ROOT 作为类中的一个成员变量,伴随着对象的整个生命周期。比较典型的类有: THD,String, TABLE, TABLE_SHARE, Query_arena, st_transactions 等。

MEM_ROOT 分配内存的单元是 Block,使用 USED_MEM 结构体来描述。结构比较简单,Block 之间相互连接形成内存块链表,left 和 size 表示对应 Block 还有多少可分配的空间和总的空间大小。

typedef struct st_used_mem
{
/* struct for once_alloc (block) */ struct st_used_mem *next;
/* Next block in use */ unsigned int left;
/* memory left in block */ unsigned int size;
/* size of block */
} USED_MEM;
而 MEM_ROOT 结构体负责管理 Block 链表 :

typedef struct st_mem_root
{
USED_MEM *free; /* blocks with free memory in it */
USED_MEM *used; /* blocks almost without free memory */
USED_MEM *pre_alloc; /* preallocated block */ /* if block have less memory it will be put in 'used' list */
size_t min_malloc;
size_t block_size; /* initial block size */ unsigned int block_num; /* allocated blocks counter */ /*
first free block in queue test counter (if it exceed
MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list)
*/ unsigned int first_block_usage;

void (*error_handler)(void);
} MEM_ROOT;
整体结构就是两个 Block 链表,free 链表管理所有的仍然存在可分配空间的 Block,used 链表管理已经没有可分配空间的所有 Block。pre_alloc 类似于 PG 内存上下文中的 keeper,在初始化 MEM_ROOT 的时候就可以预分配一个 Block 放到 free 链表中,当 free 整个 MEM_ROOT 的时候可以通过参数控制,选择保留 pre_alloc 指向的 Block。min_malloc 控制一个 Block 剩余空间还有多少的时候从 free 链表移除,加入到 used 链表中。block_size 表示初始化 Block 的大小。block_num 表示 MEM_ROOT 管理的 Block 数量。first_block_usage 表示 free 链表中第一个 Block 不满足申请空间大小的次数,是一个调优的参数。err_handler 是错误处理函数。

分配流程

使用 MEM_ROOT 首先需要初始化,调用 init_alloc_root, 通过参数可以控制初始化的 Block 大小和 pre_alloc_size 的大小。其中比较有意思的点是 min_block_size 直接指定一个值 32,个人觉得不太灵活,对于小内存的申请可能会有比较大的内存碎片。另一个是 block_num 初始化为 4,这个和决定新分配的 Block 大小策略有关。

void init_alloc_root(MEM_ROOT *mem_root, size_t block_size,
size_t pre_alloc_size __attribute__((unused)))
{
mem_root->free= mem_root->used= mem_root->pre_alloc= 0;
mem_root->min_malloc= 32;
mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE;
mem_root->error_handler= 0;
mem_root->block_num= 4; /* We shift this with >>2 */
mem_root->first_block_usage= 0;

if (pre_alloc_size)
{
if ((mem_root->free= mem_root->pre_alloc=
(USED_MEM*) my_malloc(pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM)),
MYF(0))))
{
mem_root->free->size= pre_alloc_size+ALIGN_SIZE(sizeof(USED_MEM));
mem_root->free->left= pre_alloc_size;
mem_root->free->next= 0;
rds_update_query_size(mem_root, mem_root->free->size, 0);
}
}
DBUG_VOID_RETURN;
}
初始化完成就可以调用 alloc_root 进行内存申请,整个分配流程并不复杂,代码也不算长,为了方便阅读贴出来,也可以略过直接看分析。

void *alloc_root( MEM_ROOT *mem_root, size_t length )
{
size_t get_size, block_size;
uchar * point;
reg1 USED_MEM *next = 0;
reg2 USED_MEM **prev;

length = ALIGN_SIZE( length );
if ( (*(prev = &mem_root->free) ) != NULL ) // 判断 free 链表是否为空
{
if ( (*prev)->left < length &&
mem_root->first_block_usage++ >= ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP &&
(*prev)->left < ALLOC_MAX_BLOCK_TO_DROP ) // 优化策略
{
next = *prev;
*prev = next->next; /* Remove block from list */
next->next = mem_root->used;
mem_root->used = next;
mem_root->first_block_usage = 0;
}
// 找到一个空闲空间大于申请内存空间的 Block  for ( next = *prev; next && next->left < length; next = next->next )
prev = &next->next;
}
if ( !next ) // free 链表为空,或者没有满足可分配条件 Block
{ /* Time to alloc new block */
block_size = mem_root->block_size * (mem_root->block_num >> 2);
get_size = length + ALIGN_SIZE( sizeof(USED_MEM) );
get_size = MY_MAX( get_size, block_size );

if ( !(next = (USED_MEM *) my_malloc( get_size, MYF( MY_WME | ME_FATALERROR ) ) ) )
{
if ( mem_root->error_handler )
(*mem_root->error_handler)();
DBUG_RETURN( (void *) 0 ); /* purecov: inspected */
}
mem_root->block_num++;
next->next = *prev;
next->size = get_size;
next->left = get_size - ALIGN_SIZE( sizeof(USED_MEM) );
*prev = next; // 新申请的 Block 放到 free 链表尾部
}

point = (uchar *) ( (char *) next + (next->size - next->left) );
if ( (next->left -= length) < mem_root->min_malloc ) // 分配完毕后,Block 是否还能在 free 链表中继续分配
{ /* Full block */
*prev = next->next; /* Remove block from list */
next->next = mem_root->used;
mem_root->used = next;
mem_root->first_block_usage = 0;
}
}
首先判断 free 链表是否为空,如果不为空,按逻辑应该遍历整个链表,找到一个空闲空间足够大的 Block,但是看代码是先执行了一个判断语句,这其实是一个空间换时间的优化策略,因为free 链表大多数情况下都是不为空的,几乎每次分配都需要从 free 链表的第一个 Block 开始判断,我们当然希望第一个 Block 可以立刻满足要求,不需要再扫描 free 链表,所以根据调用端的申请趋势,设置两个变量:ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP 和 ALLOC_MAX_BLOCK_TO_DROP,当 free 链表的第一个 Block 申请次数超过 ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP 而且剩余的空闲空间小于 ALLOC_MAX_BLOCK_TO_DROP,就把这个 Block 放到 used 链表里,因为它已经一段时间无法满足调用端的需求了。

如果在 free 链表中没有找到合适的 Block,就需要调用基础接口申请一块新的内存空间,新的内存空间大小当然至少要满足这次申请的大小,同时预估的新 Block 大小是 : mem_root->block_size * (mem_root->block_num >> 2) 也就是初始化的 Block 大小乘以当前 Block 数量的 1/4,所以初始化 MEM_ROOT 的 block_num 至少是 4。

找到合适的 Block 之后定位到可用空间的位置就行了,返回之前最后需要判断 Block 分配之后是否需要移动到 used 链表。

归还内存空间的接口有两个:mark_blocks_free(MEM_ROOT *root)和 free_root(MEN_ROOT *root,myf MyFlags) ,可以看到两个函数的参数不像基础封装的接口,没有直接传需要归还空间的指针,传入的是 MEM_ROOT 结构体指针,说明对于 MEM_ROOT 分配的内存空间,是统一归还的。mark_blocks_free 不真正的归还 Block,而是放到 free 链表中标记可用。free_root 真正归还空间给操作系统,MyFlages 可以控制是否和标记删除的函数行为一样,也可以控制 pre_alloc 指向的 Block 是否归还。

总结

从空间利用率上来讲,MEM_ROOT 的内存管理方式在每个 Block 上连续分配,内部碎片基本在每个 Block 的尾部,由 min_malloc 成员变量和参数 ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP,ALLOC_MAX_BLOCK_TO_DROP 共同决定和控制,但是 min_malloc 的值是在代码中写死的,有点不够灵活,可以考虑写成可配置的,同时如果写超过申请长度的空间,就很有可能会覆盖后面的数据,比较危险。但相比 PG 的内存上下文,空间利用率肯定是会高很多的。
从时间利用率上来讲,不提供 free 一个 Block 的操作,基本上一整个 MEM_ROOT 使用完毕才会全部归还给操作系统,可见 MySQL 在内存上面还是比较“贪婪”的。
从使用方式上来讲,因为 MySQL 拥有多个存储引擎,引擎之上的 Server 层是面向对象的 C++ 代码,MEM_ROOT 常常作为对象中的一个成员变量,在对象的生命周期内分配内存空间,在对象析构的时候回收,引擎的内存申请使用封装的基本接口。相比之下 MySQL 的使用方式更加多元,PG 的统一性和整体性更好。
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
分享到:
评论

相关推荐

    MySQL之innodb源码分析之page结构解析

    总的来说,"MySQL之InnoDB源码分析之page结构解析"是一个深入探讨数据库内部运作机制的主题,涵盖了数据库管理、数据存储、查询优化等多个方面。通过学习和理解这部分内容,不仅可以提升数据库的维护能力,也为...

    mysql5.5.25 源码阅读笔记

    7. **内存管理和缓存策略**:理解内存分配、释放机制,以及如何通过调整缓冲池大小来优化性能。 通过深入阅读MySQL 5.5.25的源码,我们可以了解到数据库系统的底层运作,这对于提升数据库性能调优能力,解决实际...

    高性能mysql源码.rar

    MySQL是一款广泛使用的开源关系型数据库管理系统,以其高效...或者针对特定硬件环境调整内存分配策略,最大化硬件利用率。因此,对"高性能MySQL源码"的探索对于数据库管理员、开发者和系统架构师来说都具有很高的价值。

    mysql源码包

    MySQL是世界上最受欢迎的开源关系型数据库管理系统之一,其源码包提供了深入了解系统内部运作...总的来说,MySQL源码分析对于数据库开发者、运维人员或希望深入学习数据库原理的人来说,都是一个极其有价值的实践项目。

    mysql源代码,深入学习mysql必须分析其源代码

    `heap`可能涉及到内存管理,MySQL在处理大量数据时需要高效地分配和释放内存,这个部分的源代码将揭示其内存池管理策略。 `myisamchk`和`myisam`是MyISAM存储引擎的相关工具,`myisamchk`用于检查、修复和优化...

    ruby 内存分配访问无效

    总之,解决“ruby内存分配访问无效”的问题需要深入理解Ruby的内存管理和垃圾回收机制,结合代码审查、调试工具和适当的内存分析,找出并修复问题的根源。同时,如果涉及到数据库操作,还需要关注数据库连接和查询的...

    mysql-5.5.19最新的mysql源码

    7. **内存管理**:MySQL使用自定义的内存池管理,如`sql/mem_root.h`定义的`MEM_ROOT`结构,用于高效地分配和回收内存。 8. **线程与并发**:MySQL在多线程环境下运行,`mysys/my_thread.h`和`mysys/thr_mutex.cc`...

    mysql5.7源代码

    `include/my_alloc.h`定义了内存分配和释放的接口,如`my_malloc`和`my_free`。此外,InnoDB存储引擎有自己的内存池管理,用于减少系统调用。 4. **线程与并发控制** MySQL使用多线程模型处理并发请求。`sql/...

    mysql-5.5.46.tar.gz 源码包

    12. **内存管理**:优化了内存分配和管理,减少内存碎片,提高内存利用率。 总结,`mysql-5.5.46.tar.gz`源码包包含了许多关键特性,不仅提供了强大的数据库服务,还注重性能优化、安全性以及易用性。通过深入理解...

    mysql-5.1.69.tar.gz

    6. **内存管理**:源码中,你可以了解到MySQL如何管理内存,包括缓存池、缓冲区和内存分配策略,这对于优化数据库性能至关重要。 7. **日志系统**:MySQL 5.1使用通用日志(general query log)和慢查询日志(slow ...

    mysql-5.5.40.tar.gz

    本文将详细介绍MySQL 5.5.40版本的关键特性和源码分析。 首先,MySQL 5.5系列是MySQL的一个重大升级,它在性能、可扩展性以及新功能方面都有显著提升。5.5.40作为该系列的一个稳定版本,主要关注于修复已知问题,...

    mysql5.6.12社区版本

    - 优化了内存分配策略,减少了内存碎片,提高了内存利用率。 6. **JSON 支持**: 虽然 MySQL 5.6 不直接支持 JSON 数据类型,但可以使用 TEXT 或 BLOB 存储 JSON 文档,并通过内置函数进行解析和操作。 7. **...

    mysql5.5.35源码

    - 内存池管理对MySQL的性能至关重要,5.5版在内存分配和回收上进行了优化。 - 进程调度和线程池管理保证了并发环境下的资源公平分配。 通过深入研究MySQL 5.5.35源码,不仅可以了解数据库的基本工作原理,还可以...

    基于PHP的MySQLMonitorv1.0.0MySQL运行状态分析工具源码.zip

    【标题】"基于PHP的MySQLMonitorv1.0.0MySQL运行状态分析工具源码"是一个用于监控MySQL数据库运行状态的工具,它采用PHP编程语言实现。MySQLMonitorv1.0.0旨在帮助系统管理员实时了解MySQL服务器的性能指标,如查询...

    mysql5.4.3

    这包括修改配置选项,如调整内存分配、线程池大小、缓存策略等,以适应不同的硬件和应用场景。 2. **嵌入式应用**:MySQL 5.4.3强调了对嵌入式应用的支持,这意味着它可以被整合到其他软件产品中,作为内部数据库...

    官方最新!!!mysql-8.0.11

    - **内存管理**:内存分配和管理的改进减少了内存碎片,提高了整体系统性能。 3. **安全强化** - **密码验证插件**:MySQL 8.0引入了更安全的密码验证插件,加强了用户认证过程。 - **增强的审计日志**:审计...

    mysql-5.6.17.tar.gz

    9. **内存管理**:改进了内存分配策略,减少了内存碎片,提高了内存利用率。 **源码安装流程:** 1. **解压源码**:首先,需要将"mysql-5.6.17.tar.gz"这个压缩包解压,通常使用`tar -zxvf mysql-5.6.17.tar.gz`...

    mysql-source-read:我阅读mysql源码-mysql

    - **内存管理**:了解MySQL如何高效地分配和回收内存,以及内存池的概念。 - **锁机制**:MySQL如何实现行级、页级和表级的锁定,以保证并发控制。 - **存储引擎接口**:理解存储引擎API,如handler接口,它是存储...

    mysql-5.7.17

    配置方面,可以根据实际需求调整my.cnf文件中的参数,如内存分配、缓存大小等,以达到最佳性能表现。 ### 结论 MySQL 5.7.17作为MySQL 5.7系列的一个重要版本,在性能、安全性和功能性方面都进行了显著提升,是...

    phpwind源码分析

    PHPWind源码分析是一项深入理解其内部工作原理和技术架构的过程,这对于开发者来说是提升技能和定制功能的重要途径。下面将详细探讨PHPWind源码中的关键知识点。 1. **PHP基础**:PHP是一种广泛使用的服务器端脚本...

Global site tag (gtag.js) - Google Analytics