【数据库设计与实现】第三章:数据后像与前滚

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 数据后像与前滚设计原则事务的持久性要求事务提交时本次事务的修改必须完成持久化工作,而事务修改的block或page在大部分场景下并不是连续的,在持久化设备上表现为大量的随机IO。通过记录后像,可以将随机IO转换为对持久化设备更为有利的顺序IO,并将dirty block或dirty page(指被修改过但尚未完成持久化的block或page)的多次修改合并,节约block或page的持久化次数。后

数据后像与前滚

设计原则

事务的持久性要求事务提交时本次事务的修改必须完成持久化工作,而事务修改的blockpage在大部分场景下并不是连续的,在持久化设备上表现为大量的随机IO。通过记录后像,可以将随机IO转换为对持久化设备更为有利的顺序IO,并将dirty blockdirty page(指被修改过但尚未完成持久化的blockpage)的多次修改合并,节约blockpage的持久化次数。

后像是平衡性能和事务持久性的最有效手段,因此OracleMySQL都采用了后像方案,并将其称为redo log。在设计后像时,需要考虑如下几点:

  • 后像是一个顺序日志流,高并发时会有大量的事务争用日志写入资源,如何降低高并发下的资源冲突;
  • 后像的效率如何,数据库异常重启后前滚的效率如何;
  • 后像文件的组织方式是否能在性能和可靠性之间找到好的平衡点;
  • 后像的设计是否有利于 PITR 、数据复制能延伸功能的高效实现;

MySQL设计原理

物理日志与逻辑日志

物理日志(Physical Log)记录完整的page内容或者page中被修改的内容,采取的方法是记录目标page的物理地址、page内的偏移、修改后的内容、内容的长度。物理日志的优点主要有两个:

  • 独立性:日志的恢复不依赖于原 page 的内容,哪怕原始 page 发生了损坏,仍然可以恢复;
  • 幂等性:同一条日志重复执行多次结果也是正确的,也不会发生异常;

物理日志有上述优点,缺点也很明显,就是日志量会非常大,一个逻辑操作会设计多处修改,每处都需要物理地记录下来(如BTree分裂操作需要记录的内容和一个完整的page基本相当)。逻辑日志(Logical Log)记录的是表上的某个具体操作,例如插入一行记录或者删除一行记录等等,内容非常简洁,但缺点也很明显,缺乏独立性和幂等性。

物理逻辑日志将物理日志和逻辑日志结合起来,平衡两者的优点。采取的原则如下:

  • physical-to-a-page :将操作细分到 page ,每个 page 单独记录日志;
  • logical-within-a-page page 内的修改记录逻辑日志;

此外,不管是哪种日志类型,还存在page一致性问题。一致性包括page内的一致性和page间的一致性。page内一致性指某个操作涉及page内的多处修改,例如插入操作涉及page内的PAGE HEADERUSER RECORDPAGE DIRECTORY等多个部分,一致性要求这些部分都要完成修改。page间的一致性指某个操作涉及page间的修改,且page间存在强的依赖关系,不一致会导致数据结构不一致,例如page间的双向链表指针。

InnoDB存储引擎采用的就是物理逻辑日志方式记录redo log,那InnoDB又是如何解决上述这些异常的呢?

  • 独立性: InnoDB 不解决独立性问题,而是要求必须有一个基础 page ,然后在此 page apply redo log ,所以引入 checkpoint 机制和 double write 机制。 Checkpoint 机制会将 dirty page 按照一定规则刷入数据文件(当然 checkpoint 机制不仅解决基础 page 问题,还解决了恢复时长问题), double write 机制解决 page 部分写问题,确保有一个正确的基础 page apply 物理逻辑日志;
  • 幂等性: InnoDB 引入了 lsn 机制,每个 page 都会记录本次修改的 lsn ,在 apply redo log 时,如果 page lsn 大于等于日志的 lsn ,表示该条日志已经 apply 过,从而解决重复执行的问题;
  • 一致性: InnoDB 引入了 mini transaction 机制,通过 Fix Rules 原则保证了内存中 page 操作的原子性和一致性,通过 mini transaction redo log 的原子性保证 page 间的一致性;

关于checkpointdoublewritemini transaction机制,下面章节会进一步详细展开。

LSN与检查点

所有最终要持久化的修改最终都要记录redo log,所以MySQL直接将redo log的偏移量作为度量修改大小和修改时序(前后关系)的参考,这就是lsnlog sequence number),占用8个字节。lsn出现在多个对象和机制中,表达不同的含义:

  • redo log redo log redo log buffer redo log files 中的位置和长度;
  • page :标志最近一次修改的逻辑时间;
  • checkpoint :标识最近一次 checkpoint 的逻辑时间,该 lsn 之前的 dirty page 都已经刷入持久化设备;

checkpoint解决两个问题:1)物理逻辑日志需要的基础page问题;2)系统恢复时间过长问题。原则上有一个基础page,我们就可以通过apply redo log恢复至最新的page。不过随着redo log的不断增长,这个apply的过程就不断变长,所以我们就需要一种机制将dirty page刷入持久化设备,降低apply的时长,这个机制就是checkpoint。为了保证数据的正确性,checkpoint机制需要满足WAL原则,并存在如下依赖关系:

  • dirty page 前,需要确保该 page 对应的 redo log 已经刷入持久化设备;
  • redo log file 是循环覆盖的,被覆盖前必须确保对应的 dirty page 必须已经刷入持久化设备,否则后像被覆盖,可恢复性将不再具备;
  • page 缓存区的大小限制、恢复时长等综合因素将触发 dirty page 刷入持久化设备;

上述措施是checkpoint需要考虑的基本原则,由于checkpoint和缓冲区管理有较强的依赖关系,将在“缓存管理”章节进一步介绍。

Mini Transaction

数据库是以事务为单位提供数据管理服务的,但在实现层面MySQL将事务进一步分解为mini transaction,并以mini transaction作为其内部的最小执行单元。从数据库实现的角度来看,有些修改之间存在依赖关系,必须要么都实施,要么都不实施,否则内部数据结构就会被破坏,系统就会异常。例如修改page间的双向指针,相关page都必须修改成功,才能确保系统正常。mini transaction将这些操作定义在一个mini transaction中,必须原子地实施。从redo log来看,通过type中的MLOG_SINGLE_REC_FLAG标志位和MLOG_MULTI_REC_END日志将一组有依赖关系,必须保证page间一致性的redo log封装在一个mini transaction中。归属于一个mini transactionredo log必须被原子地执行,从而保证page间的一致性。

MySQLmini transaction设计的更加通用,将其作为执行事务的基本单元,设计锁、page、缓存、log等多个方面,并且满足Fix Rules(即Latch原则)、WALForce-Log-at-CommitFix Rules原则保证了page间和page间的操作时原子的、一致的。WAL要求dirty page刷入持久化设备前,对应的redo log必须先刷入持久化设备,从而将page的随机IO转换为redo log的顺序IO,并降低dirty page的持久化频率。Force-Log-at-Commit要求事务提交时必须将该事务相关的所有redo log同步刷入持久化设备,从而保证事务ACID特性中的持久化特性。

Double Write

MySQLredo log是物理逻辑日志,apply redo log前必须要有一个正确的基础pageMySQLpage大小和存储设备的page大小很可能并不一致,所以在page刷入持久化设备的过程中,意外掉电等异常很可能导致page是不完整的。通过FIL HEADERFIL TAILER可以发现这些不完整的page,但还需要一种机制将不完整的page恢复为完整的page,这就是doublewrite机制。

