《MySQL技术内幕---InnoDB存储引擎》读书笔记第7章

2021/8/1 19:36:27

本文主要是介绍《MySQL技术内幕---InnoDB存储引擎》读书笔记第7章,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

第7章 事务

事务(Transaction)是数据库区别于文件系统的重要特性之一。
InnoDB存储引擎中的事务完全符合ACID的特性。ACID是以下4个词的缩写:
口原子性(atomicity)
口一致性(consistency)
口隔离性(isolation)
口持久性(durability)
第6章介绍了锁,讨论InnoDB是如何实现事务的隔离性的。本章主要关注事务的原子性这一概念,并说明怎样正确使用事务及编写正确的事务应用程序,避免在事务方面养成一些不好的习惯。

7.1 认识事务

7.1.1 概述
1、事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别于文件系统的重要特征之一。
2、理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的ACID特性。
3、对于InnoDB存储引擎而言,其默认的事务隔离级别为READ REPEATABLE,完全遵循和满足事务的ACID特性。
A(Atomicity),原子性。
原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
C(consistency),一致性。
一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务一一返回初始化的状态。
l(isolation),隔离性。
隔离性还有其他的称呼,如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。
D(durability),持久性。
事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。
持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。

7.1.2 分类
从事务理论的角度来说,可以把事务分为以下几种类型:
口扁平事务(Flat Transactions)
口带有保存点的扁平事务(Flat Transactions with Savepoints)
口链事务(Chained Transactions)
口嵌套事务(Nested Transactions)
口分布式事务(Distributed Transactions)
扁平事务Flat Transaction)是事务类型中最简单的一种,但在实际生产环境中,这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。图7-1显示了扁平事务的三种不同结果。
扁平事务的三种情况
扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。
带有保存点的扁平事务Flat Transactions with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎
要求,开销也太大。保存点Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。
在事务中使用保存点
链事务Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。图7-3显示了链事务的工作方式:
链事务
链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。对于锁的处理,两者也不相同。链事务在执行COMMIT后即释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务
(subtransaction),其控制每一个局部的变换。嵌套事务的层次结构如图7-4所示。
嵌套事务
下面给出Moss对嵌套事务的定义:
1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。
2)处在叶节点的事务是扁平事务。但是每个子事务从根到叶节点的距离可以是不同的。
3)位于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称
(predecessor)为父事务(parent),事务的下一层称为儿子事务(child)。
4)子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。因此可以推论出,任何子事物都在顶层事务提交后才真正的提交。
5)树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、I特性,不具有D的特性。
在Moss的理论中,实际的工作是交由叶子节点来完成的,即只有叶子节点的事务才能访问数据库、发送消息、获取其他类型的资源。而高层的事务仅负责逻辑控制,决定何时调用相关的子事务。
分布式事务Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
对于分布式事务,其同样需要满足ACID特性,要么都发生,要么都失效。
对于InnoDB存储引擎来说,其支持扁平事务、带有保存点的事务、链事务、分布式事务。对于嵌套事务,其并不原生支持,因此,对有并行事务需求的用户来说,MySQL数据库或InnoDB存储引擎就显得无能为力了。然而用户仍可以通过带有保存点的事务来模拟串行的嵌套事务。

7.2 事务的实现
事务隔离性由第6章讲述的锁来实现。原子性、一致性、持久性通过数据库的redo log和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。
有的DBA或许会认为undo是redo的逆过程,其实不然。redo和undo的作用都可以视为是一种恢复操作,redo恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。因此两者记录的内容不同,redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。

