MySQL原理 - InnoDB引擎 - 行记录存储 - Compact 行格式(上)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MySQL原理 - InnoDB引擎 - 行记录存储 - Compact 行格式(上)

MySQL 服务器上负责对表中数据的读取和写入工作的部分是存储引擎,比如 InnoDB、MyISAM、Memory 等等,不同的存储引擎一般是由不同的人为实现不同的特性而开发的,目前OLTP业务的表如果是使用 MySQL 一般都会使用 InnoDB 引擎,这也是默认的表引擎。


为了能说明 InnoDB 引擎的原理,我们必须先搞清楚 InnoDB 的存储结构,通过这些存储结构才能实现 InnoDB 的事务特性。


首先我们来看看 InnoDB 表的一行数据是如何存储的。InnoDB是一个持久化的存储引擎,也就是数据都是保存在磁盘上面的。但是读写数据,对数据处理,这些是发生在内存中。也就是数据需要从磁盘读取到内存。那么这个读取是如何读取呢?如果处理哪条数据,就读取哪一条到内存中,这样效率也太低了。因为每条数据都是一个硬盘寻址读取,我们要减少这个硬盘寻址读取的次数,可以考虑一块一块的读取数据,这样,我们很可能下次请求需要的数据就已经在内存中了,就省去了从硬盘读取。基于这个思想,InnoDB 将一个表的数据划分成了若干页(pages),这些页通过 B-Tree 索引联系起来。每一页大小默认为 16384 Bytes 也就是 16KB(配置为 innodb_page_size)。


同时,这个 B-Tree 索引就是我们经常听到的聚簇索引(Clustered Index),如果表有主键,那么主键索引就是这个聚簇索引。通过上面的描述,这个索引的节点是包含所有行所有列数据的(就是刚刚我们提到的页)。其他的二级索引的节点只是有指向主键的指针。


对于比较大的字段,例如 Text 类型的字段,如果也存在于这个聚簇索引上,那这个节点数据就会过大,会一下子读取很多页出来,这样读取效率会降低(例如在我们没有想读取这个 Text 列的请求情况下)。所以,InnoDB 对于变长字段,一般倾向于将他们存储在其他地方。至于怎么存储,这个还和 InnoDB **行格式(InnoDB Row Format)**有关。行格式一共有四种:Compact、Redundant、Dynamic和Compressed。

我们可以在创建或修改表的语句中指定行格式:


CREATE TABLE 表 (
)ROW_FORMAT=行格式;
ALTER TABLE 表 ROW_FORMAT=行格式;


Compact 行格式存储


我们来创建一个包含几乎所有基本数据类型的表,其他的例如 geometry,timestamp 等等,也是基于 double 还有 bigint 而来的, text、json、blob等类型,一般不与行数据一起存储,我们之后再说:

create table record_test_1 (
  id bigint,
  score double,
  name char(4),
  content varchar(8),
  extra varchar(16)
)row_format=compact;


插入如下几条记录:

INSERT INTO `record_test_1`(`id`, `score`, `name`, `content`, `extra`) VALUES (1, 78.5, 'hash', 'wodetian', 'nidetiantadetian');
INSERT INTO `record_test_1`(`id`, `score`, `name`, `content`, `extra`) VALUES (65536, 17983.9812, 'zhx', 'shin', 'nosuke');
INSERT INTO `record_test_1`(`id`, `score`, `name`, `content`, `extra`) VALUES (NULL, -669.996, 'aa', NULL, NULL);
INSERT INTO `record_test_1`(`id`, `score`, `name`, `content`, `extra`) VALUES (2048, NULL, NULL, 'c', 'jun');


目前表结构:

+-------+------------+------+----------+------------------+
| id    | score      | name | content  | extra            |
+-------+------------+------+----------+------------------+
|     1 |       78.5 | hash | wodetian | nidetiantadetian |
| 65536 | 17983.9812 | zhx  | shin     | nosuke           |
| NULL  |   -669.996 | aa   | NULL     | NULL             |
|  2048 | NULL       | NULL | c        | jun              |
+-------+------------+------+----------+------------------+