图3.2-1 double write原理

 

如图3.2-1所示,MySQLsystem table space中开辟了double write segment,为了保证持久设备的连续写性能,前32frag page舍弃不用,申请两个连续的extent。同时在内存中也开辟了12M的缓存,刷page进持久化设备的过程如下:

  • step1 :首先将 page 拷贝到 double write buffer 中;
  • step2 double write buffer 中的 page 积累到一定程度后刷入 double write segment
  • step3 :将已经刷入 double write 中的 dirty page 刷入数据文件;

由于step2step3是串行的,部分写不可能在step2step3都发生,从而保证了基础page的可恢复性,即如果数据文件中的page如果存在部分写,那么double write segment中一定有一个该page的完整pagedouble write是连续的存储空间,一定程度上缓解了double write对性能的影响。

当然如果存储设备能够保证page的原子性写,可以关闭double write功能。Redo logblock大小为512个字节,是所有存储设备的最小写入单元,可以保证原子性,所以redo log不需要采用double write机制。

Redo Log格式与文件组织

图3.2-2 redo log files布局

 

如图3.2-2所示,MySQL redo log文件有多个大小相同的物理文件组成(文件大小和数量可分别通过参数innodb_log_file_sizeinnodb_log_files_in_group配置),redo日志循环使用这些物理文件。每个文件有若干个block组成,每个block的大小为512个字节,block是写redo文件的最小单元。每个redo文件的前4block存放全局信息,其它block用于存放具体的redo日志。

表3.2-1 LOG FILE HEADER结构

长度

含义

LOG_HEADER_FORMAT

4

日志文件的格式

PAD

4

保留

LOG_FILE_START_LSN

8

本日志文件的开始lsn

LOG_FILE_CREATOR

32

存放本日志文件的创建者信息

表3.2-2 CHECK POINT结构

长度

含义

LOG_CHECKPOINT_NO

8

单调递增,每做一次checkpoint1

LOG_CHECKPOINT_LSN

8

checkpoint时已完成的lsn(该lsn前的dirty   page已刷入持久化设备)

LOG_CHECKPOINT_OFFSET

4

LOG_CHECKPOINT_LSN在日志文件中的偏移量

LOG_CHECKPOINT_LOG_BUFF_SIZE

4

checkpointlog buffer pool的大小

LOG_CHECKPOINT_ARCHIVED_LSN

8

归档相关,已废弃

LOG_CHECKPOINT_ARRAY

256

归档相关,已废弃

LOG_CHECKPOINT_CHECKSUM1

4

LOG_CHECKPOINT_NOLOG_CHECKPOINT_ARRAYchecksum

LOG_CHECKPOINT_CHECKSUM2

4

LOG_CHECKPOINT_LSNLOG_CHECKPOINT_CHECKSUM1checksum

每个log file的第1block存放LOG FILE HEADER,记录该redo file的总体信息,详细情况如表3.2-1所示。第1log file的第2block和第4block存放CHECK POINT信息,记录最近一次checkpoint完成的信息,其它log file的对应区域保留为空。采用两个block存放checkpoint信息是防止单个磁盘区域损坏后,仍然可以获得最近的checkpoint信息。CHECK POINT详细情况见表3.2-2所示,LOG_CHECKPOINT_LSN非常关键,记录了checkpoint完成后的lsn,即该lsn前的dirty page都已经刷入持久化设备,所以该lsn前的redo log都可以被释放。redo文件的大小和数量是固定的,block的大小也是固定的,每个文件固定预留出4block,所以可以根据lsn号算法该lsn对应于哪个redo log文件,以及在该文件中的偏移。

图3.2-3 redo log block结构

 

表3.2-3 LOG BLOCK HEADER结构

长度

含义

LOG_BLOCK_HDR_NO

4

最高位的1bit位表示本block是否已经写入持久化设备;

剩余bit位表示block   no,等于lsn/512

LOG_BLOCK_HDR_DATA_LEN

2

block中存放的日志长度

LOG_BLOCK_FIRST_REC_GROUP

2

block中第1mtr日志的位置

LOG_BLOCK_CHECKPOINT_NO

4

block最近一次被写时最新checkpoint的低4字节

如图3.2-3所示,redo log block包括LOG BLOCK HEADERLOG BODYLOG BLOCK CHECKSUM三个部分:

  • LOG BLOCK HEADER :记录 block 的总体信息,详细情况见表 3.2-3 LOG_BLOCK_HDR_NO 根据 lsn 计算得到,日志文件每次被复用,该 block no 都不同;
  • LOG BODY :存放具体的 redo log ,以 redo log record 为单位存放, 1 block 中可以存放多个 mini transaction redo log records 1 mini transaction redo log records 也可能跨越多个 block
  • LOG BLOCK CHECKSUM :尾部,占 4 个字节,整个 block checksum ,用于校验本 block 是否损坏;

图3.2-4 redo log record结构

 

如图3.2-4所示,具体单条redo log record4个部分组成,type表示redo log record的类型,spacepage no表示本条redo log record针对哪个pagebody是具体的日志内容。type的种类达XXXbody随着type的不同而不同:

  • MLOG_SINGLE_REC_FLAG(128) type 1 个字节,最高位是标志位,如果为真表示本条 redo log record 就是 1 mini transaction ,否则表示本条 redo log record 只是 mini transaction 1 record
  • MLOG_MULTI_REC_END(31) :表示 1 mini transaction 的结束,本 redo log record 没有 body 部分;
  • MLOG_1TYPE(1) MLOG_2TYPES(2) MLOG_4TYPES(4) MLOG_8TYPES(8) MLOG_WRITE_STRING(30) :记录 page 链表指针、文件头、 segment page 等修改,详细情况见表 3.2-4 、表 3.2-5 、表 3.2-6
  • MLOG_REC* MLOG_LIST_* MLOG_COMP_REC_* MLOG_COMP_LIST_* :记录 BTree page insert delete update page 分裂合并操作,表 3.2-7 和表 3.2-8 分别给出插入和删除的 redo record 格式;
  • MLOG_FILE_CREATE(33) MLOG_FILE_RENAME(34) MLOG_FILE_DELETE(35) MLOG_PAGE_CREATE(19) MLOG_INIT_FILE_PAGE(29) MLOG_PAGE_REORGANIZE(18) :记录文件和 page 的操作;
  • MLOG_UNDO_* :记录 undo log

表3.2-4 MLOG_1BYTE、MLOG_2BYTES、MLOG_4BYTES结构

长度

含义

type

1

MLOG_1BYTEMLOG_2BYTESMLOG_4BYTES

space id

5(compact)

目标table space

page no

5(compact)

目标page

page_offset

2

后像在page内的偏移

val

5(compact)

后像的具体取值

表3.2-5 MLOG_8BYTES结构

长度

含义

type

1

MLOG_8BYTES

space id

5(compact)

目标table space

page no

5(compact)

目标page

page_offset

2

后像在page内的偏移

val(高4位)

5(compact)

后像的具体取值

val(低4位)

4

后像的具体取值

表3.2-6 MLOG_WRITE_STRING结构

长度

含义

type

1

MLOG_WRITE_STRING

space id

5(compact)

目标table space

page no

5(compact)

目标page

page_offset

2

后像在page内的偏移

len

2

后像的长度

val

var

后像的具体取值

表3.2-7 MLOG_COMP_REC_INSERT结构

长度

含义

type

1

