学习

05日志

undolog #

作用 #

  1. 恢复某条记录原始状态
  2. 记录修改过程,MVCC的原理,结合事务id知道哪些数据可见 undo log的修改本身会被记录到redo log中。即使undo log未刷盘,崩溃恢复时也可以通过redo log重建undo log。

redolog #

  1. 数据持久性–事务提交后,buffer pool一些脏页没有写入数据库磁盘文件。重启时,利用redolog恢复(表空间、页号、偏移量、数值)的数据(磁盘数据)
  2. 由于redolog有几种策略时机刷入磁盘。另有额外线程每隔1s不断刷入redolog buffer pool数据到redolog磁盘日志文件中,如果事务未提交但是刷入了redolog日志文件也无妨。可以根据一些标识,找到哪个事务是未提交的,然后再用undolog恢复原始状态。
root@db211:/var/lib/mysql# ls
auto.cnf	 ibdata1	     public_key.pem
ca-key.pem	 ib_logfile0 (redolog)	     server-cert.pem
ca.pem		 ib_logfile1 (redolog)		 server-key.pem
client-cert.pem  ibtmp1		     sys
client-key.pem	 mysql		     test
db211-slow.log	 performance_schema  xx
ib_buffer_pool 	 private_key.pem

update语句执行流程 #

流程 #

  1. 事务开始
    1. 写Undo Log到Undo Log Buffer(记录旧值)
    2. 修改Buffer Pool中的数据页(生成脏页)
    3. 写Redo Log到Redo Log Buffer(记录物理变更) 修改BufferPool后,还会MySQL还会生成对应的Binlog 事件逻辑操作记录),先写入线程的私有内存缓冲区(Binlog Cache
  2. 事务提交
    1. Redo Log 标记为 Prepare
    2. 写Binlog并刷盘
    3. Redo Log Commit阶段(标记提交)

redolog的刷盘策略 #

  1. Redo Log Buffer 空间不足:当 Redo Log Buffer 的写入速度超过刷盘速度时,InnoDB 会强制刷盘以释放空间。
  2. 后台线程定期刷盘:InnoDB 的后台线程(如 log_writer 和 log_flusher)会周期性刷盘(默认每秒一次,由 innodb_flush_log_at_timeout 控制)。
  3. 参数配置触发
    1. 0:设置为 0 的时候,表示每次事务提交时不进行刷盘操作。这种方式性能最高,但是也最不安全,因为如果 MySQL 挂了或宕机了,可能会丢失最近 1 秒内的事务。
    2. 1:设置为 1 的时候,表示每次事务提交时都将进行刷盘操作。这种方式性能最低,但是也最安全,因为只要事务提交成功,redo log 记录就一定在磁盘里,不会有任何数据丢失。
    3. 2:设置为 2 的时候,表示每次事务提交时都只把 log buffer 里的 redo log 内容写入 page cache(文件系统缓存)。page cache 是专门用来缓存文件的,这里被缓存的文件就是 redo log 文件。这种方式的性能和安全性都介于前两者中间。

这里其实有个疑问,就是redolog并不是事务提交后才刷盘的,而是很有可能事务提交前就刷盘了。如果提交前刷盘了,之后系统宕机了,那么redolog磁盘文件就多出了一些未提交事务的日志。解决办法:可以通过一些属性,在undolog中找到未提交事务的id,然后通过undolog回滚未提交事务。

...

04事务

关闭自动提交 #

在MySQL命令行的默认设置下,事务都是自动提交的,即执行SQL语句后就会马上执行COMMIT操作。因此开始一个事务,必须使用BEGIN、START TRANSACTION,(显示开启事务)或者执行SET AUTOCOMMIT=0,以禁用当前会话的自动提交。

  1. select语句一般用来输出用户变量,比如select @变量名,用于输出数据源不是表格的数据。
  2. 系统变量在变量名前面有两个@
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)
# 修改变量
set autocommit=0;

本文中autocommit=0,开始事务需要使用begin显式开启

事务并发存在的问题 #

没有脏读问题,如果两个事务同时修改一条数据,那么另一个事务的修改操作会被阻塞
【隔离性】脏读(读到了还没有提交的数据)

