您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

# MySQL server 层和存储引擎层是怎么交互数据的?

时间:2022-03-11 10:12:12  来源:  作者:杨建荣的学习笔记

MySQL 存储引擎是用插件方式实现的,所以在源码里分为两层:server 层、存储引擎层。

server 层负责解析 SQL、选择执行计划、条件过滤、排序、分组等各种逻辑。

存储引擎层做的事情比较单一,负责写数据、读数据。写数据就是把 MySQL 传给存储引擎的数据存到磁盘文件或者内存中(对于 Memory 引擎是存储到内存),读数据就是把数据从磁盘或者内存读出来返回给 server 层。

server 层和引擎层是相对独立的两个模块,它们之间要配合完成工作,就会存在数据交互的过程,今天我们就以 server 层从存储引擎层读取数据来讲讲这个起着关键作用的数据交互过程。

 

1. 原理说明

在源码里,数据库中的每个表都会对应 TABLE类的一个实例,实例中有个record属性,record 属性是一个有着 2 个元素的数组,server 层每次调用引擎层的方法读取数据时,都会用table->record[0]的形式把第 1 个元素的地址传给引擎层。引擎层从磁盘或者内存中读取数据之后,把引擎层的数据格式转换为 server 层的数据格式,然后写入到这个地址对应的内存空间里,server 层就可以拿这个数据来干各种事情了(比如:WHERE 条件筛选、分组、排序等)。

整个交互过程就是这么简单,既然这么简单,那还值得单独写篇文章来叨叨这个吗?

当然是值得的,台上一分钟,台下十年功这句话大家应该都耳熟能详了,这个交互过程之所以这么简单,是因为 server 层前期做了足够的准备工作,才让这个过程看起来像百度的搜索框那么简单。

为了一探究竟,接下来就是我们往前追溯准备工作(也就是前戏阶段)的时间了。

 

2. 前戏阶段

创建表时,会计算出来每个字段在记录(也就是我们常说的)中的Offset,以及一条记录的最大长度(包含存储变长字段的长度需要占用的字节数)。

当我们第一次查询某个表的时候,MySQL 会从 frm 文件中读取字段、索引等信息,以及刚刚提到的字段 Offset、一条记录的最大长度。

接下来会根据记录的最大长度,为第 1 小节中提到的TABLE类实例的record属性申请内存,record 数组的两个元素 record[0]、record[1] 占用的字节数都等于记录的最大长度

在源码里,每个字段都对应 Field子类的一个实例,实例中有个ptr属性,指向每个字段在record[0]中对应的内存地址。对于变长字段,Field 子类实例中还会存储内容长度占用的字节数。

存储引擎从磁盘或者内存中读取一条记录的某个字段后,会判断字段的类型,如果是定长字段,把字段内容经过相应的格式转换后写入 ptr 指向的内存空间。

如果是变长字段,先把内容长度写入 ptr 指向的内存空间,然后紧挨着把字段内容经过相应的格式转换后写入内容长度之后的内存空间。

抽象的东西就写到这里为止了,接下来会用一个实际的表为例子,并且通过一张图来展示 record[0] 的内存布局,以便有个直观的了解。

 

3. 实例分析

这是示例表:

CREATE TABLE `t_recbuf` (
`id` int(10) unsigned NOT AUTO_INCREMENT,
`i1` int(10) unsigned DEFAULT '0',
`str1` varchar(32) DEFAULT '',
`str2` varchar(255) DEFAULT '',
`c1` char(11) DEFAULT '',
`e1` enum('北京','上海','广州','深圳') DEFAULT '北京',
`s1` set('吃','喝','玩','乐') DEFAULT '',
`bit1` bit DEFAULT b'0',
`bit2` bit(17) DEFAULT b'0',
`blob1` blob,
`d1` decimal(10,) DEFAULT ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf8;

这是 record[0] 的内存布局:

# MySQL server 层和存储引擎层是怎么交互数据的?

示例表和内存布局图都有了,接下来我们对照着图来分析一下各个字段对应的内存空间的情况:

字段 值标记区域

这个区域是标记一条具体的记录中,定义表结构时没有指定 NOT 的字段,实际的内容是不是 ,如果是 ,在这个区域中对应的位置会设置为 ,如果不是 ,则在这个区域中对应的位置会设置为0,每个字段的 标记占用1 bit

这个字段在 record[0] 的开头处,所以它的 Offset = 0,由于示例表中,有 10 个字段都没有指定 NOT ,所以总共需要 10 bit 来存储 标记,共占用2 字节

存储引擎读取每个字段时,如果该字段在字段 值标记区域有一席之地,就会把它对应的位置设置个值(0 或者 1)。

id

id 字段的类型是 int,定长字段,占用 4 字节,Offset = 字段 值标记区域占用字节数 = 2,ptr 属性指向 Offset 2。

存储引擎读取到 id 字段内容,经过大小端存储模式转换之后,把内容写入到 ptr 属性指向的内存空间。

由于 InnoDB 中,内容是按大端模式存储的(内容高位在前,低位在后),而 server 层是按照小端模式读取的,所以在写入整数字段内容到 record[0] 之前会进行大小端存储模式的转换。

i1

i1 字段的类型是 int,定长字段,占用 4 字节,Offset = id Offset(2) + id 长度(4) = 6,ptr 属性指向 Offset 6。

存储引擎读取到 i1 字段内容,经过大小端存储模式转换之后,把内容写入到 ptr 属性指向的内存空间。

str1

str1 字段的类型是 varchar,变长字段,Offset = i1 Offset(6) + i1 长度(4) = 10,ptr 属性指向 Offset 10。

str1 字段定义时指定要存储 32个字符,表的字符集是 utf8,每个字符最多会占用 3 字节,32 个字符最多会占用 96 字节,96 < 255,只需要1 字节就够存储 str1 内容的长度了,所以str1 len区域占用1 字节

str1 字段内容紧挨着 str1 len之后,由于str1 len占用 1 字节,所以 str1 内容的 Offset = 10 + 1 = 11。

存储引擎读取 str1 字段的内容时,也会读取到 str1 的内容长度,会先把内容长度写入 ptr 属性指向的内存空间,然后紧挨着写入 str1 的内容。

str2

str2 字段的类型也是 varchar,变长字段,Offset = str1 Offset(10) + str1 内容长度占用字节数(1) + 内容最大占用字节数(96) = 107,ptr 属性指向 Offset 107。

str2 字段定义时指定要存储 255个字符,最多会占用 255 * 3 = 765 字节,需要2 字节才能存储 str2 的内容长度,所以str2 len区域占用2 字节

str2 字段内容紧挨着 str2 len之后存储,由于str2 len占用 2 字节,所以 str2 内容的 Offset = 107 + 2 = 109。

存储引擎读取 str2 字段内容后,会先把内容长度写入 ptr 属性指向的内存空间,然后紧挨着写入 str2 的内容。

c1

c1 字段的类型是 char,定长字段,Offset = str2 Offset(107) + str2 内容长度占用字节数(2) + 内容最大占用字节数(765) = 874,ptr 属性指向 Offset 874。

c1 字段定义时指定要存储 11个字符,最多会占用 11 * 3 = 33 字节。

存储引擎读取 c1 字段内容后,会把内容写入 ptr 属性指向的内存空间。如果 c1 字段的实际内容长度比字段内容最大字节数小,会挨着刚刚写入的内容,再写入一定数量的空格。

比如:实际内容长度为 11 字节,而字段内容最大字节数为 33,则会在实际内容之后再写入 22 个空格。

e1

e1 字段类型是 enum,定长字段,只有 4 个选项,占用 1 字节,Offset = c1 Offset(874) + 内容最大长度占用字节数(33) = 907。

enum 类型在存储引擎中是用整数存储的,存储引擎读取 e1 字段内容后,会对内容进行大小端转换,把转换后的内容写入 ptr 属性指向的内在空间。

s1

s1 字段类型是 set,定长字段,只有 4 个选项,占用 1 字节,Offset = e1 Offset(907) + e1 长度(1) = 908。

set 类型在存储引擎中也是按照整数存储的,存储引擎读取 s1 字段内容后,也需要对内容进行大小端转换,把转换后的内容写入 ptr 属性指向的内存空间。

set 字段是用 enum 来实现的,最多占用 8 字节,共 64 bit,每个选项用 1 bit 表示,所以 1 个 set 字段总共可以有 64 个选项。

enum、set 字段的需要长度说明一下,如果创建表时定义的选项数量不一样,字段的长度也可能会不一样(1 ~ 8 字节),但是字段长度在创建表时就已经是确定的了,所以它们也是定长字段。

bit1

bit1 字段的类型是 bit,定长字段,创建表时定义的长度表示的是 bit,不是字节数,Offset = s1 Offset(908) + s1 长度(1) = 909。

bit1 字段定义时指定的是 bit(8),表示该字段长度为 8 bit,也就是 1 字节

bit 类型的字段在存储引擎中是按 char 存储的,存储引擎读取 bit1 字段的内容后,把内容写入到 ptr 属性指向的内存空间。

这里的 char 是指的 C/C++ 里的 char,不是指的 MySQL 的 char 类型。

bit2

bit2 字段的类型也是 bit,定长字段,创建表时定义的是 bit(17),占用 3 字节,Offset = bit1 Offset(909) + bit1 长度(1) = 910。

bit 类型的字段,如果创建表时指定的 bit 数不是 8 的整数倍,存储引擎在插入数据到磁盘或者内存时,就会在前面补充 0,比如 bit(17),占用 3 字节,内容为 00010000010010011 时,会在前面再补充 7 个 0 变成 000000000010000010010011,读出来的时候也还是这样的内容。

之所以定义 2 个 bit 字段,是为了测试 bit 类型的字段,定义的 bit 位数不是 8 的整数倍时,是不是会把多出来的那些 bit 存储到 字段值 标记区域中,后来发现,只有 MyISAM、NDB 存储引擎才会这样处理,InnoDB 中 bit 字段是按 char 存储的,bit 位数不是 8 的整数倍时,多出来的 bit 还需要占用 1 字节,比如:bit(17) 需要占用 3 字节。

blob1 len

blob1 字段的类型是 blob,变长字段,Offset = bit2 Offset(910) + bit2 长度(3) = 913。

blob 类型的字段,最多可以存储 2 ^ 16 = 65536 字节 = 64K。

存储引擎读取 blob1 字段内容之后,会分配一块能够容纳 blob1 字段内容的内存空间,把读取出来的内容写入该内存空间中。然后把 blob1 字段的内容长度写入 ptr 属性指向的内存空间处,占用 2 字节,然后紧挨着写入刚刚分配的那块内存空间的首地址,占用 8 字节。

注意:只是把 blob1 字段的内容首地址,而不是 blob1 字段的完整内容写入 record[0]。

示例中只使用了 blob 类型的字段,实际 blob 类型分为 4 种:tinyblob、blob、mediumblob、longblob,这 4 种类型的内容长度分别占用 1 ~ 4 字节。

另外,还需要说明的一点是:tinytext、text、mediumtext、longtext 也是用上面相应的 blob 类型实现的,json 类型是用 longblob 类型实现的。

d1 字段的类型是 decimial,定长字段,Offset = blob1 Offset(913) + blob1 长度占用字节数(2) + blob1 内容首地址占用字节数(8) = 923。

decimal 类型的字段,在存储引擎中是用二进制存储的,在创建表的时候,就计算出来了需要用几字节来存储。

存储引擎读取 d1 字段的内容之后,把内容写入 ptr 属性指向的内存空间。

以上就是本文所有内容了,内容有点多,希望坚持看完的朋友有所收获,同时也感谢大家关注订阅号,以及帮忙转发本文 ^_^



Tags:MySQL   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  Search: MySQL  点击:(8)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  Search: MySQL  点击:(15)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  Search: MySQL  点击:(13)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  Search: MySQL  点击:(28)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10  Search: MySQL  点击:(13)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  Search: MySQL  点击:(31)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  Search: MySQL  点击:(53)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  Search: MySQL  点击:(54)  评论:(0)  加入收藏
为什么高性能场景选用Postgres SQL 而不是 MySQL
一、 数据库简介 TLDR;1.1 MySQL MySQL声称自己是最流行的开源数据库,它属于最流行的RDBMS (Relational Database Management System,关系数据库管理系统)应用软件之一。LAMP...【详细内容】
2024-02-19  Search: MySQL  点击:(39)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  Search: MySQL  点击:(55)  评论:(0)  加入收藏
▌简易百科推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  爱可生开源社区    Tags:MySQL   点击:(8)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  爱可生开源社区  微信公众号  Tags:MySQL   点击:(15)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  MySQL学习  微信公众号  Tags:MySQL   点击:(13)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  字节跳动技术团队    Tags:ByteHouse   点击:(28)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10    dbaplus社群  Tags:MySQL   点击:(13)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  转转技术  微信公众号  Tags:MySQL   点击:(31)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  数据库干货铺  微信公众号  Tags:MySQL   点击:(53)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  编程技术汇    Tags:MySQL   点击:(54)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  数据库干货铺  微信公众号  Tags:MySQL   点击:(55)  评论:(0)  加入收藏
mysql索引失效的场景
MySQL中索引失效是指数据库查询时无法有效利用索引,这可能导致查询性能显著下降。以下是一些常见的MySQL索引失效的场景:1.使用非前导列进行查询: 假设有一个复合索引 (A, B)。...【详细内容】
2024-01-15  小王爱编程  今日头条  Tags:mysql索引   点击:(87)  评论:(0)  加入收藏
站内最新
站内热门
站内头条