后面也会持续更新,学到新东西会在其中补充。
建议按顺序食用,欢迎批评或者交流!
缺什么东西欢迎评论!我都会及时修改的!
大部分截图和文章采用该书,谢谢这位大佬的文章,在这里真的很感谢让迷茫的我找到了很好的学习文章。我只是加上了自己的拙见。我只是记录学习没有任何抄袭意思。
MySQL 是怎样运行的:从根儿上理解 MySQL - 小孩子4919 - 掘金小册
先啰嗦一下之前说的表空间
前言
通用链表结构
在写入undo日志
的过程中会使用到多个链表,很多链表都有同样的节点结构。
想定位表空间内的某一个位置的话,只需指定页号以及该位置在指定页号中的页内偏移量即可。
Pre Node Page Number
和Pre Node Offset
的组合就是指向前一个XDES Entry
的指针Next Node Page Number
和Next Node Offset
的组合就是指向后一个XDES Entry
的指针。
整个List Node
占用12个字节
的存储空间。
链表的基节点。这个结构中包含了链表的头节点和尾节点的指针以及这个链表中包含了多少节点的信息。
List Length
表明该链表一共有多少节点First Node Page Number
和First Node Offset
表明该链表的头节点在表空间中的位置。Last Node Page Number
和Last Node Offset
表明该链表的尾节点在表空间中的位置。
整个List Base Node
占用16个字节
的存储空间。
FIL_PAGE_UNDO_LOG页面
表空间其实是由许许多多的页面构成的,页面默认大小为16KB
。
这些页面有不同的类型
比如:
类型为FIL_PAGE_INDEX
的页面用于存储聚簇索引以及二级索引
类型为FIL_PAGE_TYPE_FSP_HDR
的页面用于存储表空间头部信息
类型为FIL_PAGE_UNDO_LOG
的页面是专门用来存储undo日志
FIL_PAGE_UNDO_LOG
简称为Undo页面
,File Header
和File Trailer
是各种页面都有的通用结构。
先就此打住!让我介绍一下其他东西加以理解!
事务回滚的需求
事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。
但是也有意外出现:
- 情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。
- 情况二:程序员可以在事务执行过程中手动输入
ROLLBACK
语句结束当前的事务的执行。
这两种情况都会导致事务执行到一半就结束,但是事务执行过程中可能已经修改了很多东西,为了保证事务的原子性,需要把东西改回原先的样子,这个过程就称之为回滚(英文名:rollback)
每当我们要对一条记录做改动时(这里的改动可以指INSERT
、DELETE
、UPDATE
),都需要留一手 —— 把回滚时所需的东西都给记下来。
insert
语句,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。delete
语句,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。update
语句,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。
为了回滚而记录这种操作称之为撤销日志,英文名为undo log
,也就是undo 日志
由于查询操作(SELECT
)并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo日志
。
事务id
给事务分配id
的时机
一个事务可以是一个只读事务,或者是一个读写事务:
- 通过
START TRANSACTION READ ONLY
语句开启一个只读事务。
在只读事务中不可以对普通的表(其他事务也能访问到的表)进行增、删、改操作,但可以对临时表做增、删、改操作。
START TRANSACTION READ WRITE
语句开启一个读写事务,或者使用BEGIN、START TRANSACTION
语句开启的事务默认也算是读写事务。
在读写事务中可以对表执行增删改查操作。
如果某个事务执行过程中对某个表执行了增、删、改操作,那么InnoDB存储引擎
就会给它分配一个独一无二的事务id
,分配方式如下:
- 对于只读事务来说,只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个
事务id
,否则的话是不分配事务id
的。 - 对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个
事务id
,否则的话也是不分配事务id
的。
有的时候虽然我们开启了一个读写事务,但是在这个事务中全是查询语句,并没有执行增、删、改的语句,那也就意味着这个事务并不会被分配一个事务id
。
trx_id
隐藏列
对这个聚簇索引记录做改动的语句所在的事务对应的事务id
而已(此处的改动可以是INSERT
、DELETE
、UPDATE
操作)。
undo日志的格式
为了实现事务的原子性,InnoDB存储引擎
在实际进行增、删、改一条记录时,都需要先把对应的undo日志
记下来。一般每对一条记录做一次改动,就对应着一条undo日志
,但在某些更新记录的操作中,也可能会对应着2条undo日志
。
一个事务在执行过程中可能新增、删除、更新若干条记录,也就是说需要记录很多条对应的undo日志
,这些undo日志
会被从0
开始编号,也就是说根据生成的顺序分别被称为第0号undo日志
、第1号undo日志
、…、第n号undo日志
等,这个编号也被称之为undo no
。
这些undo日志
是被记录到类型为FIL_PAGE_UNDO_LOG
的页面中。
这些页面可以从系统表空间中分配,也可以从一种专门存放undo日志
的表空间,也就是所谓的undo tablespace
中分配。
CREATE TABLE undo_demo (
id INT NOT NULL,
key1 VARCHAR(100),
col VARCHAR(100),
PRIMARY KEY (id),
KEY idx_key1 (key1)
)Engine=InnoDB CHARSET=utf8;
id列
是主键,我们为key1列
建立了一个二级索引,col列
是一个普通的列。
InnoDB
的数据字典,每个表都会被分配一个唯一的table id
。
information_schema.innodb_sys_tables在mysql5.7中有但在mysql9.0没有
mysql> SELECT * FROM information_schema.innodb_sys_tables WHERE name = 'test/undo_demo';
+----------+----------------+------+--------+-------+-------------+------------+---------------+------------+
| TABLE_ID | NAME | FLAG | N_COLS | SPACE | FILE_FORMAT | ROW_FORMAT | ZIP_PAGE_SIZE | SPACE_TYPE |
+----------+----------------+------+--------+-------+-------------+------------+---------------+------------+
| 233 | test/undo_demo | 33 | 6 | 253 | Barracuda | Dynamic | 0 | Single |
+----------+----------------+------+--------+-------+-------------+------------+---------------+------------+
1 row in set (0.00 sec)
undo_demo
表对应的table id
为233
INSERT
操作对应的undo
日志
当我们向表中插入一条记录时会有乐观插入和悲观插入的区分,但是不管怎么插入,最终导致的结果就是这条记录被放到了一个数据页中。
如果希望回滚这个插入操作,那么把这条记录删除就好了,也就是说在写对应的undo日志
时,主要是把这条记录的主键信息记上。
类型为TRX_UNDO_INSERT_REC
的undo日志
,它的完整结构如下图所示:
undo no
在一个事务中是从0
开始递增的,也就是说只要事务没提交,每生成一条undo日志
,那么该条日志的undo no
就增1
。- 如果记录中的主键只包含一个列,那么在类型为
TRX_UNDO_INSERT_REC
的undo日志
中只需要把该列占用的存储空间大小和真实值记录下来,如果记录中的主键包含多个列,那么每个列占用的存储空间大小和对应的真实值都需要记录下来(图中的len
就代表列占用的存储空间大小,value
就代表列的真实值)。
向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。只需要考虑向聚簇索引插入记录时的情况就好了,因为其实聚簇索引记录和二级索引记录是一一对应的,在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的删除操作,做删除操作时就会顺带着把所有二级索引中相应的记录也删除掉。
DELETE
操作和UPDATE
操作对应的undo日志
也都是针对聚簇索引记录而言的
BEGIN; # 显式开启一个事务,假设该事务的id为100
INSERT INTO undo_demo(id, key1, col)
VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');
因为记录的主键只包含一个id列
,所以在对应的undo