[MySQL学习]Innodb压缩表之内存分配/回收

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

最近看到Yoshinori Matsunobu在官方buglist上提交的一个Bug#68077,大意是说,当使用压缩表时,在bp吃紧时,存在过度碎片合并的情况。Innodb压缩表由于存在不同的Page Size,因此使用buddy allocator的方式进行内存分配,他的内存块来自于buffer pool中。

如bug#68077所提到的,如果我们使用的全部是4kb的内存块,那么把他们合并成8k的又有什么意义呢?并且在碎片整理的过程中,函数buf_buddy_free

的效率是很低的。在之前已经碰到过很多类似的案例,例如DROP TABLE,在遍历block,释放adaptive hash index记录时,可能会在这里hang住。

但从Marko的角度来看,由于内存是从buffer pool中分配的,如果不做碎片合并的话,很容易导致大量的4KB的页面存留在内存中,这对非压缩表而言是不可用的;如果你释放了4*n*4k的压缩页内存,那在buffer pool中就有n个16kb的block是可以为非压缩表所用的。

不过合并成8KB的block对只使用4kb和16kb的场景用处并不大,在回收内存时,也存在时间复杂度较高的情况

简单看看buddy allocator如何分配和回收内存。

 

A.分配内存

接口函数:buf_buddy_alloc

参数描述:

buf_pool_t* buf_pool //当前page所在的bp对象

ulint size  //压缩页需要的内存大小

ibool* lru  //表示是否是从bp->LRU上分配的内存

通过size,计算出其在buf_pool->zip_free[]数组中的slot(buf_buddy_get_slot),例如1kb,对应slot 0,4kb对应slot 2

具体内存分配实现函数是buf_buddy_alloc_low,注意调用这个函数需要持有bp->mutex,并且不可持有bp->zip_mutex或者任何block->mutex。内存分配的方式也是典型的binary buddy allocator 算法

 

1.首先尝试从buf_pool->zip_free[]数组中查找(buf_buddy_alloc_zip),优先从buddy system中分配block

>>根据slot,查看bp->zip_free对应数组元素链表,如果有的话,则将其从zip_free中移除,以占用这个block(buf_buddy_remove_from_free)

>>如果没有对应slot的空闲块,则查找更大的空闲块,例如,如果没有4kb的空闲块,我们就去看有没有8kb的空闲块,这里采用递归调用buf_buddy_alloc_zip(buf_pool, i + 1)的方法来返回block。

虽然递归算法存在效率问题,不过幸好这里的递归深度不会很大,最多4层调用

例如,我们申请4kb的内存块,这里我们获得一个8kb的内存块,只将低位的拿来用,而高位的4kb则放到bp->zip_free链表上,状态设置为BUF_BLOCK_ZIP_FREE

如果我们申请的是2kb的内存块,当前只有8kb的空闲块,那么会产生一个新的4kb的和2Kb的空闲块

这种情况下,我们可以直接返回获得的block首地址;

 

2.如果bp->zip_free上没有空闲块,就从bp->free上取一个block(buf_LRU_get_free_only)

如果buffer pool没有的话,就调用buf_LRU_get_free_block,获得的block的状态被设置为BUF_BLOCK_READY_FOR_USE

这里存在的困惑是,buf_LRU_get_free_block同样也会调用buf_LRU_get_free_only,因为它是一个通用的获取空闲块的函数,同样也会先去看看free list上有没有空闲块,这里似乎存在着重复调用,尽管这对性能影响不大。

 

3.当我们从LRU或Free List上得到一个空闲块后,就:

首先将其状态设置为BUF_BLOCK_MEMORY,再将其插入到bp->zip_hash中(buf_buddy_block_register)

再从其中首地址取得想要的内存块大小,剩下的进行切分,放到bp->zip_free中(buf_buddy_alloc_from)

 

B.回收内存

接口函数:buf_buddy_free

参数描述

buf_pool_t* buf_pool  //当前Page所在的bp对象

void* buf   //将被释放的块首地址

ulint size  //块大小

相对于分配,回收内存要复杂一些,因为这里还涉及到内存整理。回收内存具体函数为buf_buddy_free_low,由于函数中存在大量的goto,以下按照正常思维的逻辑,而非代码的至上而下分析:

首先,判断是否需要进行碎片整理:

1.如果相同大小的碎片数小于16,不进行整理(UT_LIST_GET_LEN(buf_pool->zip_free[i]) < 16);

2.看看相邻的block(buf_buddy_get),称为buddy

如果这个block被标记为BUF_BLOCK_ZIP_FREE,会遍历当前slot对应的bp->zip_free,看看buddy是不是在其中。

