MySQL系列-事务的实现之redo(上)
事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这是事务的目的,也是数据库区别于文件系统的重要特征。
理论上,必须同时满足A(原子性)、C(一致性)、I(隔离性)、D(持久性)四个特性的才能称之为事务,但是要知道的是,在实际应用中,数据库厂商会考虑到各种需求的存在,某些数据库引擎并没有严格去满足ACID的特性。
在以上4个特性中,原子性、一致性、持久性是通过数据库的redo log和undo log来实现的,也是本系列的重点,隔离性是通过锁来实现的,此文不表。
redo
redo log
redo log也叫做重做日志,是事务用来实现持久性的主要技术。该日志由两部分组成,一是内存中的重做日志缓存,由于是在系统内存,所以系统宕机就会丢失,但是读写快;二是重做日志文件,在磁盘上,系统与数据库的崩溃都影响不到,是持久的。这节就来梳理一下redo log的数据结构和存储过程。
redo log的通用格式如下图:
重做日志格式
前三个字段是所有重做日志相同的部分,是redo log的header:
redo_log_type记录重做日志的类型
space表示空间的ID
page_no是页的偏移量
之后的redo log body的部分就视类型不同而存储不同的内容了,长度不固定。InnoDB1.2版本总共有51种重做日志类型。
log block
当一个事务提交时就会产生上述的redo log,那最终是否以这样的结构存入log file呢,并不是,这个log首先会被组织成log block,log block才是最终存储的基本单元。log block的结构如图:
log block
可以看到一个log block的大小总共是512字节,与磁盘的扇区大小一致。512字节中包含12字节的heder和8字节的tailer。实际存储log的大小是492字节。
header中包含4个字段,按照从上到下分别占4、2、2、4字节。
LOG_BLOCK_HDR_NO是用来标记log block在log buffer中的位置的,这个log buffer的作用后文再说。这个字段的第1位是用来判断是否是flash big,就是这位并不能用来记录位置,所以这个字段最多的地址空间是2G。
LOG_BLOCK_HDR_DATA_LEN表示log block占用的大小,因为是2字节,所以最大能表示512K的空间,正好是log block的最大容量。
LOG_BLOCK_FIRST_REC_GROUP表示log block中第一个重做日志所在偏移量。举个例子,如果一个日志的大小小于492,那这个日志就是这个block里的第一个日志,偏移量就是12字节。如果现在有个日志是762字节,现在需要两个block才能存储,对于第二个block来说,还需要额外存储762 - 492 = 270字节的内容,那对于这个block,需要注意的是,这个270字节的内容并不能算是第二个block里的第一条日志,这个日志后来存储到block的才算是第一条日志,所以在这个block中,这个字段的值应该是270 + 12 = 282。
LOG_BLOCK_CHECKPOINT_NO表示该block最后被写入时的检查点第4字节的值(不知道是不是翻译的问题,并没有理解这句话的意思)。
redo log的同步
上述行为都是在内存进行的,MySQL为了实现持久化如何需要将这些日志同步到磁盘,MySQL里有三种策略:
先介绍MySQL默认的方式,就是每次事务提交时都将生成的log block刷新到磁盘,由于I/O是非常昂贵的操作,所以这种方式的效率相当低下,一般没有人会用,但是这种方式最安全,不会因为数据库或者系统崩溃丢失日志。
第二种是事务提交时将日志写入log buffer,但是不写入重做日志文件的系统缓存中(上文提到重做日志包含缓存和磁盘文件),然后每秒进行一次刷新到磁盘的操作。这种策略一旦遇到数据库崩溃那么未被刷新到磁盘的内容将会丢失,是三种策略中最不安全的一种。
第三种策略是事务提交时将log block写入重做日志缓存,然后每秒刷新到磁盘。这个策略比第二种要安全一些,除非系统宕机,否则日志不会丢失。
log group & redo log file
log group是一个逻辑上的概念,由多个重做日志文件组成,InnoDB存储引擎只有一个log group。下图就是log group的结构:
log group
根据前文的介绍,log block里才是log的内容,但是从图中可以看到,redo log file除了log block,前面还有4个块存的是log无关的信息,更有意思的是,这两个redo log file,group里只有第一个前4块有内容,第二个是留空的,其实不光是第二个,同一个group里除了第一个file,后面所有的log file的前4块都没有内容。下面来解读一下第一个log file的前4块的内容。
根据前面的介绍,每一个log block的大小是512字节,同样地,这4块每块的大小也是512字节,总共就是2K的内容。先说header后面的存储的是checkpoint值,checkpoint表示的是已经到刷新到数据库磁盘页的位置,cp1和cp2是交替写入的,为了避免因介质失败而导致无法找到可以用的checkpoint。
LSN
LSN是Log Sequence Number的缩写,不同位置的LSN具有不同的含义。InnoDB引擎中,LSN占用8字节,记录重做日志写入的总量,log file header里也有一个LSN,表示该页最后刷新时的位置,这个字段最重要的作用用来同步日志和数据库中的内容,也相当于附带了版本校验的功能。来看一个数据库恢复的例子:
恢复redo log的LSN为13000,而数据库里的LSN为10000,说明数据库的版本落后于redo log,需要进行恢复,这时就会从redo log file10000的位置开始,将10000-13000之间的内容恢复到数据库。