MLOG_COMP_REC_INSERT,表示插入1条记录

space id

5(compact)

目标table space

page no

5(compact)

目标page

columns

2

记录的总列数

unique columns

2

主键索引:主键的列数

二级索引:二级索引+主键的列数

columns length

2*columns

每个列的长度,每个列长度的最高位为1表示该列DATA_NOT_NULL

record offset

2

page内插入位置的偏移

extra size

5(compact)

Extra info flag

record

var

各列的后像值

表3.2-8 MLOG_COMP_REC_DELETE结构

长度

含义

type

1

MLOG_COMP_REC_DELETE,表示删除1条记录

space id

5(compact)

目标table space

page no

5(compact)

目标page

columns

2

记录的总列数

unique columns

2

主键索引:主键的列数

二级索引:二级索引+主键的列数

columns length

2*columns

每个列的长度,每个列长度的最高位为1表示该列DATA_NOT_NULL

record offset

2

page内删除记录的偏移

日志持久化

图3.2-5 redo log record持久化机制

 

从上面章节我们知道MySQLmini transactionMTR)作为执行单元,redo日志也是以mini transaction为单位写入到redo log buffer,并最终写入到redo日志文件中。如图3.2-5所示redo日志从生成到写入日志文件经过如下过程:

  • mini transaction 维护一个本地缓存, mini transaction 执行期间生成的 redo log records 存放在本地缓存中;
  • Mini transaction 执行完成后,将本地缓存中的 redo log records 提交到公共的 redo log buffer 中(缓存的大小可通过参数 innodb_log_buffer_size 设置);
  • 当整个事务提交时, redo log buffer 中的 redo log block 写入到 redo log 文件中;

存储及缓存按照上述三个层次进行组织,但实际执行过程还是比较复杂的。首先看mini transaction的执行和提交过程:

  • step1 :如果待提交的 redo log records 超过了 redo log buffer 1/2 ,将 redo log buffer 扩大一倍;
  • step2 :如果本次写入会覆盖检查点,强制进行一次同步 checkpoint
  • step3 :检查本次修改的 data page 是否是该 table space 自上次 checkpoint 以来的第一次修改。如果是第一次则增加 1 MLOG_FILE_NAME 日志(此为 5.7 的优化,重启恢复时只需要打开相关的 table space 文件);
  • step4 :根据 redo log records 的数量,打上 MLOG_SINGLE_REC_FLAG 标志,或者增加 MLOG_MULTI_REC_END
  • step5 :将 redo log records mini transaction 的本地缓存拷贝到公共的 redo log buffer
  • step6 :如果 redo log buffer 中被写的最后 1 block 未写满,设置该 block LOG_BLOCK_FIRST_REC_GROUP ,并根据 log buffer max_modified_age_async max_checkpoint_age_async 情况设置 check_flush_or_checkpoint (该标志一旦被设置,用户线程在修改 data page 时会刷 dirty page redo log );
  • step7 :将本 mini transaction 涉及的 dirty page 加入到 flush list 中;
  • step8 :释放本 mini transaction 持有的 page latch 、内存等资源;

在上述过程中,step2~step6一直持有log_sys->mutex锁,即在此期间其它mini transaction及后台线程都无法操作公共的redo log bufferstep8释放本mini transaction持有的page latch,即在step8之后,其它会话才能操作本mini transaction操作的data page

redo logredo log buffer刷入redo文件是一个持续的异步过程。Redo log buffer空间不足、事务提交、定期(每11次)、DML执行前redo log buffer可用空间小于1/2checkpoint(遵守WAL)、shutdown数据库实例、切换binlog等都会触发将redo logredo log buffer写入redo文件。一般情况下,redo block都是写入操作系统缓存,由操作系统异步持久化,从而提升效率。但事务提交要满足持久化特性,日志必须持久化成功,这时需要调用fsync确保真正写入持久化设备。由于fsync执行时间比较长,MySQL在调用fsync前会释放log_sys->mutex,这样其它事务可以继续写日志,下次fsync可以通过将多个事务的redo log一次写入持久化设备,从而达到组提交的目的。

重做日志恢复

当数据库正常关闭时,数据库会完成所有dirty page的持久化,并将最后持久化的lsn记录到system table space1pageFIL_HEADERFIL_PAGE_FILE_FLUSH_LSN中。当数据库重启后,会读取FIL_PAGE_FILE_FLUSH_LSN,同时读取redo log文件中的两个checkpoint。比较FIL_PAGE_FILE_FLUSH_LSNcheckpointmax(checkpoint1, checkpoint2)),如果相等则不需要恢复,否则需要恢复,恢复的区间为(checkpoint lsn redo log last lsn)。

图3.2-6 并行恢复内存结构

 

如图3.2-6所示,MySQLspace idpage nohash,维护一个hash列表,列表中每个bucket对应1recv_addr_t结构。如果有hash冲突,则将recv_addr_t通过add_hash串在一起。recv_addr_t通过recv_t结构将本pageredo log recod串在一起,每个recv_t对应于一条redo log record,并记录该recordtypelendata(日志内容)、start lsnend lsn。同一个pagerecv_t按照lsn顺序穿在一起。

系统恢复期间,从(checkpoint lsnredo log last lsn)区间批量读取redo log record,并存放到图3.2-6的内存结构中。如果内存无法容纳,则先开始apply这些redo log recordapply完毕后,再批量读取,再apply,如此循环直至完成所有redo log record的前滚工作。

apply redo log record期间,各page是可以并行apply的(物理逻辑日志的设计原则为physical-to-a-page)。如果pagelsn大于等于redo log recordlsn则跳过该条redo log record(日志的幂等性)。如果所涉page不在缓冲区,则批量读取相关page到内存。如果缓存区内存不足,可以将已经apply完毕的dirty page写入持久化设备。

Oracle设计原理

物理日志与逻辑日志

图3.3-1 逻辑日志、物理日志、物理逻辑日志示例

 

如图3.3-1所示,逻辑日志记录逻辑操作,物理日志记录block的物理变更,物理逻辑日志结合逻辑日志和物理日志的优点:

  • 将操作细分到 block ,每个 block 独立记录日志;
  • 每个 block 内记录逻辑日志;

OracleMySQL一样采用物理逻辑日志记录redo log,并同样需要解决如下问题:

  • 独立性: Oracle 日志本身不解决独立性问题,要求必须有一个基础 block ,然后在此 block apply redo 日志,所以需要 checkpoint 机制。 checkpoint 机制会将 dirty page 按照一定规则刷入持久化设备,确保有一个正确的 block apply 物理逻辑日志;
  • 幂等性:引入 scn 机制,每个 block 都会记录本次修改时的 scn seq Apply redo 日志时,通过比对 redo log block 中的 scn seq ,解决重复执行的问题;
  • 一致性:通过 Fix Rules 原则保证内存中 block 操作的原子性和一致性,通过 mini transaction undo block data block 等相关的日志打包到一个 redo record 中;

Oracle采用物理逻辑日志的方式组织redo日志,最终体现在change vectorredo record这两个结构上。Change vector记录单个block的逻辑修改,redo record由若干个change vector组成,表现为一个mini transaction。例如,一般情况下,1redo record1change vector组成,分别对应某次修改涉及的undo blockdata block

SCN与检查点