【隔离性】不可重复读(同一事务中,第1次读的数据和第2次读的数据不一样。读到了事务操作过程中其他事务提交的数据,也不应该,这个没法保证一致性。比如我第一次根据某个事务做了一个操作,第二次想同样的逻辑做一个判断操作,但是数据变了,导致同一个事务中对同一个(其实不是了)数据做的操作居然不一致

【隔离性】虚读、幻读(同一事务中:我第一次读到某一条数据,但是第二次没读到;或者第一次没读到,第二次居然读到了。条数问题)

事务的隔离级别 #

READ_UNCOMMITTED 读未提交
READ_COMMITTED 读已提交
REPEATABLE_READ 重复读 SERIALIZABLE 串行化(行锁) 修改事务隔离级别:

set tx_isolation='SERIALIZABLE'; (可以加global,让新开的session也使用该事务隔离级别)

经测试,如果有三个session(mysql客户端),其中一个(a)是SERIALIZABLE,而其他两个(b,c)是READ_UNCOMMITTED,那么客户端b,c之间不会因为锁挂起,而ab或ac会导致(行)锁挂起

REPEATABLE_READ重复读 #

REPEATABLE_READ重复读的隔离级别只能在一定程度上防止幻读 如果事务a(插入一条新数据,或者删除一条数据)后提交。那么变动的数据不会在事务b被查出来。但是如果插入的这条数据(在事务b,注意 不是事务a)被修改了,那么之后它会在事务b被查出来

新增后修改 #

事务a新增数据后提交,在事务b执行中修改了该数据,则该数据会显示出来(影响了)

...

03存储引擎

表结构、数据、索引

文件目录 #

 root@db211:/var/lib/mysql/mysql# ls | tail -16
tables_priv.frm
tables_priv.MYD
tables_priv.MYI
time_zone.frm
time_zone.ibd
time_zone_leap_second.frm
time_zone_leap_second.ibd
time_zone_name.frm
time_zone_name.ibd
time_zone_transition.frm
time_zone_transition.ibd
time_zone_transition_type.frm   #INNODB存储引擎-表结构
time_zone_transition_type.ibd   #INNODB存储引擎-表数据+表索引(包括所有索引)
user.frm   #MyISAM存储引擎-表结构
user.MYD   #MyISAM存储引擎-表数据
user.MYI   #MyISAM存储引擎-表索引

INNODE存储引擎的表主键聚簇索引和数据在同一个文件(所以即使没有设置主键,innodb也会为每一行自动生成一个默认的隐藏主键列,用来形成B+树)

索引 #

索引创建 #

建表时创建 #

create table index1( id int, name varchar(20), index idx_id_name (id,name));

后续添加 #

create index idx_name on bank_bill (name);

B树 #

m阶B树 ,m个分支(或者说m个区间)
x个元素就会有x+1个区间

B+树和B树区别 #

从B树考虑 #

log2 2000万≈24.3 log500 2000万≈3 (m阶平衡树) 如果是500阶B树,那么2000万个节点最多只需要3层

从B+树考虑 #

MySQL中,一个节点1页,也就是16KB(可以改),而一个节点可以放(主键bigint(8字节)+页号地址 6字节)单元1170个,假设是1000,那两层可以放100 0000万个节点。由于1个节点16KB,如果一条数据用1KB的话,那么最底层每个叶子节点就是放16条数据。那就是3层可以放1600万条数据。这是估算,细算的话应该是2000万左右

...

02增删改查

分类 #

  • DDL 数据定义语言(create,drop,alter)
  • DML 数据操纵语言(insert,delete,update,select)
  • DCL 数据控制语言(grant,revoke)

删除 #

mysql> select * from user;
+----+----------+-----+-----+
| id | name     | age | sex |
+----+----------+-----+-----+
|  1 | zhangsan |  20 | M   |
|  2 | wuqi     |  34 | M   |
|  5 | baobaoi  |  45 | W   |
|  6 | lalal    |  44 | W   |
+----+----------+-----+-----+
4 rows in set (0.00 sec)
mysql> delete from user where id = 6
#如果之后重启了mysql(且id最大为5,则再插入的数据id为6),(如果id最大为10,则插入的数据id为11);如果没有重启,则再插入的数据id为7
mysql> nsert into user(name,age,sex) values('hah',35,'W');

新增 #

一次新增多条数据和多次新增单条数据区别

搜索 #

limit可以优化sql语句,不过要看数据的位置

mysql> select * from t_user  limit 1999999 ,1;
+---------+------------------+------------+
| id      | email            | password   |
+---------+------------------+------------+
| 2000000 | cwng78@yahoo.com | h4HcxZKBNQ |
+---------+------------------+------------+
1 row in set (0.52 sec)
#这条数据在最后一条,所以优化效果不怎么样
mysql> select * from t_user where email='cwng78@yahoo.com';
+---------+------------------+------------+
| id      | email            | password   |
+---------+------------------+------------+
| 2000000 | cwng78@yahoo.com | h4HcxZKBNQ |
+---------+------------------+------------+
1 row in set (0.47 sec)

在第一条或者前面,则优化效果显著

...

01基础知识

本课程大纲 #

部分扩展知识 #

MySQL分为服务端和客户端(两进程),SQLite是进程程序,客户端和服务端都在同一个进程操作数据

安装(windows) #

安装目录下包括数据目录(Data/)和配置文件(my.ini)

安装(debian) #

数据目录 #

root@db211:/etc/mysql# ls -l /var/lib/mysql | awk '{print $1,$3,$4,$9}'
total   
-rw-r----- mysql mysql auto.cnf
-rw------- mysql mysql ca-key.pem
-rw-r--r-- mysql mysql ca.pem
-rw-r--r-- mysql mysql client-cert.pem
-rw------- mysql mysql client-key.pem
-rw-r----- mysql mysql ib_buffer_pool
-rw-r----- mysql mysql ibdata1
-rw-r----- mysql mysql ib_logfile0
-rw-r----- mysql mysql ib_logfile1
-rw-r----- mysql mysql ibtmp1
drwxr-x--- mysql mysql mysql
drwxr-x--- mysql mysql performance_schema
-rw------- mysql mysql private_key.pem
-rw-r--r-- mysql mysql public_key.pem
-rw-r--r-- mysql mysql server-cert.pem
-rw------- mysql mysql server-key.pem
drwxr-x--- mysql mysql sys
drwxr-x--- mysql mysql xx

配置目录 #

root@db211:/etc/mysql# ls -l | awk '{print $1,$9,$10,$11}'
total   
drwxr-xr-x conf.d  
lrwxrwxrwx my.cnf -> /etc/alternatives/my.cnf
-rw-r--r-- my.cnf.fallback  
-rw-r--r-- mysql.cnf  
drwxr-xr-x mysql.conf.d

再进一步查看

...

05MachineLevelProgramming01Basic

概述 #

计算机为了运行程序,会执行一串独立的指令
两种形式的机器语言:

  1. 在计算机上运行的实际目标代码(字节,二进制01)
  2. 汇编代码(编译器的目标,文本格式)

不写汇编,但需要学习:

  1. 编译器产生的结果(汇编及目标代码)与文本代码关联
  2. 低级代码(汇编、二进制)如何实现高级别程序构造
  3. 过程函数、结构体、数组在机器语言中的实现

课程选择64位版本的Intel指令集
为什么叫x86处理器16位微处理器(也称CISC复杂,与之对应的是RISC精简),因为一开始的处理器是8086,接着8286,8386…
需要更加关注–gcc编译生成的代码长什么样

x86进化 #

多核处理器 #

另一个公司:AMD

教学范围 #

哪些处理器 #

  • ARM(AcornRISCMachine),比x86机器功耗更低
  • x86

定义 #

  • 指令集
  • 寄存器:非常小的内存位置
  • 机器指令

(无法直接操作缓存(没有这个概念),是机器级别的概念)

将c源代码转换成目标文件 #

汇编简介 #

gcc -Og -S sum.c #-Og 调试级别的优化过的(英文字母大写O); -S 生成汇编 


带点的这些,是给调试器用的(用来定位);也有一些是给链接器用(告诉它这是个全局定义的函数)

数据类型 #

...

第26章_写作本书时用到的一些重要的参考资料

第26章 写作本书时用到的一些重要的参考资料

感谢 #

我不生产知识,只是知识的搬运工。写作本小册的时间主要用在了两个方面:

搞清楚事情的本质是什么。

这个过程就是研究源码、书籍和资料。

如何把我已经知道的知识表达出来。

这个过程就是我不停的在地上走过来走过去,梳理知识结构,斟酌用词用句,不停的将已经写好的文章推倒重来,只是想给大家一个不错的用户体验。

这两个方面用的时间基本上是一半一半吧,在搞清楚事情的本质是什么阶段,除了直接阅读MySQL的源码之外,查看参考资料也是一种比较偷懒的学习方式。本书只是MySQL进阶的一个入门,想了解更多关于MySQL的知识,大家可以从下面这些资料里找点灵感。

一些链接 #

MySQL官方文档: https://dev.mysql.com/doc/refman/5.7/en/

MySQL官方文档是写作本书时参考最多的一个资料。说实话,文档写的非常通俗易懂,唯一的缺点就是太长了,导致大家看的时候无从下手。

MySQL Internals Manual: https://dev.mysql.com/doc/internals/en/

介绍MySQL如何实现各种功能的文档,写的比较好,但是太少了,有很多章节直接跳过了。

何登成的github: https://github.com/hedengcheng/tech

登博的博客非常好,对事务、优化这讨论的细节也非常多,不过由于大多是PPT结构,字太少,对上下文不清楚的同学可能会一脸懵逼。

orczhou的博客: http://www.orczhou.com/

Jeremy Cole的博客: https://blog.jcole.us/innodb/

Jeremy Cole大神不仅写作了innodb_ruby这个非常棒的解析InnoDB存储结构的工具,还对这些存储结构写了一系列的博客,在我几乎要放弃深入研究表空间结构的时候,是他老人家的博客把我又从深渊里拉了回来。

那海蓝蓝(李海翔)的博客: https://blog.csdn.net/fly2nn

taobao月报: http://mysql.taobao.org/monthly/

因为MySQL的源码非常多,经常让大家无从下手,而taobao月报就是一个非常好的源码阅读指南。

吐槽一下,这个taobao月报也只能当作源码阅读指南看,如果真的不看源码光看月报,那只能当作天书看,十有八九被绕进去出不来了。

MySQL Server Blog: http://mysqlserverteam.com/

MySQL team的博客,一手资料,在我不知道看什么的时候给了很多启示。

mysql_lover的博客: https://blog.csdn.net/mysql_lover/

mariadb的关于查询优化的文档: https://mariadb.com/kb/en/library/query-optimizations/

不得不说mariadb的文档相比MySQL的来说就非常有艺术性了(里边儿有很多漂亮的插图),我很怀疑MySQL文档是程序员直接写的,mariadb的文档是产品经理写的。当我们想研究某个功能的原理,在MySQL文档干巴巴的说明中找不到头脑时,可以参考一下mariadb娓娓道来的风格。

Reconstructing Data Manipulation Queries from Redo Logs: https://www.sba-research.org/wp-content/uploads/publications/WSDF2012_InnoDB.pdf

非官方优化文档: http://www.unofficialmysqlguide.com/optimizer-trace.html

这个文档非常好,非常非常好~

MySQL8.0的源码文档: https://dev.mysql.com/doc/dev/mysql-server

一些书籍 #

《数据库查询优化器的艺术》李海翔著

...

第25章_工作面试老大难-锁

第25章 工作面试老大难-锁

解决并发事务带来问题的两种基本方式 #

上一章介绍了事务并发执行时可能带来的各种问题,并发事务访问相同记录的情况大致可以划分为3种:

读-读情况:即并发事务相继读取相同的记录。

读取操作本身不会对记录有一毛钱影响,并不会引起什么问题,所以允许这种情况的发生。

写-写情况:即并发事务相继对相同的记录做出改动。

我们前面说过,在这种情况下会发生脏写的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过来实现的。这个所谓的其实是一个内存中的结构,在事务执行前本来是没有锁的,也就是说一开始是没有锁结构和记录进行关联的,如图所示:

当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中生成一个锁结构与之关联。比方说事务T1要对这条记录做改动,就需要生成一个锁结构与之关联:

其实在锁结构里有很多信息,不过为了简化理解,我们现在只把两个比较重要的属性拿了出来: - trx信息:代表这个锁结构是哪个事务生成的。 - is_waiting:代表当前事务是否在等待。

如图所示,当事务T1改动了这条记录后,就生成了一个锁结构与该记录关联,因为之前没有别的事务为这条记录加锁,所以is_waiting属性就是false,我们把这个场景就称之为获取锁成功,或者加锁成功,然后就可以继续执行操作了。

在事务T1提交之前,另一个事务T2也想对该记录做改动,那么先去看看有没有锁结构与这条记录关联,发现有一个锁结构与之关联后,然后也生成了一个锁结构与这条记录关联,不过锁结构is_waiting属性值为true,表示当前事务需要等待,我们把这个场景就称之为获取锁失败,或者加锁失败,或者没有成功的获取到锁,画个图表示就是这样:

在事务T1提交之后,就会把该事务生成的锁结构释放掉,然后看看还有没有别的事务在等待获取锁,发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is_waiting属性设置为false,然后把该事务对应的线程唤醒,让它继续执行,此时事务T2就算获取到锁了。效果图就是这样:

我们总结一下后续内容中可能用到的几种说法,以免大家混淆:

+  

不加锁

意思就是不需要在内存中生成对应的锁结构,可以直接执行操作。

+  

获取锁成功,或者加锁成功

意思就是在内存中生成了对应的锁结构,而且锁结构的is_waiting属性为false,也就是事务可以继续执行操作。

+  

获取锁失败,或者加锁失败,或者没有获取到锁

意思就是在内存中生成了对应的锁结构,不过锁结构的is_waiting属性为true,也就是事务需要等待,不可以继续执行操作。

小贴士:这里只是对锁结构做了一个非常简单的描述,我们后边会详细介绍介绍锁结构的,稍安勿躁。

读-写写-读情况:也就是一个事务进行读取操作,另一个进行改动操作。

我们前面说过,这种情况下可能发生脏读不可重复读幻读的问题。

小贴士:幻读问题的产生是因为某个事务读了一个范围的记录,之后别的事务在该范围内插入了新记录,该事务再次读取该范围的记录时,可以读到新插入的记录,所以幻读问题准确的说并不是因为读取和写入一条相同记录而产生的,这一点要注意一下。

SQL标准规定不同隔离级别下可能发生的问题不一样: - 在READ UNCOMMITTED隔离级别下,脏读不可重复读幻读都可能发生。 - 在READ COMMITTED隔离级别下,不可重复读幻读可能发生,脏读不可以发生。 - 在REPEATABLE READ隔离级别下,幻读可能发生,脏读不可重复读不可以发生。 - 在SERIALIZABLE隔离级别下,上述问题都不可以发生。

不过各个数据库厂商对SQL标准的支持都可能不一样,与SQL标准不同的一点就是,MySQLREPEATABLE READ隔离级别实际上就已经解决了幻读问题。

怎么解决脏读不可重复读幻读这些问题呢?其实有两种可选的解决方案:

+  

方案一:读操作利用多版本并发控制(MVCC),写操作进行加锁

所谓的MVCC我们在前一章有过详细的描述,就是通过生成一个ReadView,然后通过ReadView找到符合条件的记录版本(历史版本是由undo日志构建的),其实就像是在生成ReadView的那个时刻做了一次时间静止(就像用相机拍了一个快照),查询语句只能读到在生成ReadView之前已提交事务所做的更改,在生成ReadView之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用MVCC时,读-写操作并不冲突。

小贴士:我们说过普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。在READ COMMITTED隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现象;REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用这个ReadView,这样也就避免了不可重复读和幻读的问题。

...

第24章_一条记录的多幅面孔-事务的隔离级别与MVCC

第24章 一条记录的多幅面孔-事务的隔离级别与MVCC

事前准备 #

为了故事的顺利发展,我们需要创建一个表: CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY KEY (number) ) Engine=InnoDB CHARSET=utf8; 小贴士:注意我们把这个hero表的主键命名为number,而不是id,主要是想和后边要用到的事务id做区别,大家不用大惊小怪~ 然后向这个表里插入一条数据: INSERT INTO hero VALUES(1, '刘备', '蜀'); 现在表里的数据就是这样的: mysql> SELECT * FROM hero; +--------+--------+---------+ | number | name | country | +--------+--------+---------+ | 1 | 刘备 | 蜀 | +--------+--------+---------+ 1 row in set (0.00 sec)