7.2.1 redo
1.基本概念
①重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的。
②InnoDB是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性,即当事务提交(COMMIT)时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成。这里的日志是指重做日志,在InnoDB存储引擎中,由两部分组成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助事务回滚及MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。而 undo log是需要进行随机读写的。
③为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。由于重做日志文件打开并没有使用
O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。
fsync函数同步内存中所有已修改的文件数据到储存设备。
④参数innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。该参数的默认值为1,表示事务提交时必须调用一次fsync操作。还可以设置该参数的值为0和2。0表示事务提交时不进行写入重做日志操作,这个操作仅在master thread中完成,而在master thread中每1秒会进行一次重做日志文件的fsync操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。
⑤在默认的设置下,即参数innodb_flush_log_at_trx_commit为1的情况下,InnoDB存储引擎会将重做日志缓冲中的日志写入文件,并调用一次 fsync操作。
binlog
①在MySQL数据库中还有一种二进制日志(binlog),其用来进行POINT-IN-TIME
(PIT)的恢复及主从复制(Replication)环境的建立。从表面上看其和重做日志非常相似,都是记录了对于数据库操作的日志。然而,从本质上来看,两者有着非常大的不同。
②首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库的上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
③其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
④此外,两种日志记录写入磁盘的时间点不同,如图7-6所示。二进制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。
二进制日志与重做日志
从图7-6中可以看到,二进制日志仅在事务提交时记录,并且对于每一个事务,仅包含对应事务的一个日志。而对于InnoDB存储引擎的重做日志,由于其记录的是物理操作日志,因此每个事务对应多个日志条目,并且事务的重做日志写入是并发的,并非在事务提交时写入,故其在文件中记录的顺序并非是事务开始的顺序。*T1、*T2、*T3表示的是事务提交时的日志。
2.log block
①在InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块的大小为512字节。
②若一个页中产生的重做日志数量大于512字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。
③重做日志块除了日志本身之外,还由日志块头(log block header)及日志块尾(log
block tailer)两部分组成。重做日志头一共占用12字节,重做日志尾占用8字节。故每个重做日志块实际可以存储的大小为492字节(512-12-8)。图7-7显示了重做日志块缓存的结构。
重做日志块缓存的结构
图7-7显示了重做日志缓存的结构,可以发现重做日志缓存由每个为512字节大小的日志块所组成。日志块由三部分组成,依次为日志块头(log block header)、
日志内容(log body)、日志块尾(log block tailer)。
log block header 由4部分组成,如表7-2所示。
log block header
log buffer是由log block组成,在内部log buffer就好似一个数组,因此LOG_BLOCK_HDR_NO用来标记这个数组中的位置。其是递增并且循环使用的,占用4个字节,但是由于第一位用来判断是否是flush bit,所以最大的值为2G。
LOG_BLOCK_HDR_DATA_LEN 占用2字节,表示log block所占用的大小。当log block被写满时,该值为0x200,表示使用全部log block空间,即占用512字节。
LOG _BLOCK_FIRST_REC_GROUP占用2个字节,表示log block中第一个日志所在的偏移量。如果该值的大小和LOG_BLOCK_HDR_DATA_LEN相同,则表示当前log block不包含新的日志。如事务T1的重做日志1占用762字节,事务T2的重做日志占用100字节。由于每个log block实际只能保存492个字节,因此其在log buffer中的情况应如图7-8所示。
LOG_BLOCK_FIRST_REC_GROUP
从图7-8中可以观察到,由于事务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)。
LOG_BLOCK_CHECKPOINT_NO占用4字节,表示该log block最后被写入时的检查点第4字节的值。
log block tailer只由1个部分组成(如表7-3所示),且其值和LOG_BLOCK_HDR_NO相同,并在函数log_block_init 中被初始化。
log block tailer3. log group
①log group为重做日志组,其中有多个重做日志文件。InnoDB存储引擎实际只有一个log group。
②log group 是一个逻辑上的概念,并没有一个实际存储的物理文件来表示log group信息。log group由多个重做日志文件组成,每个log group中的日志文件大小是相同的。
③重做日志文件中存储的就是之前在log buffer中保存的log block,因此其也是根据块的方式进行物理存储的管理,每个块的大小与log block一样,同样为512字节。在InnoDB存储引擎运行过程中,log buffer 根据一定的规则将内存中的log block刷新到磁盘。这个规则具体是:
口事务提交时
口当log buffer 中有一半的内存空间已经被使用时
口 log checkpoint时
对于log block的写入追加(append)在redo log file的最后部分,当一个redo log fle被写满时,会接着写入下一个redo log fle,其使用方式为round-robin。
2KB
除了log block的写入操作,还需要更新前2KB部分的信息,这些信息对于InnoDB存储引擎的恢复操作来说非常关键和重要。
4.重做日志格式



这篇关于《MySQL技术内幕---InnoDB存储引擎》读书笔记第7章的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程