Oracle数据库需要一个轻量高效的机制对系统中发生的事件进行排序,从而理清各事件发生的前后关系。为此,Oracle设计了(system change number),一个高效的逻辑时钟,表现为单调递增的数值,由2个字节的wrap scn4个字节的base scn组成。Scn驻留在SGA中,由system commit number latch保护,任何线程或进程要操作scn都要先获得该latchscn设计为2个字节+4个字节,4个字节是CPU操作整型的最小单元,在某些场景下latch保护可以进一步优化)。Scn的推进机制如下:

  • 每个事务的开启和提交都需要明确的时序关系,所以都会导致 scn 1
  • 每个 block 通过 cache layer.scn 标识本 block 被修改的时序,如果 block 在同一个 scn 下被频繁修改,则通过 cache layer.seq 标识各次修改之间的前后关系。但 seq 只占 1 个字节,所以同一个 scn 内某 block 的修改次数超过 255 scn 1
  • 每隔 3 秒自动加 1
  • Oracle 内部发生其它事件;

Oracle内部会控制scn增长率,参数为_max_reasonable_scn_rate,默认32K,即每秒增加不超过32K。即使按照峰值32K计算,scn用满也需要250年。当然,scn只是逻辑时间,在Oracle内部可以非常高效地表示事件的前后关系,但对DBA来说可读性差,所以后台smon进程在sysaux table space中维护了一张SMON_SCN_TIME表,记录scn与墙上时间的对应关系:

  • Oracle9.2 5 分钟更新 1 次,共计维护 1440 条记录,所以粒度为 5 分钟,可查询的历史时间为 (1440*5)/(24*60)=5 天;
  • Oracle10 6 秒更新 1 次,共计维护 144000 条记录,所以粒度为 6 秒,原则上可查询的历史时间为 (144000*6)/(24*60*60)=10 天,实际上 Oracle 后继版本采用动态调整算法,当 scn 增长缓慢时 SMON_SCN_TIME 表的更新频率也变慢;

checkpoint机制同样也用于解决基础block和恢复时长问题,也需要遵守WAL原则。为了识别是否需要恢复,采用实例恢复还是介质恢复,Oraclecheckpoint完成时会记录如下scn

  • system checkpoint scn :系统检查点 scn ,存在于控制文件中, Oracle 会根据 checkpoint 情况持续更新该 scn
  • datafile checkpoint scn :文件检查点 scn ,存在于控制文件中,每个数据文件一个, Oracle 会根据 checkpoint 情况持续更新该 scn (只读 table space 不更新);
  • datafile stop scn :结束 scn ,存在于控制文件中,每个数据文件一个,正常运行期间为 null ,系统正常关闭时设置为结束时的 scn
  • datafile header start scn :文件检查点 scn ,存在于每个数据文件的文件头中, Oracle 会根据 checkpoint 情况持续更新该 scn

checkpoint机制和缓冲区管理有较强的关系,checkpoint的触发及写入机制将在“缓存管理与检查点”章节做进一步介绍。

Fractured Block

当数据库block size大于存储系统的block size时,掉电、操作系统异常等异常很可能导致block断裂(fractured block)。OracleMySQLredo log采用的都是物理逻辑方案,基础block的缺失都会导致无法恢复。为此,MySQL通过double write机制解决partial write问题,那Oracle又是如何解决的呢?

Oracle并没有采用MySQL的事前预防方案,而是采用事后补救方案,主要基于如下考虑:

  • 如果在恢复过程中发现有 fractured block ,从备份数据中提取一个可用的基础 block
  • double write 方案每次写 dirty page 都要写两次持久化设备,且是串行的,影响性能;
  • Oracle 的默认 block size 8K ,发生 fractured block 的概率较小;
  • 在关键应用系统中,会有更强的断电保护措施,存储设备的原子写能力也会更强,发生 fractured block 的概率进一步降低;

可见Oracle为了正常运行期间的性能最大化,采取了事后补救方案,而事后补救方案的核心是备份过程中不能再出现fractured block。备份解决fractured block问题的原理如下:

  • Hot Backup 方案:由于采用的是操作系统 cp 命令, cp 命令和 dbwr 很可能同时操作同一个 block ,所以数据文件的拷贝很可能出现 fractured block 。为此,一旦启动 hotbackup ,某 block 第一次被修改时,会将该 block 的整块内容作为后像保持到 redo log 中(是整个 block ,而不仅仅是 block 中被修改的内容, change vector 的操作类型为 18.1 block image )。这样即使拷贝出的数据文件中有 fractured block redo log 中也有该 block 的基础 block
  • RMAN 备份方案:由于 RMAN 能够完全识别出数据库 block 的格式,通过 cache layer checkval scn seq )和 footer 就可以发现 fractured block 。如果是 fractured block 就重复读取,直至读到正常的 block

Redo Log格式与文件组织

图3.3-2 redo log files全景图

 

如图3.3-2所示,Oracle中与redo日志发生直接或间接关系的主要进程有:

  • DBW :将 database buffer cache 中的 dirty block 写入 data file 中,但需要考虑 WAL 原则,即 dirty block 写入 data file 前需要确保该 dirty block 对应的 redo log 已经写入到 online redo log file 中;
  • LGWR :将 redo log block 持续地从 redo log buffer 写入到 online redo log file 中;
  • CKPT :将 checkpoint 信息写入到 control file data file (头部)中;
  • ARC :运行在归档模式下,将 online redo log files 及时归档,并将归档信息写入到 control file 中;

可见和redo log相关的文件有control filedata fileonline redo log filearchive redo log fileControl file是一个二进制物理文件,是数据库启动的入口,存放数据库的总体信息,主要包括:

  • FILE HEADER :记录 control file 的总体信息,如版本号、 DB ID DB NAME block size control file 也是以 block 为单位组织的), file size control file 的大小,即 block 的数量), file type 等;
  • DATABASE ENTRY :数据库详细信息,如 data file 的数量、 redo log file 的数量、检查点信息( system checkpoint scn );
  • CHECKPOINT PROCESS RECORDS :检查点详细信息;
  • EXTENTED DATABASE ENTRY :备份相关信息;
  • REDO THREAD RECORDS redo log buffer 相关信息;
  • LOG FILE RECORDS redo log file 相关信息;
  • DATA FILE RECORDS :数据文件相关信息( datafile checkpoint scn datafile stop scn );
  • 临时文件条目、表空间条目、 RMAN 配置条目、闪回日志文件条目、进程实例映射条目等等;

图3.3-3 redo log file布局

 

如图3.3-3所示,online redo log files由多个redo log group组成(至少2个),LGWR循环写redo log group。每个redo log group中可以有多个log fileredo log member),这些log file存放的内容是相同的,从而防止单个持久化设备异常导致redo log丢失。LGWR是循环写日志文件的,所以每个redo log file可能处于下列状态之一:

  • CURRENT :当前正在写的日志文件,做实例恢复时 current 状态的日志文件是必须的;
  • ACTIVE :不是当前正在的写的日志文件,但日志对应的 dirty block 尚未刷入持久化设备,实例恢复时 active 状态的日志文件也是必须的;
  • INACTIVE :日志对应的 dirty block 已经刷入到持久化设备,所以 inactive 状态的日志文件可以被恢复,但介质恢复时仍然需要 inactive 状态的日志文件;
  • UNUSED :尚未写入任何日志的文件,一般是刚加入系统的日志文件;
  • CLEARING :正在进行日志清空的文件( alter database clear logfile ),清空完成后状态转换为 unused

表3.3-1 File Header Block部分关键信息

长度

含义

file type

1

文件类型:

  • 0xa2 data file
  • 0xc2 control file
  • 0x22 redo log file

block size