事务隔离级别 #

我们知道MySQL是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称之为一个会话(Session)。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。在事务简介的章节中我们说过事务有一个称之为隔离性的特性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样子的话对性能影响太大,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时性能尽量高些,鱼和熊掌不可得兼,舍一部分隔离性而取性能者也。

事务并发执行遇到的问题 #

怎么个舍弃法呢?我们先得看一下访问相同数据的事务在不保证串行执行(也就是执行完一个再执行另一个)的情况下可能会出现哪些问题:

脏写(Dirty Write

...

第23章_后悔了怎么办-undo日志(下)

第23章 后悔了怎么办-undo日志(下)

上一章我们主要介绍了为什么需要undo日志,以及INSERTDELETEUPDATE这些会对数据做改动的语句都会产生什么类型的undo日志,还有不同类型的undo日志的具体格式是什么。本章会继续介绍这些undo日志会被具体写到什么地方,以及在写入过程中需要注意的一些问题。

通用链表结构 #

在写入undo日志的过程中会使用到多个链表,很多链表都有同样的节点结构,如图所示:

在某个表空间内,我们可以通过一个页的页号和在页内的偏移量来唯一定位一个节点的位置,这两个信息也就相当于指向这个节点的一个指针。所以: - Pre Node Page NumberPre Node Offset的组合就是指向前一个节点的指针 - Next Node Page NumberNext Node Offset的组合就是指向后一个节点的指针。

整个List Node占用12个字节的存储空间。

为了更好的管理链表,设计InnoDB的大佬还提出了一个基节点的结构,里边存储了这个链表的头节点尾节点以及链表长度信息,基节点的结构示意图如下:

其中: - List Length表明该链表一共有多少节点。 - First Node Page NumberFirst Node Offset的组合就是指向链表头节点的指针。 - Last Node Page NumberLast Node Offset的组合就是指向链表尾节点的指针。

整个List Base Node占用16个字节的存储空间。

所以使用List Base NodeList Node这两个结构组成的链表的示意图就是这样:

小贴士:上述链表结构我们在前面的文章中频频提到,尤其是在表空间那一章重点描述过,不过我不敢奢求大家都记住了,所以在这里又强调一遍,希望大家不要嫌我烦,我只是怕大家忘了学习后续内容吃力而已~

FIL_PAGE_UNDO_LOG页面 #

我们前面介绍表空间的时候说过,表空间其实是由许许多多的页面构成的,页面默认大小为16KB。这些页面有不同的类型,比如类型为FIL_PAGE_INDEX的页面用于存储聚簇索引以及二级索引,类型为FIL_PAGE_TYPE_FSP_HDR的页面用于存储表空间头部信息的,还有其他各种类型的页面,其中有一种称之为FIL_PAGE_UNDO_LOG类型的页面是专门用来存储undo日志的,这种类型的页面的通用结构如下图所示(以默认的16KB大小为例):

“类型为FIL_PAGE_UNDO_LOG的页”这种说法太绕口,以后我们就简称为Undo页面了。上图中的File HeaderFile Trailer是各种页面都有的通用结构,我们前面介绍过很多遍了,这里就不赘述了(忘记了的可以到讲述数据页结构或者表空间的章节中查看)。Undo Page HeaderUndo页面所特有的,我们来看一下它的结构:

...