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

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

发现COMPACT行记录格式下,对于变长字段的更新,会使原有数据失效,产生一条新的数据在末尾。

第一行数据原有的被废弃,记录头发生变化,主要是打上了删除标记,这个稍后我们就会提到。第一行新数据:

变长字段长度列表:82 80 10 08 
Null值列表:00 
记录头信息:00 00 30 01 04 
隐藏列DB_ROW_ID:00 00 00 00 08 0c 
隐藏列DB_TRX_ID:00 00 00 03 c9 6e 
隐藏列DB_ROLL_PTR:4f 00 00 01 89 1c 51 
列数据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 
列数据large_content(abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz):61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a

可以看到,变长字段长度列表变成了82 80 10 08,这里的large_content字符编码最大字节大小为1,字段字符最大个数为1024,这里第一行记录这个字段字符数量是130,所以应该用两个字节。130*1转换成16进制为 0x82 也就是 0x02 + 0x80,最高位标识1之后,就是 0x82 + 0x80,对应咱们的变长字段长度列表的开头。

而新的第二行,变长字段长度列表变成了00 06 04,因为实际large_content占用了0个字节。


Compact 行格式存储 - NULL 值列表


某些字段可能可以为 NULL,如果对于 NULL 还单独存储,是一种浪费空间的行为,和 Compact 行格式存储的理念相悖。采用 BitMap 的思想,标记这些字段,可以节省空间。Null值列表就是这样的一个 BitMap。

NULL 值列表仅仅针对可以为 NULL 的字段,如果一个字段标记了not null,那么这个字段不会进入这个 NUll 值列表的 BitMap 中。

NULL值列表占用几个字节呢?每个不为 NULL 的字段,占用一位,每超过八个字段,就是 8 位,就多一个字节,不足一个字节,高位补0。假如一个表所有字段都是not null,那么就没有NULL 值列表,也就占用 0 个字节。并且,每个字段在这个 bitmap 中,类似于变长字段长度列表,是逆序排列的。

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


针对第一第二行记录,由于没有为 NULL 的字段,所以他们的 NULL 值列表为00. 针对第三行记录,他的 NULL 字段分别是 idcontentextralarge_content,分别是第一,第四,第五,第六列,那么 NULL 值列表为:00111001,也就是 0x39。在加入新字段之前NULL 字段分别是 idcontentextra,分别是第一,第四,第五列,那么 NULL 值列表为:00011001,也就是 0x19 针对第四行记录,他的 NULL 字段分别是score,namelarge_content,分别是第二,第三,第六列,那么 NULL 值列表为:00100110,也就是 0x26。在加入新字段之前NULL 字段分别是score,name,分别是第二,第三列,那么 NULL 值列表为:00000110,也就是 0x06。


Compact 行格式存储 - 记录头信息



对于Compact 行格式存储,记录头固定为5字节大小:


名称大小(bits)描述无用位2目前没用到deleted_flag1记录是否被删除min_rec_flag1B+树中非叶子节点最小记录标记n_owned4该记录对应槽所拥有记录数量heap_no13该记录在堆中的序号,也可以理解为在堆中的位置信息record_type3记录类型,普通数据记录为000,节点指针类型为001,伪记录首记录 infimum 行为010,伪记录最后一个记录 supremum 行为011,1xx的为保留的next_record pointer16页中下一条记录的相对位置


对于更新前的第一行和第二行:

第一行记录头信息:00 00 10 00 47 
转换为2进制:00000000 00000000 00010000 00000000 01000111
无用位:00,deleted_flag:0,min_rec_flag:0,n_owned:0000,heap_no:0000000000010,record_type:000,next_record:00000000 01000111
第二行记录头信息:00 00 18 00 37 
转换为2进制:00000000 00000000 00011000 00000000 00110111
无用位:00,deleted_flag:0,min_rec_flag:0,n_owned:0000,heap_no:0000000000010,record_type:000,next_record:00000000 01000111


对于更新后的原始第一行和第二行:

第一行记录头信息:20 00 10 00 47 
转换为2进制:00010000 00000000 00010000 00000000 01000111
无用位:00,deleted_flag:1,min_rec_flag:0,n_owned:0000,heap_no:0000000000010,record_type:000,next_record:00000000 01000111
第二行记录头信息:20 00 18 00 37 
转换为2进制:00010000 00000000 00011000 00000000 00110111
无用位:00,deleted_flag:1,min_rec_flag:0,n_owned:0000,heap_no:0000000000010,record_type:000,next_record:00000000 01000111


可以看出,原有的数据 deleted_flag 变成 1,代表数据被删除。

对于更新后的新的第一行和第二行:

第一行记录头信息:00 00 30 00 ca 
转换为2进制:00000000 00000000 00110000 00000000 11001010
无用位:00,deleted_flag:0,min_rec_flag:0,n_owned:0000,heap_no:0000000000011,record_type:000,next_record:00000000 11001010
第二行记录头信息:00 00 38 fe e6
转换为2进制:00000000 00000000 00111000 11111110 11100110
无用位:00,deleted_flag:0,min_rec_flag:0,n_owned:0000,heap_no:0000000000111,record_type:000,next_record:11111110 11100110

这些信息的其他字段,在我们之后用到的时候,会详细说明。


Compact 行格式存储 - 隐藏列


隐藏列包含三个:

列名大小(字节)描述DB_ROW_ID6主键ID,这个列不一定会生成。优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有定义的话,则会为表默认添加一个名为 DB_ROW_ID 的隐藏列作为主键DB_TRX_ID6产生当前记录项的事务id,每开始一个新的事务时,系统版本号会自动递增,而事务开始时刻的系统版本号会作为事务id,事务 commit 的话,就会更新这里的 DB_TRX_IDDB_ROLL_PTR7undo log 指针,指向当前记录项的 undo log,找之前版本的数据需通过此指针。如果事务回滚的话,则从 undo Log 中把原始值读取出来再放到记录中去


这里我们先不详细展开这些列的说明,只是先知道这些列即可,只会会在聚簇索引说明以及多版本控制分析的章节中详细说明。


Compact 行格式存储 - 数据列 bigint 存储


对于 bigint 类型,如果不为 NULL,则占用8字节,首位为符号位,剩余位存储数字,数字范围是 -2^63 ~ 2^63 - 1 = -9223372036854775808 ~ 9223372036854775807。如果为 NULL,则不占用任何存储空间


存储时,如果为正数,则首位 bit 为1,如果为负数,则首位为 0 并用补码的形式存储。

对于我们的四行数据:

第一行列数据id(1):80 00 00 00 00 00 00 01 
第二行列数据id(65536):80 00 00 00 00 01 00 00 
第三行行列数据id(NULL):空
第四行列数据id(2048):80 00 00 00 00 00 08 00

其他的类似的整数存储,tinyint(1字节),smallint(2字节),mediumint(3字节),int(4字节)等,只是字节长度上面有区别。对应的无符号类型,tinyint unsigned,smallint unsigned, mediumint unsigned,int unsigned,bigint unsigned等等,仅仅是是否有符号位的区别。

同时,这里提一下 bigint(20) 里面这个 20 的作用。他只是限制显示,和底层存储没有任何关系。整型字段有个 zerofill 属性,设置后(例如 bigint(20) zerofill),在数字长度不够 20 的数据前面填充0,以达到设定的长度。这个 20 就是显示长度的设定。


Compact 行格式存储 - 数据列 double 存储


double 的存储对于非 NULL 的列,符合 IEEE 754 floating-point "double format" bit layout 这个统一标准:

  • 最高位 bit 表示符号位(0x8000000000000000)
  • 第二到第十二的 bit 表示指数(0x7ff0000000000000)
  • 剩下的 bit 表示浮点数真正的数字(0x000fffffffffffffL)

同时,Innodb存储在数据文件上的格式为 Little Edian,需要进行反转后,才能取得字段的真实值。 同样的,如果为 NULL, 则不占用空间。

例如:

第一行列数据score(78.5):00 00 00 00 00 a0 53 40
翻转: 40 53 a0 00 00 00 00 00
二进制: 01000000 01010011 10100000 00000000 00000000 00000000 00000000 00000000
符号位:0,指数位10000000101 = 1029,减去阶数 1023 = 实际指数 6,小数部分0.0011101000000000000000000000000000000000000000000000,转换为十进制为0.125 + 0.0625 + 0.03125 + 0.0078125 = 0.2265625, 加上隐含数字 1 为 1.2265625, 之后乘以 2 的 6 次方就是 1.2265625 * 64 = 78.5

计算过程较为复杂,可以利用 Java 的 Double.longBitsToDouble()转换:

public static void main(String[] args) {
    System.out.println(Double.longBitsToDouble(0x4053a00000000000L));
}

输出为 78.5

类似的类型,float,也是相同的格式,只是长度减半。


Compact 行格式存储 - 数据列 char 存储


对于定长字段,不需要存长度信息直接存储数据即可,如果不足设定的长度则补充。对于char类型,补充 0x20, 对应的就是空格。

例如:

第一行列数据name(hash):68 61 73 68 
第二行列数据name(zhx):7a 68 78 20 
第三行列数据name(aa):61 61 20 20 
第四行列数据name(NULL):空

对于类似的 binary 类型,补充 0x00。


Compact 行格式存储 - 数据列 varchar 存储


因为数据开头有可变长度字段长度列表,所以 varchar 只需要保存实际的数据即可,不需要填充额外的数据。

正是由于这个特性,对于可变长度字段的更新,一般都是将老记录标记为删除,在记录末尾添加新的一条记录填充更新后的记录。这样提高了更新速度,但是增加了存储碎片。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
存储 安全 关系型数据库
InnoDB引擎特性
InnoDB事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键。MySQL5.5.5之后,InnoDB作为默认存储引擎,InnoDB主要特性有: InnoDB给MySQL提供了具有提交,回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎。InnoDB锁定在行级并且也在SELECT语句中提供了一个类似Oracle的非锁定读。 InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘关系的数据库引擎所不能匹敌的。 InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池
|
1月前
|
存储 关系型数据库 MySQL
Mysql行格式DYNAMIC和COMPACT区别
总之,选择哪种行格式取决于具体的应用场景,如数据类型分布、读写比例、存储与性能需求等。在处理大量文本或二进制数据且对存储空间敏感的应用中,DYNAMIC格式可能是更好的选择;而对于混合型数据且对读取性能有一定要求的场景,COMPACT格式可能更合适。在设计数据库时,评估这些因素并进行适当测试,可以帮助确定最适合的行格式。
113 0
|
4月前
|
存储 SQL 关系型数据库
(十三)MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?
MySQL是一款支持拔插式引擎的数据库,在开发过程中你可以根据业务特性,从支持的诸多引擎中选择一款适合的,例如MyISAM、InnoDB、Merge、Memory(HEAP)、BDB(BerkeleyDB)、Example、Federated、Archive、CSV、Blackhole.....
|
5月前
|
存储 关系型数据库 分布式数据库
PolarDB产品使用问题之如何用InnoDB引擎创建Federated表
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
52 1
|
4月前
|
存储 关系型数据库 MySQL
InnoDB 引擎技术文档
【7月更文挑战第6天】InnoDB 是 MySQL 数据库中最常用的关系型数据库存储引擎,自 MySQL 5.5 版本以来成为默认存储引擎。它支持事务处理、行级锁定、外键约束以及崩溃恢复能力,特别适合于高并发、高可靠性的应用场景。InnoDB 引擎还提供了对大容量数据的支持,通过聚簇索引实现数据和索引的紧密集成,优化了查询性能。
82 0
|
5月前
|
存储 关系型数据库 MySQL
mysql的InnoDB引擎实现ACID特性的原理
mysql的InnoDB引擎实现ACID特性的原理
|
5月前
|
存储 运维 关系型数据库
PolarDB产品使用问题之如何使用innodb和x-engine混合引擎
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
9天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
23 4
|
7天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
22 1
|
16天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
82 1