2

redo log block的大小,和操作系统强相关

block counts

4

redo log fileblock数量(不含file header block),所以本redo log file的大小等于(block   counts+1)*block size

magic

4

oracle文件标识符

表3.3-2 Redo Header Block部分关键信息

含义

db version

版本号

db id

db id

sid

实例id

control sequence

控制文件序号

file number

日志文件序号

file type

文件类型,0x0200表示redo log

description

描述

low scn

redo log file中第一条日志的scn

low scn timestamp

low scn对应的墙上时间

next scn

等于下一个redo log filelow scn,本redo log   file处于current状态时next   scn无穷大

next scn timestamp

next scn对应的墙上时间

表3.3-3 Block Header关键信息

长度

含义

signature

4

签名字段

block number

4

block序号,RBA的组成部分之一

sequence

4

redo log文件序号,RBA的组成部分之一

offset

2

第一条redo record在本block内的偏移,RBA的组成部分之一

checksum

2

blockchecksum值,用于检测block是否损坏

图3.3-4 redo file结构

 

下面来看redo log file的内部组织结构。如图3.3-4所示,每个redo log file由固定大小的block组成,block的大小随操作系统而变化,一般为512个字节。Redo log file中各block的情况分布如下:

  • 1 block file header block ,存放本 redo log file 作为文件的总体信息,关键信息有文件类型、 block 大小、文件大小等,详细情况见表 3.3-1
  • 2 block redo header block ,存放本 redo log file 作为 redo log 类文件的总体信息, db id sid 给出了数据库信息, file number 给出了在日志组中的位置, low scn next scn 给出了本日志文件的起始 scn 和结束 scn ,详细情况见表 3.3-2
  • 剩余 block redo record block ,存放具体的 redo records redo record 的长度是变化的,所以有可能 1 block 中存放多条 redo record ,也有可能 1 redo record 跨越多个 block
  • 除第 1 block 之外,其它 block 都有 1 个固定大小的 block header 结构(占 16 个字节),用于描述本 block 的总体信息,详细情况见表 3.3-3

表3.3-4 redo record header部分关键信息

长度

含义

vld

1

组合标志位:

  • 0 the contents are not valid
  • 1 includes change vectors
  • 2 includes commit scn
  • 4 includes dependent scn
  • 8 new scn mark record scn   allocated exactly at this point in the redo log by this instance
  • 16 old scn mark record, scn allocated at or before this   point in the redo, may be allocated by another instance
  • 32 new scn was allocated to ensure redo for some block   would be ordered by inc/seq# when redo sorted by scn

thread

2

标识产生本条redo recordredo log   buffer

rba

10

本条redo record的地址

len

2

本条redo record的长度

scn

6

本条redo recordscn

timestamp

4

本条redo record的墙上时间

表3.3-5 change vector header部分关键信息

长度

含义

op

2

操作类型,标识本条change vector对应的具体操作,由1级操作码和2级操作码组成(layer   code+sub code,各占1个字节),总计多达150+种操作码

cls

2

change vector操作的block的类型:

  • 1: data block
  • 2: sort block
  • 3: save undo segment block
  • 4: segment header block(table)
  • 5: save undo segment header block
  • 6: free list block
  • 7: extent map block
  • 8: 1 st    level bitmap block
  • 9: 2 nd    level bitmap block
  • 10: 3 rd    level bitmap block
  • 11: bitmap block
  • 12: bitmap index block
  • 13: file header block
  • 14: unused
  • 15: system undo segment header block
  • 16: system undo segment block

后继取值都与undo segment no相关,规律如下:

undo segment header block=undo segment no+16

undo segment block=undo segment no+17

afn

2

change vector所涉绝对文件号

dba

4

change vector所涉block地址

scn

6

change vector对应的scn

seq

1

change vector对应的seq(与block中的cache   layer.seq相对应)

type

1

变更类型

obj

2

object id

图3.3-5 change vector结构

 

Change vector针对的是具体某个block的后像,其组成部分如图3.3-5所示,具体包括3个关键部分:

  • change vector header :头部,主要描述 change vector 的总体信息,如操作的类型( op, 做了哪种操作),操作的目标对象( block 的地址 dba 以及该 block 的类型 cls ),操作的逻辑时序( scn seq ),详细情况见表 3.3-5;
  • change record change vector 由多个 change record 组成,用于存放具体的后像。如 insert 11.2 insert row piece )由 3 change record 组成,分别为 ktb kernel transaction layer )、 kdo rowid )、 insert 字段的后像值。 Update 11.5 update row piece )由 4 change record 组成,分别为 ktb kernel transaction layer )、 kdo rowid )、 update 所涉字段的编号及后像值;
  • length vector :描述各个 change record 的长度,内容为 lv_len+len1+len2+...+lenN ,每个 len 2 个字节, lv_len 描述 length vector 的长度, lenN 描述 change recordN 的长度, length vector 及各个 change record 的长度都 4 个字节对齐;

图3.3-6 redo record结构

 

Change vector是针对某个具体block的,而Oracle是以mini transaction为单位组织和管理数据修改的,把归属于同一个mini transaction的一组change vector存放在一个redo record中。例如,针对某条记录的更新,设计记录的undo、记录本身、索引的undo、索引本身,将这些修改的change vector封装在一个redo record中。Redo record的结构如图3.3-6所示,包括如下组成部分:

  • redo record header :描述本条 redo record 的总体信息,主要包括 redo record 的地址( rba )、长度( len )、逻辑时间( scn )和墙上时间( timestamp ),详细情况见表 3.3-4
  • change vector :存放具体某个 block 的修改信息(前像);

至此,我们可以根据rba地址从redo log file中找到对应的redo record,并据此解析出我们需要的信息:

  • 通过 rba 可以计算出 redo record 所在的 redo 文件、 block no ,以及在 block 内的偏移;
  • 通过 record header 中的 len 可以得到本条 redo record 的长度,也可以据此得到下一条 redo record 的位置;
  • record header 只有就是第 1 change vector ,根据 change vector header 之后的 length vector 获得本 change vector 的总长度,并能据此得到下一条 change vector 的位置;
  • 获得 change vector header 以及各个 change record 的长度之后,就可以提取所有前像内容;

表3.3-6 change vector header主要操作码

一级操作码

含义

4

block cleanoutITL,延迟块清除)

5

Transaction managementundo):

  • 5.1: undo block
  • 5.2: update undo segment header(start transaction)
  • 5.4 commit transaction
  • 5.11 rollback DBA in transaction table entry
  • 5.19 transaction start audit log record
  • 5.20 transaction continue audit log record

10

Index operations

  • 10.2: insert leaf row
  • 10.3: purge leaf row
  • 10.4 delete leaf row
  • 10.5: restore leaf row
  • 10.6: lock block
  • 10.7: clear block opcode on commit
  • 10.8: initialize header
  • 10.9: apply XAT do to ITL 1
  • 10.10: set leaf block next pointer
  • 10.11 set leaf block previous pointer
  • 10.12 initialize root block after split
  • 10.13 make leaf block empty
  • 1015 insert branch row
  • 10.16 purge branch row
  • 10.18 update key data in row
  • 10.19 clear split flag
  • 10.21 undo branch operation
  • 10.22 undo branch operation
  • 10.24 shrink ITL
  • 10.30 update nonkey value
  • 10.31 create/load index
  • 10.34 make leaf block empty

11

