MySQL:事务(事务的实现之redo)

asds事务(Transaction) 是数据库区别于文件系统的重要特性之一。在文件系统中,如果正在写文件,但是操作系统突然崩溃了,这个文件就很有可能被破坏。当然,有一些机制可以把文件恢复到某个时间点。不过,如果需要保证两个文件同步,这些文件系统可能就显得无能为力了。这正是数据库系统引入事务的主要目的:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。

asdsInnoDB 存储引擎中的事务完全符合ACID 的特性。ACID 是以下4 个词的缩写:

ddss①、原子性(atomicity) asddss②、一致性(consistency) asddss③、隔离性(isolation) asddss④、持久性 (durability)

asdsadasdasdasdsadasdasdasdsadassdasdsadasdasdsadasdsadassadasdas————《MySQL技术内幕INNODB存储引擎》


事务的实现

ssdss 事务隔离性 (I) 由锁来实现。原子性(A)、一致性(C.)、持久性(D) 通过数据库的redo log 和undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。

ss 注:有的DBA 或许会认为undo 是redo 的逆过程,其实不然。redo 和undo 的作用都可以视为是一种恢复操作, redo 恢复提交事务修改的页操作,而undo 回滚行记录到某个特定版本。因此两者记录的内容不同, redo 通常是物理日志,记录的是页的物理修改操作undo 是逻辑日志,根据每行记录进行记录。

redo
基本概念

ssdss 重做日志用来实现事务的 持久性,即事务ACID 中的 D。其由两部分组成:

sdsdssdss①、内存中的重做日志缓冲(redo log buffer), 其是 易失 的;

sdsdssdss②、(磁盘)重做日志文件 (redo log file),其是 持久 的 。

ssdss InnoDB 是事务的存储引擎,其通过 Force Log at Commit 机制实现事务的持久性,即当事务提交(COMMIT) 时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT 操作完成才算完成。这里的日志是指重做日志,在InnoDB 存储引擎中,由两部分组成,即redo log 和undo log 。redo log 用来保证事务的持久性, undo log 用来帮助事务回滚及MVCC 的功能。redo log 基本上都是顺序写的,在数据库运行时不需要对redo log 的文件进行读取操作。而undo log 是需要进行 随机读写 的。

dsdss 注 1 :为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后, InnoDB 存储引擎都需要调用一次 fsync 操作。由于重做日志文件打开并没有使用O_DIRECT 选项,因此重做日志缓冲先写入文件系统缓存 为了确保重做日志写入磁盘,必须进行一次fsync 操作。由于fsync 的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。

dsdss 注 2 :InnoDB 存储引擎允许用户手工设置非持久性的情况发生,以此提高数据库的性能。即当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync 操作。 由于并非强制在事务提交时进行一次fsync 操作,显然这可以显著提高数据库的性能。但是当数据库发生宥机时,由于部分日志未刷新到磁盘,因此会丢失最后一段时间的事务。

dsdss 注 3 :参数innodb_ flush_log_at_trx_commit 用来控制重做日志刷新到磁盘的策略。该参数的默认值为 1 , 表示事务提交时必须调用一次fsync 操作。还可以设置该参数的值为0 和 2 。0 表示事务提交时不进行写入重做日志操作,这个操作仅在master thread 中完成,而在master thread 中每1 秒会进行一次重做日志文件的fsync 操作2表示事务提交时将重做日志写入重做日志文件, 但仅写入文件系统的缓存中,不进行fsync 操作 在这个设置下,当MySQL 数据库发生宕机而操作系统不发生宥机时,并不会导致事务的丢失。而当操作系统宥机时, 重启数据库后会丢失未从文件系统缓存刷新到重做日志文件那部分事务。(虽然用户可以通过设置参数innodb_flush_log_at_trx_commit 为0 或2 来提高事务提交的性能,但是需要牢记的是,这种设置方法丧失了事务的ACID 特性。而针对上述存储过程,为了提高事务的提交性能,应该在将多条记录插入表后进行一次的COMMIT 操作,而不是在每插入一条记录后进行一次COMMIT 操作。这样做的好处是还可以使事务方法在回滚时回滚到事务最开始的确定状态。)

ssdss 在 MySQL 数据库中还有 二进制日志(binlog) , 其用来进行 POINT-IN-TIME(PIT) 的恢复及 主从复制 (Replication) 环境的建立。从表面上看其和重做日志非常相似,都是记录了对于数据库操作的日志。然而也有很大的区别:

sdsdssdss①、重做日志是在InnoDB 存储引擎层产生,而二进制日志是在MySQL 数据库的上层产生的,并且二进制日志不仅仅针对于lnnoDB 存储引擎, MySQL 数据库中的任何存储引擎对于数据库的更改都会产生二进制日志

