作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。
目录
正文
创建测试表:
CREATE TABLE `t6` (
`id` int unsigned NOT NULL,
`name` varchar(32) DEFAULT '',
`mobile` char(11) DEFAULT '',
`sex` enum('男','女','未填写') DEFAULT NULL,
`address` varchar(128) DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`),
KEY `idx_address` (`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
插入测试数据:
INSERT INTO `t6` (`id`, `name`, `mobile`, `sex`, `address`) VALUES
(1, '唐僧', '12800128000', '男', '东土大唐'),
(5, '西梁女王', '11800118000', '女', '女儿国'),
(10, '张三', '13800138000', '男', '张家口'),
(15, '李四', '13900139000', '男', '李家庄'),
(20, '王五', '15900159000', '男', '王家大院'),
(25, '紫霞仙子', '19900199000', '女', '九龙城'),
(30, '猪八戒', '16900169000', '男', '高老庄'),
(35, '孙悟空', '17900179000', '男', '花果山'),
(40, '沙和尚', '18900189000', '男', '流沙河');
示例 SQL:
UPDATE `t6`
SET `mobile` = '17988179888', `address` = '水帘洞'
WHERE `id` = 35;
Update 语句更新表中一条记录,先更新主键索引,再更新二级索引。更新二级索引的流程为:遍历二级索引,每轮循环处理一个二级索引,如果当前二级索引包含 Update 语句的 Set 子句中指定的字段,则更新该二级索引,否则,不更新。
更新主键索引记录之前,会生成 Undo 日志,并写入 Undo 页。更新二级索引记录,不会生成 Undo 日志。更新记录产生的 Undo 日志格式,如下图所示。
各属性详细说明如下:
DB_TRX_ID
字段值。DB_ROLL_PTR
字段值。primary_field_len
字节,存储时不会压缩。
如果主键是由多个字段组成的联合主键,Undo 日志中,按照联合主键定义的字段顺序,写入所有主键字段的长度和值:
primary_field_len_1
primary_field_value_1
primary_field_len_2
primary_field_value_2
... ...
primary_field_len_N
primary_field_value_Nupd_field_len
字节,表示 Update 语句的 Set 子句中指定的字段,更新之前的字段值,存储时不会压缩。
如果更新了 N 个字段(N >= 2),Undo 日志中,按照 Update 语句的 Set 子句中指定的字段顺序,写入更新字段在表中的位置、更新之前的字段值的长度、更新之前的字段值。如下:
upd_field_pos_1
upd_field_len_1
upd_field_value_1
upd_field_pos_2
upd_field_len_2
upd_field_value_2
... ...
upd_field_pos_N
upd_field_len_N
upd_field_value_Nindex_field_bytes
、index_field_pos
、index_field_len
、index_field_value
这个区域占用的总字节数。index_field_len
字节,表示本次 Update 操作之前,二级索引的字段值。
如果表中有 N 个字段(N >= 2)属于二级索引,Undo 日志中,按照二级索引字段在表中出现的位置,写入字段值的长度和字段值。如下:
index_field_pos_1
index_field_len_1
index_field_value_1
index_field_pos_2
index_field_len_2
index_field_value_2
... ...
index_field_pos_N
index_field_len_N
index_field_value_Ntype_flag 属性,表示 Undo 日志的类型,还包含一些标志位,如下。
从图中可以看到,type_flag 分为四部分:
info_bits 属性,表示本次 Update 操作之前,记录的头信息中第 1 字节的第 5 ~ 8 位(对应 offset 4 ~ 7)的标志位,如下。
示例 SQL 更新 t6 表中 <id = 35>
的记录产生的 Undo 日志,如下图所示。
各属性值详细说明如下:
12 | 64
得到,不会压缩,固定占用 1 字节。
12
表示这条 Undo 日志是更新记录产生的,代码里定义为 TRX_UNDO_UPD_EXIST_REC
。
64
表示这条 Undo 日志中包含 lob_flag
属性,代码里定义为 TRX_UNDO_MODIFY_BLOB
。undo_no
属性。事务产生的第一条 Undo 日志编号为 0,第二条 Undo 日志编号为 1,依此类推。<id = 35>
的记录的头信息中第 1 字节第 5 ~ 8 位的值。<id = 35>
的记录中 DB_TRX_ID
字段的值。压缩之后占用 5 字节。<id = 35>
的记录的 DB_ROLL_PTR
字段的值,压缩之后占用 8 字节。int unsigned
,占用 4 字节。index_field_bytes
、index_field_pos
、index_field_len
、index_field_value
这个区域(对应图中紫色区域 offset 388 ~ 418)占用的总字节数。InnoDB 存储引擎的表中,每条记录都有个隐藏字段 DB_ROLL_PTR
,字段长度固定为 7 字节。通过这个字段值可以找到 Undo 日志(也是 MVCC 中记录的历史版本)。
从整体上来看,我们可以认为它是 Undo 日志的地址。但是,这个字段值实际上由 4 部分组成,如下图所示。
各属性详细说明如下:
DB_ROLL_PTR 的计算公式如下:
is_insert << 55 | undo_space_id << 48 | page_no << 16 | offset
以示例 SQL 为例,更新记录时产生 Undo 日志得到的各属性值如下:
用 Shell 按照以上公式计算得到 DB_ROLL_PTR,如下:
# 输出结果为 562949962269009
echo $((0 << 55 | 2 << 48 | 135 << 16 | 337))
没有需要总结的内容。