查看底层存储文件:record_test_1.ibd,用16进制编辑器打开,我这里使用的是Notepad++和他的HEX-Editor插件。可以找到如下的数据域(可能会有其中 mysql 生成的行数据不一样,但是我们创建的行数据内容应该是一样的,而且数据长度应该是一摸一样的,可以搜索其中的字符找到这些数据):


微信图片_20220624175141.jpg


我们这里先直接给出这些数据代表的意义,让大家直观感受下:

变长字段长度列表:10 08 
Null值列表:00 
记录头信息:00 00 10 00 47 
隐藏列DB_ROW_ID:00 00 00 00 08 0c 
隐藏列DB_TRX_ID:00 00 00 03 c9 4d 
隐藏列DB_ROLL_PTR:b9 00 00 01 2d 01 10 
列数据id(1):80 00 00 00 00 00 00 01 
列数据score(78.5):00 00 00 00 00 a0 53 40 
列数据name(hash):68 61 73 68 
列数据content(wodetian):77 6f 64 65 74 69 61 6e 
列数据extra(nidetiantadetian):6e 69 64 65 74 69 61 6e 74 61 64 65 74 69 61 6e 
变长字段长度列表:06 04 
Null值列表:00 
记录头信息:00 00 18 00 37 
隐藏列DB_ROW_ID:00 00 00 00 08 0d 
隐藏列DB_TRX_ID:00 00 00 03 c9 4e 
隐藏列DB_ROLL_PTR:ba 00 00 01 2f 01 10 
列数据id(65536):80 00 00 00 00 01 00 00 
列数据score(17983.9812):b5 15 fb cb fe 8f d1 40 
列数据name(zhx):7a 68 78 20 
列数据content(shin):73 68 69 6e 
列数据extra(nosuke):6e 6f 73 75 6b 65 
Null值列表:19 
记录头信息:00 00 00 00 27 
隐藏列DB_ROW_ID:00 00 00 00 08 0e 
隐藏列DB_TRX_ID:00 00 00 03 c9 51 
隐藏列DB_ROLL_PTR:bc 00 00 01 33 01 10 
列数据score(-669.996):87 16 d9 ce f7 ef 84 c0 
列数据name(aa):61 61 20 20 
变长字段长度列表:03 01 
Null值列表:06 
记录头信息:00 00 28 ff 4b 
隐藏列DB_ROW_ID:00 00 00 00 08 0f 
隐藏列DB_TRX_ID:00 00 00 03 c9 54 
隐藏列DB_ROLL_PTR:be 00 00 01 3d 01 10 
列数据id(2048):80 00 00 00 00 00 08 00 
列数据content(c):63 
列数据extra(jun):6a 75 6e

可以看出,在 Compact 行记录格式下,一条 InnoDB 记录,其结构如下图所示:


image.png


Compact 行格式存储 - 变长字段长度列表

对于像 varchar, varbinary,text,blob,json以及他们的各种类型的可变长度字段,需要将他们到底占用多少字节存储起来,这样就省去了列数据之间的边界定义,MySQL 就可以分清楚哪些数据属于这一列,那些不属于。Compact行格式存储,开头就是变长字段长度列表,这个列表包括数据不为NULL的每个可变长度字段的长度,并按照列的顺序逆序排列。

例如上面的第一条数据:

+-------+------------+------+----------+------------------+
| id    | score      | name | content  | extra            |
+-------+------------+------+----------+------------------+
|     1 |       78.5 | hash | wodetian | nidetiantadetian |
+-------+------------+------+----------+------------------+

有两个数据不为NULL的字段contentextra,长度分别是 8 和 16,转换为 16 进制分别是:0x08,0x10。倒序的顺序排列就是10 08

这是对于长度比较短的情况,用一字节表示长度即可。如果变长列的内容占用的字节数比较多,可能就需要用2个字节来表示。那么什么时候用一个字节,什么时候用两个字节呢?

我们给这张表加一列来测试下:

alter table `record_test_1` 
add column `large_content` varchar(1024) null after `extra`;