sdsdssdss②、两种日志记录的内容形式不同。MySQL 数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL 语句。而InnoDB 存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改

sdsdssdss③、两种日志记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入,即对于每一个事务,仅包含对应事务的一个日志。而InnoDB 存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。因此每个事务对应多个日志条目,并且事务的重做日志写入是并发的。

log block

ssdss 在InnoDB 存储引擎中,重做日志都是以512 字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block) 的方式进行保存的,称之为重做日志块 (redolog block), 每块的大小为 512 字节。

ssdss若一个页中产生的重做日志数最大于512 字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512 字节,因此重做日志的写入可以保证原子性,不需要doublewrite 技术

ssdss重做日志块除了日志本身之外,还由 日志块头(log block header)日志块尾(logblock tailer) 两部分组成。重做日志头一共占用12 字节,重做日志尾占用8 字节。故每个重做日志块实际可以存储的大小为 492 字节(512-12-8) ,如图:
在这里插入图片描述
dsdss 注 :

dsedss ①、log buffer 是由 log block 组成,在内部 log buffer 就好似一个 数组,因此 LOG_BLOCK_HDR_NO 用来标记这个数组中的位置。其是 递增并且循环使用 的,占用 4个字节,但是由于第一位用来判断是否是flush bit, 所以最大的值为 2G (4字节,32位,减去1位,也就是31位)。

dsedss ②、LOG_ BLOCK_ HOR_ DATA _LEN 占用2 字节,表示log block 所占用的大小。当logblock 被写满时,该值为Ox200, 表示使用全部log block 空间,即占用512 字节。

dsedss ③、LOG_BLOCK_FIRST_REC_GROUP占用2 个字节,表示 log block 中第一个日志所在的偏移量如果该值的大小和LOG_BLOCK_OR_DATA_LEN 相同,则表示当前log block 不包含新的日志。如事务T1 的重做日志1 占用762 字节,事务T2 的重做日志占用100 字节。由于每个log block 实际只能保存492 个字节,因此其在log buffer 中的情况应如下图:
在这里插入图片描述
ddssedss 由于事务T1 的重做日志占用792 字节,因此需要占用两个log block 。左侧的log block 中LOG_BLOCK_FIRST_ REC_GROUP 为12, 即log block中第一个日志的开始位置。在第二个log block 中,由于包含了之前事务 T1 的重做日志,事务 T2 的日志才是 log block 中第一个日志,因此该log block 的LOG_BLOCK_FIRST_REC_GROUP 为282 (270+12) 。

dsedss ④、LOG_BLOCK_CHECKPOINT_NO 占用4 字节,表示该 log block 最后被写入时的检查点第4 字节的值。

dsedss ⑤、log block tailer 只由1 个部分组成,其值和LOG_BLOCK_HDR_NO 相同,并在函数log_block_init 中被初始化。

log group

ssdss log group 为重做日志组,其中有多个重做日志文件。虽然源码中已支持log group的镜像功能,但是在ha_innobase.cc 文件中禁止了该功能。因此 InnoDB 存储引擎实际只有一个log group 。

ddssedss 注:log group 是一个 逻辑上的概念,并没有一个实际存储的物理文件来表示log group信息。log group 由多个重做日志文件组成,每个log group 中的日志文件大小是相同的,且在InnoDB 1.2 版本之前,重做日志文件的总大小要小于4GB (不能等于4GB) 。从InnoDB 1.2 版本开始重做日志文件总大小的限制提高为了512GB 。InnoSQL 版本的InnoDB 存储引擎在1.1 版本就支持大于4GB 的重做日志。

ssdss重做日志文件中存储的就是之前在log buffer 中保存的log block, 因此其也是根据块的方式进行物理存储的管理,每个块的大小与log block 一样,同样为512 字节。在InnoDB 存储引擎运行过程中, log buffer 根据一定的规则将内存中的log block 刷新到磁盘。这个规则具体是:

sdsdssdss①、事务提交时

sdsdssdss②、当log buffer 中有一半的内存空间巳经被使用时

sdsdssdss③、log checkpoint 时

ssdss 对于log block 的写入追加(append) 在redo log file 的最后部分, 当一个redo log file 被写满时,会接着写入下一个redo log file, 其使用方式为 round-robin