这一步的目的实际上是为了确认邻居block和当前要释放的block是否是同样大小的,但不幸的是,这里采用遍历bp->zip_free的方法,复杂度为O(n)。如果zip_free链表很长的话,显然开销是不可接受的。

如果邻居节点可以用来合并,那就将其从zip_free中移除(buf_buddy_remove_from_free),并继续去尝试合并;

例如,我们要释放2kb的block,发现邻近有个空闲2kb的,合并成4kb后,如果发现邻近还有个4kb的,就会去合并成8kb的block。

当合并成16kb的block后,已经和普通非压缩表使用的块一样了,就将其“还”给bp,从bp->zip_hash中删除,并将其加入到bp->free空闲链表中(buf_LRU_block_free_non_file_page),这种情况就直接返回,不进行下面的逻辑了。

当发现无法再合并buddy后,查看当前slot在zip_free中是否存在空闲块,如果有的话,则先将这个page从zip_free中移除(buf_buddy_remove_from_free),然后尝试把buddy的数据转移到zip_free链表头(buf_buddy_relocate,在某些情况下不可以迁移,例如这个buddy是被IO FIX的),将其置为BUF_BLOCK_ZIP_FREE,再去进行合并。如果不能块迁移,就再把这个block加入到free list上。

以上都是比较典型的binary buddy allocator算法。但也可以看到,这里面涉及到大量的内存操作和内存块迁移,难怪会被人吐槽该函数的效率太低下。。。 

~~~~~~~~~~~

另外最近一个跟Percona相关的bug#1100159,属于Percona在引入新的特性时的失误导致,一个用buf_page_alloc_descriptor分配的内存块,却使用buf_buddy_free来释放,尽管对应的code path不容易触发,但这依然是一个很严重的问题,如果碰到了,直接就导致crash了。Percona应该在下一个版本,也就是Percona Server 5.5.30版本Fix这个问题


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
24天前
|
存储 SQL 关系型数据库
MySQL底层概述—2.InnoDB磁盘结构
InnoDB磁盘结构主要包括表空间(Tablespaces)、数据字典(Data Dictionary)、双写缓冲区(Double Write Buffer)、重做日志(redo log)和撤销日志(undo log)。其中,表空间分为系统、独立、通用、Undo及临时表空间,分别用于存储不同类型的数据。数据字典从MySQL 8.0起不再依赖.frm文件,转而使用InnoDB引擎存储,支持事务原子性DDL操作。
205 100
MySQL底层概述—2.InnoDB磁盘结构
|
24天前
|
缓存 算法 关系型数据库
MySQL底层概述—1.InnoDB内存结构
本文介绍了InnoDB引擎的关键组件和机制,包括引擎架构、Buffer Pool、Page管理机制、Change Buffer、Log Buffer及Adaptive Hash Index。
216 97
MySQL底层概述—1.InnoDB内存结构
|
21天前
|
SQL 关系型数据库 MySQL
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
MySQL底层概述—10.InnoDB锁机制
|
23天前
|
存储 缓存 关系型数据库
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
MySQL底层概述—5.InnoDB参数优化
|
23天前
|
存储 SQL 关系型数据库
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
|
23天前
|
存储 缓存 关系型数据库
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
1月前
|
存储 SQL 缓存
MySQL原理简介—2.InnoDB架构原理和执行流程
本文介绍了MySQL中更新语句的执行流程及其背后的机制,主要包括: 1. **更新语句的执行流程**:从SQL解析到执行器调用InnoDB存储引擎接口。 2. **Buffer Pool缓冲池**:缓存磁盘数据,减少磁盘I/O。 3. **Undo日志**:记录更新前的数据,支持事务回滚。 4. **Redo日志**:确保事务持久性,防止宕机导致的数据丢失。 5. **Binlog日志**:记录逻辑操作,用于数据恢复和主从复制。 6. **事务提交机制**:包括redo日志和binlog日志的刷盘策略,确保数据一致性。 7. **后台IO线程**:将内存中的脏数据异步刷入磁盘。
|
3月前
|
存储 缓存 关系型数据库
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL的存储引擎是其核心组件之一,负责数据的存储、索引和检索。不同的存储引擎具有不同的功能和特性,可以根据业务需求 选择合适的引擎。本文详细介绍了MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案。
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
|
3月前
|
SQL 存储 关系型数据库
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
本文详细介绍了MySQL中的SQL语法,包括数据定义(DDL)、数据操作(DML)、数据查询(DQL)和数据控制(DCL)四个主要部分。内容涵盖了创建、修改和删除数据库、表以及表字段的操作,以及通过图形化工具DataGrip进行数据库管理和查询。此外,还讲解了数据的增、删、改、查操作,以及查询语句的条件、聚合函数、分组、排序和分页等知识点。
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
|
3月前
|
存储 关系型数据库 MySQL
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
88 7