InnoDB存储引擎下的事务

概述

数据库系统引入事务的主要目的:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。

认识事务

事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要特征之一。

对于InnoDB存储引擎而言,其默认的事务隔离级别为READREPEATABLE,
InnoDB存储引擎中的事务完全符合ACID的特性。ACID是以下4个词的缩写:

  • 原子性( atomicity)

原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。

  • 一致性(consistency)

一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  • 隔离性(isolation)

隔离性还有其他的称呼,如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。

  • 持久性(durability)

事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。

事务分类

从事务理论的角度来说,可以把事务分为以下几种类型:

  • 扁平事务(Flat Transactions)

扁平事务(Flat Transaction)是事务类型中最简单的一种,但在实际生产环境中,这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGINWORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块.
扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。
如果支持有计划的回滚操作,那么就不需要终止整个事务。因此就出现了带有保存点的扁平事务。

  • 带有保存点的扁平事务(Flat Transactions with Savepoints)

除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

  • 链事务(Chained Transactions)

链事务(Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的〈persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是﹔在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。
在这里插入图片描述
链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。对于锁的处理,两者也不相同。链事务在执行COMMIT后即释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。

  • 嵌套事务(Nested Transactions)

嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务〈top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务( subtransaction),其控制每一个局部的变换。
在这里插入图片描述
下面给出Moss对嵌套事务的定义:
1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。
2)处在叶节点的事务是扁平事务。但是每个子事务从根到叶节点的距离可以是不同的。
3)位于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称( predecessor)为父事务( parent),事务的下一层称为儿子事务(child)。
4)子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。因此可以推论出,任何子事物都在顶层事务提交后才真正的提交。
5)树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、Ⅰ特性,不具有D的特性。
在 Moss 的理论中,实际的工作是交由叶子节点来完成的,即只有叶子节点的事务才能访问数据库、发送消息、获取其他类型的资源。而高层的事务仅负责逻辑控制,决定何时调用相关的子事务。即使一个系统不支持嵌套事务,用户也可以通过保存点技术来模拟嵌套事务。

  • 分布式事务((Distributed Transactions)

分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
假设一个用户在ATM机进行银行的转账操作,例如持卡人从招商银行的储蓄卡转账10 000元到工商银行的储蓄卡。在这种情况下,可以将ATM机视为节点A,招商银行的后台数据库视为节点B,工商银行的后台数据库视为C,这个转账的操作可分解为以下的步骤:
1)节点A发出转账命令。
2)节点B执行储蓄卡中的余额值减去10 000。
3)节点C执行储蓄卡中的余额值加上 10 000。
4)节点A通知用户操作完成或者节点A通知用户操作失败。
这里需要使用分布式事务,因为节点A不能通过调用一台数据库就完成任务。其需要访问网络中两个节点的数据库,而在每个节点的数据库执行的事务操作又都是扁平的。对于分布式事务,其同样需要满足ACID特性,要么都发生,要么都失效。对于上述的例子,如果2)、3〉步中任何一个操作失败,都会导致整个分布式事务回滚。若非这样,结果会非常可怕。

对于InnoDB存储引擎来说,其支持扁平事务、带有保存点的事务、链事务、分布式事务。对于嵌套事务,其并不原生支持,因此,对有并行事务需求的用户来说MySQL数据库或InnoDB存储引擎就显得无能为力了。然而用户仍可以通过带有保存点的事务来模拟串行的嵌套事务。

事务的实现

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

redo

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

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

MySQL中的二进制日志和重做日志的区别

  • 首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库的上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
  • 其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
  • 此外,两种日志记录写入磁盘的时间点不同,如图7-6所示。二进制日志只在事务提交完成后进行一次写人。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写人的。
    =========================================================================
    undo

重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。

redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段( segment)中,这个段称为undo段( undo segment)。undo段位于共享表空间内。

用户通常对undo有这样的误解: undo用于将数据库物理地恢复到执行语句或事务之前的样子─—但事实并非如此。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 也需要持久性的保护。

purge线程