Row operations(DML):

  • 11.2: IRP(insert single row)
  • 11.3: DRP(delete single row)
  • 11.4: LKR(lock row)
  • 11.5: URP(update row)
  • 11.6: ORP(chained row)
  • 11.9: CKI(cluster key index)
  • 11.10: SKL(set cluster key pointers)
  • 11.11: QMI(insert multiple rows)
  • 11.12: QMD(delete multiple rows)

13

Segment management(block allocation)

14

Extent management(extent allocation)

17

Tablespace management(backup management):

  • 17.1: end backup marker

18

Block image(hot backup):

  • 18.1: block image

19

Direct loader:

  • 19.1: direct load blocks(when archive enabled)
  • 19.2: direct loader invalidate block range(no logging)

20

Compatibility segment(transaction metadata/logminer)

22

Local managed tablespaces(ASSM)

23

Block writes(DBW):

  • 23.1: blocks written from buffer cache to disk by DBW

24

ddl statements

图3.3-7 redo record&change vector示例

 

最后我们看一下DML语句对应的redo recordchange vector是如何组织和布局的。如图3.3-7所示,假设表t1,该表上有索引index1,事务分别执行insertupdatedelete语句。生成redo recordchange vector的过程如下:

  • 事务的第 1 条语句是 insert 语句,该语句涉及 data block index block 的修改,以及对应 undo 记录的生成,这些都需要记录 redo 日志,并作为 1 mini transaction 打包到 1 redo record 中:
    • change vector1(5.2) :启动事务需要更新 undo segment header block 中的 transaction control transaction table ,本 change vector 记录 undo segment header 的后像;
    • change vector2(5.1) :对 data block 执行 insert 操作需要产生 undo 记录,本 change vector 记录该 undo 记录的后像;
    • change vector3(11.2) :记录 data block 中本次 insert 操作的后像;
    • change vector4(5.1) change vector5(10.2) :分别记录对 index block 执行 insert 操作时, undo block index block 的后像;
  • 事务的第 2 条语句是 update 语句,该语句同样涉及 data block index block 修改,以及对应的 undo 记录,对应于第 2 redo record
    • Change vector1(5.1) change vector2(11.5) 分别记录 undo block data block 的后像,对应于记录的 undo 和记录本身;
    • oracle 会将索引的更新转换为索引的删除和插入操作,每个操作又封闭有 undo block index block 上的后像,索引对应 change vector3(5.1) change vector4(10.4) change vector5(5.1) change vector6(10.2)
  • 事务的第 3 条语句是 delete 语句,该语句同样涉及 data block index block 修改,以及对应的 undo 记录,对应于第 3 redo record
    • change vector1(5.1) change vector2(11.3) 对应于记录的 undo 和记录本身;
    • change vector3(5.1) change vector4(10.4) 对应于索引的 undo 和索引本身;
  • 事务的第 4 条语句是 commit 语句,对应于第 4 redo record change vector 5.4 )记录 undo segment header block transaction table 中的状态设置,表示事务已经提交;

上述过程给出了Oracle在事务执行期间生成redo record的最简过程,单条语句也可能涉及大量的数据修改,这时会生成多条redo record。对于单条语句的redo record,其记录的主要内容如下:

  • insert(11.2) :记录插入行的各列值;
  • update(11.5) :记录被更新列的目标值;
  • delete(11.3) :记录删除行的 RowID
  • undo(5.1) :记录 undo record heap

日志持久化

Oracle在下列情况下将redo log buffer中的redo record刷入持久化设备中:

  • 3 秒触发;
  • 阈值达到触发,包括 redo log buffer 已使用 1/3 空间,或者 redo log buffer 已缓存 1M 日志;
  • 用户提交触发,满足事务的持久化特性;
  • DBW 触发,满足 WAL 原则;

用户提交触发redo record持久化,在高并发下日志持久化过程将是并发冲突的关键瓶颈点。为此,Oracle在持续优化日志持久化机制,表现为初始版本只有1redo log buffer,在Oracle7引入多个redo log buffer以提升并发性,并在Oracle8Oracle9中进一步增强为public redolog strands(PBRS)Oracle10引入了private redo log buffer机制,即private redolog strands(PVRS),进一步降低冲突,提升并发性。由于这些机制的基本原理是相同的,所以首先从1redo log buffer讲起。

图3.3-8 redo log buffer区域划分

 

如图3.3-8所示,redo log buffer也是循环使用的,可以划分为空闲区域、LGWR正在持久化区域、用户进程正在写入区域。指针A指向空闲区域的头部,用户进程每次申请新的待写入区域,指针A都会向前推进。指针B指向空闲区域的尾部,一旦LGWR完成某段区域的持久化,该区域就会被释放出来,表现为指针BPoint1推进到Point2

了解了redo log buffer的区域原理后,我们来看用户进程写redo record的过程。用户在自己的PGA区域中缓存redo record,当用户提交或者达到一条完整的redo record后,就将该redo record拷贝到redo log buffer中。过程如下:

  • 获取 redo copy latch
  • 获取 redo allocation latch
  • 为本次 redo record 分配 redo log buffer 空间,即移动指针 A
  • 释放 redo allocation latch
  • redo record PGA 复制到 redo log buffer 的对应区域中;
  • 释放 redo copy latch
  • 如果此时 redo log buffer 已使用空间达到 1M 或者达到总空间的 1/3 ,通过 LGWR 进程写持久化设备;
  • 如果 redo record 是提交日志,通知 LGWR 写持久化设备,并将自己和日志地址放到 xxx list 中;
  • redo record apply 到对应的 data block undo block 上;

Data blockundo block的实际变更发生在对应的redo record拷贝到redo log buffer之后。用户进程在通知LGWR时,首先获取redo writing latch,并检查写标志位。如果获取不到redo writing latch,或者写标志位已经被置上,表明LGWR已经在运行中,不需要重复发送通知,否则要将LGWR进程唤醒。LGWR进程唤醒后的执行过程如下:

  • step1 :获取 redo writing latch
  • step2 :设置写标志位;
  • step3 :释放 redo writing latch
  • step4 :获取 redo allocation latch
  • step5 :计算本次要持久化的内存区域,原则上指针 B 到指针 A 的区域都需要持久化,但此时可能存在尚未完成从 PGA redo log buffer 的拷贝, LGWR 通过监视 redo copy latch 判断拷贝完成的时机,最终计算出 point2
  • step6 :释放 redo allocation latch
  • step7 :将指针 B point2 之间的内存持久化到日志文件中;
  • step8 :获取 redo allocation latch
  • step9 :将指针 B 移到 point2 位置;
  • step10 :释放 redo allocation latch
  • step11 :检查 xxx list ,如果已持久化的日志地址大于记录的日志地址,通知这些用户进程提交完成;
  • step12 :检查 xxx list ,如果还有用户进程在等待日志持久化,进入 step4 继续持久化;
  • step13 :检查 xxx list ,如果没有用户进程在等待日志持久化,获取 redo writing latch ,将写标志置为空,释放 redo writing latch ,将自己阻塞到后台;

LGWR持续地尽最大可能地持久化日志,通过设计redo allocation latchredo copy latch将提交的并发度尽可能最大化:

  • LGWR 写日志到持久化设备是最消耗时间的,但此时不占用任何 latch ,不影响用户进程的并发提交;
  • 用户进程的最长时间发生将日志从 PGA 拷贝到 redo log buffer 上,此时仅占用 redo copy latch ,不影响 LGWR 和其它用户进程申请空间;

