第七章 事务

1.事务概述
事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。
InnoDB存储引擎中的事务完全符合ACID的特性。ACID是以下4个词的缩写:
□ 原子性(atomicity)
    A(Atomicity),原子性。原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。如果事务中的操作都是只读的,要保持原子性是很简单的。一旦发生任何错误,要么重试,要么返回错误代码。因为只读操作不会改变系统中的任何相关部分。但是,当事务中的操作需要改变系统中的状态时,例如插入记录或更新记录,那么情况可能就不像只读操作那么简单了。如果操作失败,很有可能引起状态的变化,因此必须要保护系统中并发用户访问受影响的部分数据。
□ 一致性(consistency)
    C(consistency),一致性。一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏
□ 隔离性(isolation)
    I(isolation),隔离性。隔离性还有其他的称呼,如并发控制(concurrencycontrol)、可串行化(serializability)、锁(locking)等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。
□ 持久性(durability)
    D(durability),持久性。事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。
事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要特征之一。虽然理论上定义了严格的事务要求,但是数据库厂商出于各种目的,并没有严格去满足事务的ACID标准。


2. 事务的分类
从事务理论的角度来说,可以把事务分为以下几种类型:
□ 扁平事务(Flat Transactions)
在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。
□ 带有保存点的扁平事务(Flat Transactions with Savepoints)
□ 链事务(Chained Transactions)
链事务(Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。图7-3显示了链事务的工作方式:

链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。对于锁的处理,两者也不相同
□ 嵌套事务(Nested Transactions)
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。嵌套事务的层次结构如图7-4所示。

□ 分布式事务(Distributed Transactions)
通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。


3.事务的实现
原子性、一致性、持久性通过数据库的redolog和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。
redo和undo的作用都可以视为是一种恢复操作,redo恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。因此两者记录的内容不同,redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。
3.1 redo
重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redolog file),其是持久的。

redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。

在一条更新语句进行执行的时候,InnoDB引擎会把更新记录写到redo log日志中,然后更新内存,此时算是语句执行完了,然后在空闲的时候或者是按照设定的更新策略将redo log中的内容更新到磁盘中,这里涉及到WAL即Write Ahead logging技术,他的关键点是先写日志,再写磁盘。

有了redo log日志,那么在数据库进行异常重启的时候,可以根据redo log日志进行恢复,也就达到了crash-safe。

redo log日志的大小是固定的,即记录满了以后就从头循环写。

binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的是这个语句的原始逻辑,依靠binlog是没有crash-safe能力的
  • redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑【两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。】
  • redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
  • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
  • 两种日志记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。
3.2 undo

重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undo segment)
undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。

用户执行了一个INSERT 10W条记录的事务,这个事务会导致分配一个新的段,即表空间会增大。在用户执行ROLLBACK时,会将插入的事务进行回滚,但是表空间的大小并不会因此而收缩。因此,当InnoDB存储引擎回滚时,它实际上做的是与先前相反的工作。对于每个INSERT,InnoDB存储引擎会完成一个DELETE;对于每个DELETE,InnoDB存储引擎会执行一个INSERT;对于每个UPDATE,InnoDB存储引擎会执行一个相反的UPDATE,将修改前的行放回去。
除了回滚操作,undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
InnoDB存储引擎对undo的管理同样采用段的方式。但是这个段和之前介绍的段有所不同。首先InnoDB存储引擎有rollback segment,每个回滚段种记录了1024个undo log segment,而在每个undo log segment段中进行undo页的申请


1.redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
2.undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。


一、重做日志(redo log)

作用:

确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
二、回滚日志(undo log)

作用:

保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

三、二进制日志(binlog):

作用:

用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。 

用于数据库的基于时间点的还原。


4.事务的隔离级别
SQL标准定义的四个隔离级别为:
□ READ UNCOMMITTED
□ READ COMMITTED
□ REPEATABLE READ
□ SERIALIZABLE
InnoDB存储引擎默认支持的隔离级别是REPEATABLE READ,但是与标准SQL不同的是,InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用Next-KeyLock锁的算法,因此避免幻读的产生。
InnoDB存储引擎在默认的REPEATABLEREAD的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别。
隔离级别越低,事务请求的锁越少或保持锁的时间就越短。
在SERIALIABLE的事务隔离级别,InnoDB存储引擎会对每个SELECT语句后自动加上LOCK IN SHARE MODE,即为每个读取操作加一个共享锁。因此在这个事务隔离级别下,读占用了锁,对一致性的非锁定读不再予以支持。这时,事务隔离级别SERIALIZABLE符合数据库理论上的要求