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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介:

前言

内存资源由操作系统管理,分配与回收操作可能会执行系统调用(以 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 的统一性和整体性更好。
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
18天前
|
Java C#
深入理解操作系统的内存管理机制
【4月更文挑战第25天】 在现代计算机系统中,操作系统扮演着关键的角色,它负责协调和管理硬件资源,确保系统运行的稳定性和效率。内存管理是操作系统中的核心职能之一,它涉及到物理内存的分配、虚拟内存的管理以及内存的优化等复杂过程。本文将深入探讨操作系统的内存管理机制,包括分页系统的实现原理、地址转换机制以及内存碎片问题的解决策略。通过对这些概念和技术的详细分析,读者将能够更好地理解操作系统如何高效地管理和利用计算机的内存资源。
|
4天前
|
算法 程序员 调度
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在现代计算机系统中,操作系统的内存管理是一个至关重要的部分,它直接影响到系统的性能和稳定性。本文将深入探讨操作系统的内存管理机制,包括物理内存的管理、虚拟内存的概念和应用,以及内存分配和回收的策略。通过对这些内容的深入理解,我们可以更好地理解操作系统的工作原理,提高我们的编程效率和质量。
|
13天前
|
算法
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统扮演着至关重要的角色,它负责协调和管理硬件资源,确保系统高效、稳定地运行。其中,内存管理是操作系统的核心功能之一,涉及到物理内存的分配、虚拟内存的映射以及内存的优化等多个方面。本文将深入探讨操作系统中的内存管理机制,包括分页、分段和段页式结合等技术,旨在为读者提供一个清晰的内存管理框架视图,并解释这些技术如何提高系统的性能和稳定性。
|
16天前
|
算法 Java 大数据
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其是内存管理作为其核心功能之一,它直接关系到系统性能和稳定性。本文将探讨操作系统中内存管理的基本原理、关键技术以及面临的挑战,旨在为读者提供一个清晰的内存管理概念框架,并通过分析不同内存管理策略的优缺点,揭示其对操作系统整体性能的影响。
|
16天前
|
缓存 算法 调度
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其在资源管理和调度方面。内存管理是操作系统的核心功能之一,它负责分配、跟踪和回收应用程序使用的物理内存。本文将探讨操作系统如何通过不同的内存管理技术来优化内存使用效率,包括分页、分段以及虚拟内存等概念。通过对这些技术的深入分析,读者将获得对操作系统内部工作原理的更深刻理解,并了解它们如何影响应用程序性能和系统稳定性。
|
4天前
|
算法 安全 UED
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在本文中,我们将探讨操作系统的核心组件之一——内存管理。不同于传统的摘要概述,我们将直接切入主题,首先介绍内存管理的基础知识,然后深入讨论操作系统如何处理内存分配、内存保护以及虚拟内存技术。通过分析具体实例和案例研究,文章旨在为读者提供一个清晰的框架,以理解内存管理在现代操作系统中的实现和重要性。
4 0
|
4天前
|
存储 内存技术
深入理解操作系统的内存管理机制
【5月更文挑战第9天】操作系统的内存管理机制是计算机科学中的核心概念,它负责协调和管理计算机的内存资源,确保系统的稳定性和效率。本文将深入探讨操作系统的内存管理机制,包括内存分配、内存保护和虚拟内存等关键技术,帮助读者更好地理解和掌握操作系统的运行原理。
|
6天前
|
存储 关系型数据库 MySQL
MySQL数据库锁定机制
MySQL数据库锁定机制
11 0
|
9天前
|
存储 关系型数据库 MySQL
MySQL的锁机制
MySQL的锁机制主要用于管理并发事务对数据的一致性和完整性的访问控制
25 4
|
13天前
|
存储 缓存 算法
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理机制是确保系统高效稳定运行的关键。本文将深入探讨操作系统中的内存管理概念,包括虚拟内存、物理内存、内存分配策略以及页面置换算法。通过对这些关键概念的剖析,我们能够理解操作系统如何有效地管理有限的内存资源,支持多任务并发执行,同时保证系统的响应速度和稳定性。

推荐镜像

更多