当前位置:网站首页>innodb_ruby 视角下 MySQL 记录增删改
innodb_ruby 视角下 MySQL 记录增删改
2022-06-23 03:32:00 【执霜】
前言:学习MySQL的时候,想到一个问题,记录在文件中是怎么存放的?
MySQL技术内幕InnoDB存储引擎--InnoDB数据页结构:通过 hexdump来分析xx.ibd文件,记录对应的是一串二进制
MySQL是怎样运行的--记录在页中的存储 pg 73:记录存放在页中的User Records中,插入新记录时向Free Space申请空间
MySQL是怎样运行的--记录头的秘密 pg 76:User Records中的记录紧密排列。将记录紧密排列的这种结构称为堆heap,记录在堆中的相对位置称为 heap_no,该值随记录的插入递增
MySQL是怎样运行的--update操作对应的undo日志 pg 348:执行 delete语句时,分为delete mark阶段、purge阶段
MySQL是怎样运行的--update操作对应的undo日志 pg 353:执行 update 语句时,分更新主键和不更新主键的情况,最终分为:处理旧记录,插入新数据
反复思考上面的信息后:初步认为,无论插入顺序如何。从物理层面来说,记录在文件中是顺序排放的
从逻辑层面来说,记录是按照主键大小排序并通过链表组织的
考虑到:若记录的逻辑位置和物理位置是对应的,那么在现有结构中插入数据就会导致插入位置之后的记录都要移动
但是缺少验证方式,于是想通过 innodb_ruby 分析一波
1. 插入一条记录
实验数据说明:rush数据库下t数据表
① 表结构
CREATE TABLE `t` (
`ID` int(11) NOT NULL,
`k` int(11) NOT NULL DEFAULT '0',
`s` varchar(16) NOT NULL DEFAULT '',
PRIMARY KEY (`ID`),
KEY `k` (`k`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
② 表数据
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (100, 1, 'aa');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (200, 2, 'bb');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (300, 3, 'cc');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (500, 5, 'ee');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (600, 6, 'ff');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (700, 7, 'GG');
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (800, 8, 'hh');
③ 表信息
实验方案:向数据表t中插入ID=400的行记录
预期结果:按照主键顺序排列,ID=400的记录会放到ID=300的记录后面,
若是在物理文件中采用 append的方式添加则新添加记录在数据页中的偏移量会比ID=800记录的偏移量值大
1.1 page-records 插入记录前
innodb_space -s ibdata1 -T rush/t -p 3 page-records

说明:
① innodb_space -s ibdata1:以系统文件的方式开启 innodb_space
② -T rush/t:数据库 rush 下的表 t,注意-T仅在 -s模式下才能使用,而且表名后面不带.ibd后缀
③ -p 3:查看第3页,通过 space-page-type-regions可以查看所有类型页面的分布情况
从3页开始是INDEX页,前面 0 - 2页分别为FSP_HDR,IBUF_BITMAP,INODE
④ Record xxx:记录在页中的偏移 offset
⑤ (ID=X00):主键,page 3 为索引,使用 page-dump可以查看索引类型,type=:clustered
实验结果分析:上述的按照主键大小顺序插入6条记录,记录在页中的偏移量是递增的
1.2 page-records 插入记录后
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (400, 4, 'dd');
同样执行上述innodb_space -s ibdata1 -T rush/t -p 3 page-records
① 通过 page-records打印的信息
可以看到:ID=400的记录在页中的偏移量为329
实际上这里测试不严谨,应该先判断是否出现由于插入记录导致页分裂的情况,不过这里的数据量很小,没有新增页。
下面是查看页信息的指令
② 通过space-page-type-regions查看页信息
innodb_space -s ibdata1 -T rush/t space-page-type-regions

start:3,end:4,count:2,type:INDEX
表示:数据页类型为INDEX的起始页号为3,终止页号为4,共2页
说明:2个索引页的原因,创建表的时候有PRIMARY KEY (`ID`)和KEY `k` (`k`)
不过,数据量较大的时候即使只有两个索引,应该不止2页【待验证】
③ page-dump查看记录详细信息
innodb_space -s ibdata1 -T rush/t -p 3 page-dump
仅截取新插入ID=400的记录
说明:查询的时候,该记录信息在第4条,infimum和supremum这两条虚记录并不显示
即检索的结果和通过select查询的顺序一致
记录是通过offset标记在页中的位置,通过next串联逻辑位置
ID=300 :id = 300, offset = 184, next = 329
ID=400 :id = 400, offset = 329, next = 213
④ page-directory-summary查看组信息
当前已插入8条记录,而supremum所在的组最多插入8条记录,因此会分裂组到新组中
innodb_space -s ibdata1 -T rush/t -p 7 page-directory-summary

说明:页目录 page directory对记录分组,组偏移量为最大记录的offset,owned为该组中的记录数
infimum--owned 1:自成一组,即该组仅有一条记录
supremum--owned 5:记录范围为1-8,这里并不是因为普通记录只能容纳4条,
而是supremum组满了,需要分裂,而conventional类型的组至少容纳4条记录
ID=400--owned 4:普通组最少4条,而ID=400的记录在本组中主键值最大,将其作为带头大哥
从这里看出在分组的时候还是按照链表的顺序即索引的顺序,否则无法利用二分查找
还有一点需要说明:仅带头大哥的 owned非 0
2. 删除一条记录
MySQL 是怎样运行的 pg 347 :
Page Header 部分中有一个 PAGE_FREE 属性,指向由被删除记录组成的垃圾链表中的头节点
阶段1:delete mark :设置记录的 deleted_flag = 1,此时事务未提交,记录还在正常的记录链表中
阶段2:purge :将记录从正常记录链表中移除,加入到垃圾链表,此阶段在事务提交后执行
实验方案:开启事务,删除ID=700的记录,观察事务提交前后的相关信息
包括:page-dump页记录, record-dump行记录,
2.1 事务提交前
① 开启事务,删除ID=700的记录
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+-----+---+----+
| ID | k | s |
+-----+---+----+
| 100 | 1 | aa |
| 200 | 2 | bb |
| 300 | 3 | cc |
| 400 | 4 | dd |
| 500 | 5 | ee |
| 600 | 6 | ff |
| 700 | 7 | GG |
| 800 | 8 | hh |
+-----+---+----+
8 rows in set (0.00 sec)
mysql> delete from t where ID = 700;
Query OK, 1 row affected (0.00 sec)
mysql>
② 开启新session 查看表 t
可以看到:即使删除一条记录,在事务未提交前,另一个 session 中仍然可以查询到待删除的记录
③ record-dump行记录
innodb_space -s ibdata1 -T rush/t -p 3 -R 271 record-dump

使用 -R offset检索指定偏移量的行记录信息,该offset值可以通过page-records查看,不可任意指定
上述使用ID=700待删除记录和ID=400未删除记录对比
可以看到上述有两处不同:待删除记录 Deleted : true,Insert : false【有点不同,没有 delete_flag 信息】
④ page-dump该记录在页中的信息
innodb_space -s ibdata1 -T rush/t -p 3 page-dump

同其他记录对比了下:仅上述两处不同,info_flags,和 is_insert
其他记录 info_flags = 0,由于修改实际上先处理旧记录再插入新记录,最后看到的info_flags应该同 insert
2.2 事务提交后
① commit 提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
② 开启新 session查看表 t
由于单条SQL自动提交事务,这里可以看到修改后的情况
③ page-dump页记录
已经查找不到 ID=700的记录,而且从打印的行记录信息来看,该记录的前驱指向了后继
④ page-directory-summary页目录
上述分别为事务提交前后的测试结果,可以看到事务提交后,该记录才不再显示到page-records列表中
⑤ page-records页记录信息
从④中可以看到 ID=700的记录再第三组中,也即同supremum在同一组,因此提交后,owned=4
2.3 删除记录后
问题:删除记录后,文件中记录原来的位置怎么处理?
page-illustrate页插图
可以看到:删除一条记录后,其他记录的位置并不受影响,而该条记录的位置被划归到垃圾链表中
所以再次回归主题:记录在文件中物理位置和逻辑位置并不是对应的,逻辑位置依靠链表来组织
那么问题又来了,记录存放的时候紧密排列,修改的时候存储空间变化,怎么处理记录的位置?
留到3.修改一条记录进行分析
2.4 再次新插入记录
测试目的:新插入存储空间相同的记录,验证是否会先使用垃圾链表中的空间
INSERT INTO `t`(`ID`, `k`, `s`) VALUES (900, 9, 'ii');
① session 下检索结果
② page-illustrate页插图
注意看红框里面,上幅图里面,此处被标记为Garbage,现在这里有了新的记录
通过打印记录在页中的偏移情况,进一步验证是否复用了Garbage空间
③ page-records记录的页内偏移
新记录ID=900的记录复用了删除记录ID=700的位置
需要注意的是:这里能复用的前提是,两条记录的存储空间大小相同
MySQL是怎样运行的:每当新插入记录时,首先判断垃圾链表头节点代表的已删除记录所占用的存储空间是否足够容纳这条新插入的记录
后面还有一句:若不能容纳则直接向页面申请新空闲来容纳新记录的节点
拿本书作者常说的一句话:遍历是不可能遍历的,这辈子都不可能遍历
那么由于删除记录产生的碎片空间如何处理?
上图也看到了是有统计Garbage空间的,若最后页面满到无法存放一条新记录时,
会统计Garbage空间的大小以及剩余空间判断能否用来存放新记录,
若能存放,则尝试重组页面记录。意味着将Garbage空间归并到剩余空间中,形成连续可用空间再插入记录
3. 修改一条记录
pg 353:执行 UPDATE 语句时,分为 更新主键 和 不更新主键 两种情况
① 不更新主键:
就地更新:被更新的每个列,更新后列与更新前列占用的存储空间同样大
先删除旧记录,再插入新记录:被更新的列在更新前后所占用的存储空间大小不一致
② 更新主键:
事务提交前:对旧记录进行 delete mark 操作
创建新记录并将其插入到聚簇索引中
3.1 不更新主键
实验方案:分为存储空间变化,不变两种情况,另外验证对于变化的情况,事务未提交前会不会删除记录
存储空间不变:更新ID=400的记录,设置s=DD
存储空间变小:更新ID=800的记录,设置s=h
存储空间变大:更新ID=200的记录,设置s=bbbb
3.1.1 存储空间不变
① sql 更新语句
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t set s = 'DD' where ID = 400;
Query OK, 1 row affected (0.00 sec)
② session 端查询
执行这条查询,足足用了39.448s,或许应该分析一波慢查询日志
但是提交后查询就很快,可能跟 buffer pool 有关,也或许跟快照读有关
③ page-records页记录
显式开启事务,此时事务未提交,但通过page-records查寻s列的值已经变化
但前后 ID=400的记录偏移量并未变化,即仍然在原来的位置上,而且并没有走delete mark那套流程
3.1.2 存储空间变小
① 更新 ID=800的记录,设置 s=h
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t set s = 'h' where ID = 800;
Query OK, 1 row affected (0.00 sec)
② page-records页记录
存储空间变小,仍然会在原来的位置上,但暂时看不到先删除的痕迹
③ page-dump页中行记录
对比上述删除中的操作,info_flags标识并未变化
以上操作均在事务未提交时进行,此时查询到的记录列已经更新,基本说明和2.删除一条记录的流程不同
问题说明:书上写的是只要列变化,就会先删除,但是在其中一列变化,总体不变的情况下是否会变化还需要验证
3.1.3 存储空间变大
① 更新 ID=200的记录,设置 s=bbbb
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t set s = 'bbbb' where ID = 200;
Query OK, 1 row affected (0.01 sec)
② page-records页记录
存储空间变大,ID=200的偏移量为358排在最后一条记录的后面
③ page-directory-summary页目录
页目录变化情况,其实暗含了ID=200的记录已经执行了删除再插入的操作
原因如下:忘记截插入ID=900的图了,不过还是可以进行推理一波
插入ID=900是为了验证复用了ID=700的空间,且由于ID=700在supremum组
相当于supremum组记录数一删一添后并未变化,即 owned=5
但是删除 ID=200的记录后,conventional组的记录owned=3这是不满足最小4条记录规定的
因此会将supremum组中的一条记录划归到conventional
说明:此图未删除ID=700时的分组情况,conventional组最大记录为ID=400,将其标记为带头大哥
而删除记录ID=200后情况应该变成这样
所以ID=500的记录才能被标记为带头大哥
然后就是插入记录,由于conventional组,允许4-8条记录,所以情况变成了现在这样
从而从侧面验证了MySQL是怎样运行的 pg 83非主键顺序插入的时候,普通组的记录数不完全是4
而书上普通组记录全是4的原因是:
主键顺序插入,新记录都会分到supremum组,然后不断分裂新组,而新组至少4条记录
④ page-illustrate页插图
分析上图可以得出的结论:
ID=800由于更新后减少了存储空间,更新后仍在原位,并与下一条记录ID=400之间形成碎片ID=200由于更新后增加了存储空间,删除了原位置上的记录,并append到记录最后
以上操作均在事务未提交时进行,此时查询到相关记录均已经更新,
基本说明和2.删除一条记录的流程不同,即更新记录但不更新主键的情况下,若需要删除记录则直接删除
3.2 更新主键
MySQL是怎样运行的 pg 356
更新主键:意味着记录在聚簇索引中的位置将会发生改变
这种情况下分两步进行:
将旧记录进行
delete mark操作创建新记录,并插入到聚簇索引中
注意:上面未更新主键的情况,已经反复验证不会走delete mark操作
而更新主键时是否会走delete mark操作呢?
测试方案:更新ID=500的记录为ID=700,事务提交前分析该记录的相关变更信息
测试分析:有无必要做存储空间变化的实验,若存储空间不变的情况下都是append到最后一条记录后面
则存储空间变化的情况就没必要做了,此时的 UPDATE操作相当于是先执行DELETE再执行INSERT【后面验证该句有误】
3.2.1 事务提交前 delete mark ?
① 更新ID=500的记录为ID=700
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
+-----+---+------+
| ID | k | s |
+-----+---+------+
| 100 | 1 | aa |
| 200 | 2 | bbbb |
| 300 | 3 | cc |
| 400 | 4 | DD |
| 500 | 5 | ee |
| 600 | 6 | ff |
| 800 | 8 | h |
| 900 | 9 | ii |
+-----+---+------+
8 rows in set (0.00 sec)
mysql> update t set ID=700 where ID = 500;
Query OK, 1 row affected (0.00 sec)
② page-records页记录
注意:此时出现了两条记录
其中更新主键后的记录ID=700复用了ID=200更新后删除的位置
原记录ID=500还在原来位置上
③ page-dump页行记录中的链接关系
可以看到ID=500的记录并未在链表中被移除,其next=242指向ID=600的记录
而更新为ID=700的记录,已经与前后链表节点建立的连接,其next=300指向ID=800的记录
④ page-illustrate页插图
更新ID=200时产生的Garbage被ID=700的记录重用,不过,若是存储空间变大,会插入到最后一条记录后面
这里也说明:不可能重用原来空间的原因,事务未提交时,原来的记录还未物理及逻辑删除
④ page-directory-summary页目录
注意:此时 supremum = 5 ,即确实在执行插入ID=700这条记录
也即Delete Mark线程和Insert线程是独立执行的
MySQL是怎样运行的 pg356:更新主键对旧记录的处理是事务提交前仅执行delete mark,
事务提交后由purge线程将其加入到垃圾链表中
3.2.2 事务提交后
① commit 事务
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
② page-records页记录
仅剩下ID=700的记录
④ page-illustrate页插图
ID=500的记录被删除
④ page-directory-summary页目录
ID=500的记录被删除后,ID=400的记录成为新的带头大哥
普通组中允许最少 4 条记录,因此不需要再从supremum中瓜分
写在最后:本文仅分析了-p 3,由于有两个索引,至少有两个索引页,
-p 4为二级索引页,在修改记录的过程中,也会涉及二级索引页的变化
总结:
插入记录:先判断垃圾链表首节点能否复用,不能复用则
append到最后一条记录后面删除记录:先走
delete mark阶段,即此时事务未提交,记录未删除,种种与该记录有关的内容暂未变更
而事务提交后,由purge线程将该记录加入到垃圾链表,但在物理文件上,该记录的位置仍然保留,
最后若有必要重组文件空间,则会将该部分的碎片空间和剩余空闲空间合并更新记录:分为更新主键和不更新主键
更新主键=删除记录+插入记录【两者并行,删除记录受限于事务是否提交】
不更新主键:根据存储空间是否变化,决定是否复用当前位置原地更新,
若变大或变小则先删除【不走delete mark】再插入,接着就是插入的流程
变小的那种情况,忘记根据page-directory-summary验证是否是先删除了
不过由于存储空间变小复用原来的位置,由于是写覆盖,
若不先删除原有记录,剩余空间上还会保留旧记录,因此必定会先删除
边栏推荐
- I Arouter framework analysis
- Analysis on the development of China's satellite navigation industry chain in 2021: satellite navigation is fully integrated into production and life, and the satellite navigation industry is also boo
- Learning record -- superficial understanding of unity decoupling
- Online signature with canvas
- DDoS attack under Kali
- [quick view] Analysis on the development status and future development trend of the global and Chinese diamond cultivation industry in 2021 [figure]
- How to generate code-11 barcode in batch through TXT file
- ABCD identifier of SAP mm initial cycle count
- Batch generation of Codabar codes using Excel files
- Weekly Postgres world news 2022w03
猜你喜欢
![Analysis on demand and market scale of China's steamed stuffed bun industry in 2020 [figure]](/img/4b/dd272f98b89a157180bf68570d2763.jpg)
Analysis on demand and market scale of China's steamed stuffed bun industry in 2020 [figure]

Analysis on the development of China's satellite navigation industry chain in 2021: satellite navigation is fully integrated into production and life, and the satellite navigation industry is also boo

Encryption related to returnee of national market supervision public service platform
![Analysis on development history, industrial chain, output and enterprise layout of medical polypropylene in China in 2020 [figure]](/img/28/ebfc25ec288627706e15a07e6bdb77.jpg)
Analysis on development history, industrial chain, output and enterprise layout of medical polypropylene in China in 2020 [figure]
![[quick view] Analysis on the development status and future development trend of the global and Chinese diamond cultivation industry in 2021 [figure]](/img/f1/972a760459a6d599b5681aa634df09.jpg)
[quick view] Analysis on the development status and future development trend of the global and Chinese diamond cultivation industry in 2021 [figure]

Gakataka student end to bundle Version (made by likewendy)
![Analysis of China's integrated circuit industry chain in 2021: huge downstream market demand [figure]](/img/de/d73805aaf4345ca3d2a7baf85aab8d.jpg)
Analysis of China's integrated circuit industry chain in 2021: huge downstream market demand [figure]

Analysis on the development of duty-free industry in Hainan Province in 2021: the implementation of the new policy makes the duty-free market in Hainan more "prosperous" [figure]

Detailed discussion on modular architecture design of MCU firmware

Jmeter- (V) simulated user concurrent login for interface test
随机推荐
The difference between code39 and code93
Customization of openfeign
SwiftUI 组件大全之使用 ScrollView 和 GeometryReader 创建动画 3D卡片 滚动效果
How does easyplayer embed a video snapshot into a demo?
What are the advantages of the completely free and open source flutter? How to learn about flutter?
Intelligent voice climbing patio
CentOS install redis
Best practices for building multi architecture mirrors
Engineer culture: should the company buy genuine software
Mybatties plus batch warehousing
【二叉树】993. Cousins in Binary Tree
How to solve the problem that easynvr cannot be cascaded to the easynvs platform? View ports first
2022-01-22: Li Kou 411, the abbreviation of the shortest exclusive word. Give a string number
Goframe framework (RK boot): realize distributed log tracing
New configuration of Alipay
JS event delegation (event agent)
C. Differential Sorting
QUIC or TCP
Configuring multi cluster management using kubectl
Goframe framework (RK boot): enable tls/ssl