可见,LGWR在写日志期间,大量的并发事务仍然可以向redo log buffer写日志,这样LGWR在下一轮持久化时可以一次性完成所有事务的提交,这就是组提交。

图3.3-9 并行redo log buffer布局

 

在单个redo log buffer时,同时只能有一个用户进程向redo log buffer拷贝redo record(由redo copy latch保护)。单个用户进程本身会频繁地向redo log buffer拷贝redo record1redo record拷贝一次),高并发下大量用户进程并发向redo log bufferredo record,表现为大量用户进程争用redo allocation latchredo copy latch。为此,Oracle将单个redo log buffer优化为多个redo log buffer,每个buffer都有自己的redo allocation latchredo copy latch,从而提高并发性。同时用户进程写redo log buffer的过程做如下调整:

  • 以立即获得模式尝试所有 redo log buffer redo copy latch ,如果全部都无法获得则随机阻塞在某个 redo copy latch 上等待;
  • 获得了 redo copy latch ,就决定了使用哪个 redo log buffer ,然后申请对应的 redo allocation latch

LGWR进程只有1个,所以redo writing latch数量不变。不过在计算各redo log buffer的持久化区域时需要获得所有redo log bufferredo allocation latch,一旦完毕就可以释放这些redo allocation latch。等待用户进程完成各redo log buffer中的持久化区域的拷贝,然后确定各redo log buffer日志写入online redo log file的顺序,并开始持久化过程。

多个redo log buffer主要用于解决大量用户进程高并发地写redo log buffer,而大量用户进程的并发性实际上取决于cpu的数量。因此,Oracle根据cpu数量自动化管理redo log buffer的数量,计算公式为celing(1+cpu_count/16),当然也可以通过参数_log_parallelism_max_log_parallelism_dynamic进行干预。

最后再讨论一下redo log浪费和PL/SQL提交改进。LGWRredo log buffer刷入redo log file的单位redo block。当刷入时如果redo block尚未写满,Oracle出于性能考虑会将尚未写满的部分做好填充,然后写入redo log file。否则LGWR不得不从持久化设备上读出本redo block,然后更新本次内容,再写入redo log file,发生2IO,影响性能。当然在高并发下,组提交是大概率事件,redo log浪费不会太严重。

PL/SQL代码块中用户的代码逻辑可能是循环提交,这时Oracle会做优化,不会每次提交都将redo record复制到redo log buffer,并等待LGWR将对应搞得redo block写入持久化设备。而是牺牲一定的持久性,在循环完成的哪个时间点再进行提交(当然如果日志量过大也会提前出发)。在Oracle11版本将这些优化开放给用户选择,用户可以通过commit_loggingcommit_waiting进行控制。

PVRSIMU

通过多个redo log buffer一定程度上缓解了redo log buffer的争用,但此处仍然是系统的热点所在。为此,Oracleredo log buffer基础上引入了private redo log buffer,即PVRS。相对于PVRS,原来的多个redo log buffer称为PBRSPVRS是在shared pool中开辟大量小的private redo log buffer32位版本每个buffer的大小为64K64位版本每个buffer的大小为128K),每个private redo log buffer有一个redo allocation latch保护。用户进程启动某个事务时,申请绑定某个private redo log buffer,即申请该private redo log bufferredo allocation latch,之后该事务在运行过程一直占用该private redo log buffer,针对data block产生的change vector直接记录在该private redo log buffer中,不再需要频繁地向redo log buffer进行拷贝。当然,如果申请不到private redo log buffer,仍然走原来的流程,即PBRS

PVRS缓存了data blockchange vector,那么undo blockchange vector怎么办呢?为此Oracle设计了IMU机制。IMU同样在shared pool中开辟大量小的IMU buffer64K~128K),事务在运行期间产生的undo日志不直接存放到undo block中(对undo segment header block的修改还是实时进行的),而是存放在IMU buffer中。在事务提交前,没有实际产生undo block,所以也就不会产生undo dirty block,不会发生相关IO,用户回滚也非常高效。

为此,change vector成对打包(分别针对undo blockdata block)写入redo log buffer,以及修改data block的过程调整如下:

  • 获得一个 private redo log buffer 和一个 IMU buffer
  • 为本事务所涉的 data block 打上 PVRS 标签(不修改 data block );
  • 将每条与 undo block 相关的 change vector 保存到 IMU buffer 中;
  • 将每条与 data block 相关的 change vector 保存到 private redo log buffer 中;
  • 在事务提交时,将 IMU buffer private redo log buffer 中的 change vector 合并为一条 redo record ,并复制到 PBRS 中;
  • 根据 redo record 修改相关 undo block data block

通过上述缓存机制极大地降低了申请PBRS的次数,一个事务申请一次,从而降低了redo copy latchredo allocation latch的争用。当然IMU buffer引入了IMU latch,每个IMU buffer都需要一个IMU latch保护。不过从undo的角度来看,一个IMU latch替代了一个redo copy latchredo allocation latch,且IMU本身latch随着IMU buffer的增加而增加,降低了争用概率。

由于PVRSIMU数量有限,且PVRSIMU本身的容量也有限。当事务开始时申请不到PVRSIMU,仍然走PBRS路径。即使在事务执行过程中,有PVRSIMU释放出来,该事务也仍然走PBRS路径。当PVRSIMU缓存满时,事务也会提前结束PVRSIMU,将redo record复制到PBRS中,后继执行过程走PBRS路径。

PVRSIMU的引入还带来一个潜在问题,当redo log file发生切换时,private redo log buffer中的redo record尚未写入到redo log file中。这时很可能出现private redo log buffer中缓存的redo recordscn小于当前redo log filelow scn。为了解决该问题,Oracle会计算所有private redo log bufferIMU bufferredo log buffer的缓存总大小。如果当前redo log file的剩余空间等于缓存的总大小时,强制将缓存中的日志写入redo log file,并进行日志文件切换。此时各缓存可能并没有用满,所以redo log file最后一点区域可能是空的,这就是为什么归档日志文件有时比在线日志文件小一点的原因。

重做日志恢复

数据库正常关系时,系统检查点scn、各数据文件检查点scn和结束scn、各数据文件头部的开始scn都是相等的。这时数据文件中的数据是最新的,重启后无需根据redo log file做任何恢复或前滚操作。

数据库非正常关系时,系统系统检查点scn、各数据文件检查点scn、各数据文件头部的开始scn都是相等的,但各数据文件的结束scn为无穷大。这时数据文件中的数据很可能不是最新的,需要进行实例恢复。从控制文件检查点条目中找到lrba(检查点队列中第一个dirty block第一次修改的redo record的地址),以该rba为起点,applyredo log file之后的所有redo record

数据文件异常,从备份系统中提取该数据文件的历史版本。该数据文件头部的开始scn小于该数据文件检查点scn,此时需要进行介质恢复。以数据文件头部的开始scn为起点,applyredo log file中之后所有与本数据文件相关的redo record。可能的方法是通过各redo log filelow scnnext scn(在redo header block中)定位到起始的redo log file,再遍历redo record,通过redo record中的scn定位到起始的redo record

apply redo record的过程中,会比较data block中的scnscncache layer)与change vector中的scnscnchange vector header)。当满足下列两个条件之一时,不需要apply,跳过本change vector,从而解决redo log的幂等性问题。两个条件分别为:

  • data block 中的 scn 大于 change vector 中的 scn
  • data block 中的 scn 等于 change vector 中的 scn ,但 data block 中的 seq 大于等于 change vector 中的 seq

总结与分析