ssdss虽然log block 总是在redo log file 的最后部分进行写入,有的读者可能以为对redo log file 的写入都是顺序的。其实不然,因为redo log file 除了保存log buffer 刷新到磁盘的log block, 还保存了一些其他的信息,这些信息一共占用2KB 大小,即每个redo log file 的前2KB 的部分不保存log block 的信息。对于log group 中的第一个redo log file, 其前2KB 的部分保存4 个512 字节大小的块,如表:在这里插入图片描述
ssdss上述信息仅在每个log group 的第一个redo log file 中进行存储log group 中的其余redo log file 仅保留这些空间,但不保存上述信息。正因为保存了这些信息,就意味着对redo log file 的写入并不是完全顺序的。因为其除了log block 的写入操作,还需要更新前2KB 部分的信息,这些信息对于 InnoDB 存储引擎的恢复操作来说非常关键和重要。log group 与redo log file 之间的关系如下图:
dasdas在这里插入图片描述

ssdss在 log filer header 后面的部分为 lnnoDB 存储引擎保存的 checkpoint (检查点) 值,其设计是交替写入,这样的设计避免了因介质失败而导致无法找到可用的checkpoint 的情况。

重做日志格式

ssdss 不同的数据库操作会有 对应的重做日志格式。此外,由于InnoDB 存储引擎的存储管理是 基于页 的,故其重做日志格式也是基于页的。虽然有着不同的重做日志格式,但是它们有着 通用的头部格式
在这里插入图片描述
ssdsdsd通用的头部格式由3 部分组成:①、redo_log_type: 重做日志的类型。②、space: 表空间的ID 。③、page_no: 页的偏移量。
ssdsdsd 之后redo log body 的部分,根据重做日志类型的不同, 会有不同的存储内容

在这里插入图片描述
dedss 注:到InnoDBl.2 版本时,一共有51 种重做日志类型。随着功能不断地增加,相信会加入越来越多的重做日志类型。

LSN

ssdss LSN 是Log Sequence Number 的缩写, 其代表的是日志序列号。在InnoDB 存储引擎中, LSN 占用8 字节,并且单调递增。LSN 表示的含义有:

ssdss①、重做日志写入的总量

ssdss②、checkpoint 的位置

ssdss③、页的版本

ssdssLSN 表示事务写入重做日志的字节的总量。例如当前重做日志的LSN 为1000, 有一个事务 T1 写入了100 字节的重做日志,那么LSN 就变为了1100, 若又有事务 T2 写入了200 字节的重做日志,那么LSN 就变为了 1300 。可见LSN 记录的是重做日志的总量,其单位为字节

dedss 注 1 :LSN 不仅记录在重做日志中,还存在于每个页中。在每个页的头部,有一个值FIL_PAGE_LSN, 记录了该页的 LSN 。在页中,LSN 表示该页最后刷新时 LSN 的大小因为重做日志记录的是每个页的日志,因此页中的LSN 用来判断页是否需要进行恢复操作。例如,页P1 的LSN 为10 000, 而数据库启动时, InnoDB 检测到写入重做日志中的LSN 为13 000, 并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1 页中。同样的,对于重做日志中 LSN 小于 P1 页的 LSN, 不需要进行重做,因为 P1 页中的LSN 表示页已经被刷新到该位置。

dedss 注 2 :用户可以通过命令SHOW ENGINE INNODB STATUS 查看LSN 的情况:

rnysql> SHOW ENGINE INNODB STATUS\G ;
.....
-----
LOG
-----
Log sequence number 11 3047174608
Log flushed up to 11 3047174608
Last checkpoint at 11 3047174608
0 pending log writes, 0 pending chkp writes
142 log i/o's done, 0.00 log i/o 's/second
l row in set (0.00 sec)

deddsssLog sequence number 表示当前的LSN, Log flushed up to 表示刷新到重做日志文件的LSN, Last checkpoint at 表示刷新到磁盘的LSN

恢复

ssdss InnoDB 存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如二进制日志,要快很多。与此同时, InnoDB 存储引擎自身也对恢复进行了一定程度的 优化,如 顺序读取并行应用 重做日志,这样可以进一步地提高数据库恢复的速度。

ssdss由于checkpoint 表示已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint 开始的日志部分。如下图,当数据库在checkpoint 的LSN 为10 000 时发生宥机,恢复操作仅恢复LSN 10 000~13 000 范围内的日志。
在这里插入图片描述
deddsssInnoDB 存储引擎的重做日志是物理日志,因此其恢复速度较之二进制日志恢复快得多。
deddssseg:

CREATE TABLE t (a INT, b INT, PRIMARY KEY(a), KEY(b));
#执行INSERT操作:
INSERT INTO t SELECT 1,2;
#由于需要对聚集索引页和辅助索引页进行操作,其记录的重做日志大致为:
page(2,3), offset 32 , value 1,2 #聚集索引
page(2,4), offset 64 , value 2 #辅助索引

deddsss可以看到记录的是页的物理修改操作,此外,由于重做日志是物理日志,因此其是幕等的,即f(f(x))=f(x).

💖感谢各位的暴击三连~💖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值