这时候行数据部分并没有变化。

  • 如果 字符集的最大字节长度(我们这里字符集是latin,所以长度就是1)乘以 字段最大字符个数(就是varchar里面的参数,我们这里的large_content就是1024) < 255,那么就用一个字节表示。这里对于large_content,已经超过了255.
  • 如果超过255,那么:
  • 如果 字段真正占用字节数 < 128,就用一个字节
  • 如果 字段真正占用字节数 >= 128,就用两个字节


问题一:那么为什么用 128 作为分界线呢? 一个字节可以最多表示255,但是 MySQL 设计长度表示时,为了区分是否是一个字节表示长度,规定,如果最高位为1,那么就是两个字节表示长度,否则就是一个字节。例如,01111111,这个就代表长度为 127,而如果长度是 128,就需要两个字节,就是 10000000 10000000,首个字节的最高位为1,那么这就是两个字节表示长度的开头,第二个字节可以用所有位表示长度,并且需要注意的是,MySQL采取 Little Endian 的计数方式,低位在前,高位在后,所以 129 就是 10000001 10000000。同时,这种标识方式,最大长度就是 2^15 - 1 = 32767,也就是32 KB。问题二:如果两个字节也不够表示的长度,该怎么办? innoDB 页大小默认为 16KB,对于一些占用字节数非常多的字段,比方说某个字段长度大于了16KB,那么如果该记录在单个页面中无法存储时,InnoDB会把一部分数据存放到所谓的溢出页中,在变长字段长度列表处只存储留在本页面中的长度,所以使用两个字节也可以存放下来。这个溢出页机制,我们后面和Text字段一起再说。

然后对第一行数据填充large_content字段,对于第二行,将新字段更新为空字符串。

update `record_test_1` set `large_content` = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' where id = 1;
update `record_test_1` set `large_content` = '' where id = 1;

查看数据:


image.png




相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9天前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
5天前
|
存储 关系型数据库 MySQL
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
43 7
|
10天前
|
缓存 关系型数据库 MySQL
MySQL 索引优化与慢查询优化:原理与实践
通过本文的介绍,希望您能够深入理解MySQL索引优化与慢查询优化的原理和实践方法,并在实际项目中灵活运用这些技术,提升数据库的整体性能。
40 5
|
14天前
|
存储 关系型数据库 MySQL
Mysql索引:深入理解InnoDb聚集索引与MyisAm非聚集索引
通过本文的介绍,希望您能深入理解InnoDB聚集索引与MyISAM非聚集索引的概念、结构和应用场景,从而在实际工作中灵活运用这些知识,优化数据库性能。
74 7
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
146 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
21天前
|
存储 关系型数据库 MySQL
MySQL引擎InnoDB和MyISAM的区别?
InnoDB是MySQL默认的事务型存储引擎,支持事务、行级锁、MVCC、在线热备份等特性,主索引为聚簇索引,适用于高并发、高可靠性的场景。MyISAM设计简单,支持压缩表、空间索引,但不支持事务和行级锁,适合读多写少、不要求事务的场景。
51 9
|
22天前
|
SQL 存储 关系型数据库
MySQL进阶突击系列(01)一条简单SQL搞懂MySQL架构原理 | 含实用命令参数集
本文从MySQL的架构原理出发,详细介绍其SQL查询的全过程,涵盖客户端发起SQL查询、服务端SQL接口、解析器、优化器、存储引擎及日志数据等内容。同时提供了MySQL常用的管理命令参数集,帮助读者深入了解MySQL的技术细节和优化方法。
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的InnoDB存储引擎
InnoDB是MySQL的默认存储引擎,广泛应用于互联网公司。它支持事务、行级锁、外键和高效处理大量数据。InnoDB的主要特性包括解决不可重复读和幻读问题、高并发度、B+树索引等。其存储结构分为逻辑和物理两部分,内存结构类似Oracle的SGA和PGA,线程结构包括主线程、I/O线程和其他辅助线程。
【赵渝强老师】MySQL的InnoDB存储引擎

推荐镜像

更多