purge用于最终完成delete和 update操作。这样设计是因为InnoDB存储引擎支持MVCC,所以记录不能在事务提交时立即进行处理。这时其他事物可能正在引用这行,故InnoDB存储引擎需要保存记录之前的版本。而是否可以删除该条记录通过purge来进行判断。若该行记录已不被任何其他事务引用,那么就可以进行真正的delete操作。

group commit

若事务为非只读事务,则每次事务提交时需要进行一次 fsync(系统提供的系统调用,用于将内核缓冲刷到磁盘上)操作,以此保证重做日志都已经写入磁盘。当数据库发生宕机时,可以通过重做日志进行恢复。虽然固态硬盘的出现提高了磁盘的性能,然而磁盘的 fsync性能是有限的。为了提高磁盘fsync 的效率,当前数据库都提供了group commit的功能,即一次fsync可以刷新确保多个事务日志被写人文件。对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:
1)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
2)调用fsync将确保日志都从重做日志缓冲写入磁盘。

关于事务控制语句

  • InnoDB存储引擎中的事务都是原子的,这说明下述两种情况:构成事务的每条语句都会提交(成为永久),或者所有语句都回滚。这种保护还延伸到单个的语句。一条语句要么完全成功,要么完全回滚(注意,这里说的是语句回滚)。因此一条语句失败并抛出异常时,并不会导致先前已经执行的语句自动回滚。所有的执行都会得到保留,必须由用户自己来决定是否对其进行提交或回滚的操作。
  • 另一个容易犯的错误是ROLLBACK TO SAVEPOINT,虽然有ROLLBACK,但其并不是真正地结束一个事务,因此即使执行了ROLLBACK TO SAVEPOINT,之后也需要显式地运行COMMIT 或ROLLBACK命令。

事务隔离级别

SQL标准定义的四个隔离级别为:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

SERIALIABLE的事务隔离级别主要用于InnoDB存储引擎的分布式事务。

分布式事务

InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源( transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的ACID要求又有了提高。另外,在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。

XA事务允许不同数据库之间的分布式事务,如一台服务器是MySQL数据库的,另一台是Oracle数据库的,又可能还有一台服务器是SQL Server数据库的,只要参与在全局事务中的每个节点都支持XA事务。分布式事务可能在银行系统的转账中比较常见。

XA事务由一个或多个资源管理器(Resource Managers)、一个事务管理器( Transaction Manager)以及一个应用程序(Application Program)组成。

  • 资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器。
  • 事务管理器:协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器进行通信。
  • 应用程序:定义事务的边界,指定全局事务中的操作。

在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL服务器的客户端。
在这里插入图片描述

分布式事务使用两段式提交(two-phase commit)的方式。在第一阶段,所有参与全局事务的节点都开始准备(PREPARE),告诉事务管理器它们准备好提交了。在第二阶段,事务管理器告诉资源管理器执行ROLLBACK还是COMMIT。如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。可见与本地事务不同的是,分布式事务需要多一次的PREPARE操作,待收到所有节点的同意信息后,再进行COMMIT 或是ROLLBACK操作。

  • 内部XA事务
    之前讨论的分布式事务是外部事务,即资源管理器是MySQL数据库本身。在MySQL数据库中还存在另外一种分布式事务,其在存储引擎与插件之间,又或者在存储引擎与存储引擎之间,称之为内部XA事务。
    最为常见的内部XA事务存在于binlog 与InnoDB存储引擎之间。由于复制的需要,因此目前绝大多数的数据库都开启了binlog功能。在事务提交时,先写二进制日志,再写InnoDB存储引擎的重做日志。对上述两个操作的要求也是原子的,即二进制日志和重做日志必须同时写入。

长事务

长事务(Long-Lived Transactions),顾名思义,就是执行时间较长的事务。
对于长事务的问题,有时可以通过转化为小批量(mini batch)的事务来进行处理。当
事务发生错误时,只需要回滚一部分数据,然后接着上次已完成的事务继续进行,这样就可以大大减少不必要的代价。

猜你喜欢

转载自blog.csdn.net/AIJXB/article/details/113529519