事务的持久性要求事务提交时redo log必须完成持久化工作,从而导致redo log的持久化机制成为数据库高并发下的热点争用资源。为此,OracleMySQL都设计了事务本地缓存、公共缓存、日志文件三级机制。本地缓存以mini transaction为单位将日志从本地缓存拷贝到公共缓存,公共缓存以block为单位再将日志从公共缓存持久化到日志文件中。MySQL通过log_sys->mutex保护三级机制之间的协调,Oracle则通过redo copy latchredo allocation latchredo writing latch进行协调,以获得更好的并发性能,并在此基础上进一步研发出多个redo log bufferprivate redo log bufferIMUOracle在高并发上相对于MySQL做了大量的优化工作,所以在多核环境下Oracle的并发性优势会非常明显。

MySQL以日志在日志文件中的相对顺序(lsn)作为度量数据库内部事件的先后关系,Oracle则采用逻辑时间(scn)来标识事件的前后关系。lsn的优势是简单,但和日志中的偏移强绑定,在集群或分布式环境中,涉及多个独立的日志,标识全局时序关系会非常困难。Scn是独立的逻辑时间,既避免了日志系统的束缚,又避免了墙上时间的种种限制。在集群或分布式环境下,各节点可以按照某种同步算法进行同步,从而为所有节点上的事件进行排序。Oracle为了解决高频更新下scn推进得过于频繁,在每个block上增加了seq,降低数据的频繁更新给scn带来过大压力的问题。

blockpage损坏上,MySQLpartial write单独区分出来,并通过double write来解决。Oracle则拉通所有的block损坏,通过备份、备机等方式来修复损坏的block。这与两者的设计理念有关,MySQL主要运行在低端环境中,且默认的page大小为16K,所以掉电或异常重启导致partial write的概率较大。Oracle的默认block大小为8K,并认为partial write是小概率事件。为了正常情况下的性能最大化,没有采用事前预防的方案,而是归结到通用的block损坏,通过事后修复的方案来补救。相比OracleMySQLPostgreSQL采取了折中方案,并没有每次写dirty page时都写两次,而是在每次checkpoint之后dirty page第一次被写出时需要同时写一份到xlog中,该方案的缺点是增加xlog的大小。

redo log设计上,MySQLOracle也非常相似,insert记录所有列,update记录更新所涉相关列,delete记录标识(OracleRowIDMySQL为主键列),undo记录整条前像。两者都以mini transaction为单位管理日志,MySQLmini transaction粒度较小,Oracle出于性能的考虑,在引入private redo log bufferIMU之后,mini transaction的粒度进一步增大。另外,Oracle的功能特性较多,所以redo log的类型远多于MySQL

在前滚效率上,MySQLOracle都实现了并行前滚。MySQL在前滚完成后再进行回滚,只有前滚动作和回滚动作都完成之后才可以对外提供服务。Oracle也需要先进行前滚再进行回滚,不过为了尽快提供服务,前滚完成可以提供访问服务,回滚动作由SMON进程在后台逐步实施。

redo log file的组织上,MySQL比较简单,仅仅是循环写redo log file。虽然设计之初考虑过文件镜像和归档功能,但都没有实现,而将这些可用性特性留给了外部系统,如存储设备。Oracle除了实现镜像和归档功能之外,将相关信息归类到控制文件、数据文件和日志文件中:

  • checkpoint 信息记录在控制文件中, MySQL 则记录在第一个日志文件中,对于第一个日志文件来说,对顺序写的日志文件有一定影响;
  • 数据文件头记录开始 scn ,能够对单个数据文件进行更加精细化的介质恢复, MySQL 只能基于日志做实例恢复( xbackup 的恢复粒度也比较粗);

Oracle基于后像做了很多拓展功能,如LogMiner、物理复制、PITRStream等等。MySQLredo设计上相关要素都有,原则上页可以实现上述功能。不过MySQL服务层已经有binlogbinlog在逻辑上更加简洁,所以复制、PITR等大部分功能都是在binlog上构建的。但binlog是逻辑日志,在效率上要低于redo日志。

PDF文档下载地址:http://blog.itpub.net/69912723/viewspace-2717312/

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
在9月20日2024云栖大会上,阿里云智能集团副总裁,数据库产品事业部负责人,ACM、CCF、IEEE会士(Fellow)李飞飞发表《从数据到智能:Data+AI驱动的云原生数据库》主题演讲。他表示,数据是生成式AI的核心资产,大模型时代的数据管理系统需具备多模处理和实时分析能力。阿里云瑶池将数据+AI全面融合,构建一站式多模数据管理平台,以数据驱动决策与创新,为用户提供像“搭积木”一样易用、好用、高可用的使用体验。
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
|
3月前
|
关系型数据库 MySQL 数据库
ORM对mysql数据库中数据进行操作报错解决
ORM对mysql数据库中数据进行操作报错解决
99 2
|
1月前
|
存储 监控 数据处理
flink 向doris 数据库写入数据时出现背压如何排查?
本文介绍了如何确定和解决Flink任务向Doris数据库写入数据时遇到的背压问题。首先通过Flink Web UI和性能指标监控识别背压,然后从Doris数据库性能、网络连接稳定性、Flink任务数据处理逻辑及资源配置等方面排查原因,并通过分析相关日志进一步定位问题。
176 61
|
1天前
|
存储 Java easyexcel
招行面试:100万级别数据的Excel,如何秒级导入到数据库?
本文由40岁老架构师尼恩撰写,分享了应对招商银行Java后端面试绝命12题的经验。文章详细介绍了如何通过系统化准备,在面试中展示强大的技术实力。针对百万级数据的Excel导入难题,尼恩推荐使用阿里巴巴开源的EasyExcel框架,并结合高性能分片读取、Disruptor队列缓冲和高并发批量写入的架构方案,实现高效的数据处理。此外,文章还提供了完整的代码示例和配置说明,帮助读者快速掌握相关技能。建议读者参考《尼恩Java面试宝典PDF》进行系统化刷题,提升面试竞争力。关注公众号【技术自由圈】可获取更多技术资源和指导。
|
4天前
|
前端开发 JavaScript 数据库
获取数据库中字段的数据作为下拉框选项
获取数据库中字段的数据作为下拉框选项
30 5
|
1月前
|
SQL 关系型数据库 数据库
国产数据实战之docker部署MyWebSQL数据库管理工具
【10月更文挑战第23天】国产数据实战之docker部署MyWebSQL数据库管理工具
147 4
国产数据实战之docker部署MyWebSQL数据库管理工具
|
1月前
|
关系型数据库 MySQL 数据库
GBase 数据库如何像MYSQL一样存放多行数据
GBase 数据库如何像MYSQL一样存放多行数据
|
1月前
|
关系型数据库 分布式数据库 数据库
云栖大会|从数据到决策:AI时代数据库如何实现高效数据管理?
在2024云栖大会「海量数据的高效存储与管理」专场,阿里云瑶池讲师团携手AMD、FunPlus、太美医疗科技、中石化、平安科技以及小赢科技、迅雷集团的资深技术专家深入分享了阿里云在OLTP方向的最新技术进展和行业最佳实践。
|
2月前
|
人工智能 Cloud Native 容灾
云数据库“再进化”,OB Cloud如何打造云时代的数据底座?
云数据库“再进化”,OB Cloud如何打造云时代的数据底座?
|
2月前
|
SQL 存储 关系型数据库
数据储存数据库管理系统(DBMS)
【10月更文挑战第11天】
162 3