读书笔记之InnoDB事务

事务介绍


事务( Transaction) 是数据库区别于文件系统的重要特性 之一,也是MySQL里面InnoDB引擎的一个重要核心功能。InnoDB 存储引擎中的事务完全符合 ACID 的 特性。 ACID 是以下4个词的缩写:

❑ 原子性(atomicity),事务具有的原子性,是指事务操作要么全部执行,要么全部不执行,不会出现部分执行部分没执行的情况,比如一个事务要修改10条记录,要么就全部10条都修改成功,要么全部10条都不修改,而不会出现修改了5条剩下的5条没修改。总的来讲就是事务所包含的一系列操作不会被分割成若干子操作,他必定是连续的,像原子那样不能再细了不能再分割的意思。


❑ 一致性(consistency),一致性是指数据库从一个状态转变为下一个状态,事务在开始和结束以后,数据库的完整性约束没有被破坏。个人的理解是,从数据库读取出来的数据都是完整的满足约束的状态,而不会出现一个中间状态,比如某数据库表格test字段A是唯一约束,即表示test表格字段A不允许有重复相同的记录。

那么事务对表格test进行一些操作比如包含了修改字段A记录,中间过程可能会出现有相同两条记录,但是事务的最后只会保证一条,事务要么执行成功要么全部回滚到原始状态,这就是一致性。这个一致性跟上面的原子性有点让人混淆,个人理解原子性强调的是事务不可分割不可中断,一致性强调的是数据内容的一致性,偏向角度有所不同。


❑ 隔离性(isolation),隔离性是指多个不同事务在执行过程中是隔离的,互补干扰,比如多个事务对数据库表格test进行读取修改,不同事务之间是隔离的,不会看到其他事务的操作中间状态或者结果。这里还会引申出不同的隔离级别,下面会详细描述。


❑ 持久性(durability),持久性这个最好理解的,就是数据一旦成功修改后就被永久保存起来,不会出现丢失等情况(这个比较理想状态的要求了)。


事务分类


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

❑ 扁平事务(Flat Transactions)
扁平事务(Flat Transaction)是事务类型中最简单的一种,但在实际生产环境中, 这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。

❑ 带有保存点的扁平事务(Flat Transactions with Savepoints)
带有保存点的扁平事务(Flat Transactions with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。

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


对于扁平的事务来说,其隐式地设置了一个保存点。然而在整个事务中,只有这一个保存点,因此,回滚只能回滚到事务开始时的状态。保存点用SAVE WORK函数来建立,通知系统记录当前的处理状态。当出现问题时,保存点能用作内部的重启动点,根据应用逻辑,决定是回到最近一个保存点还是其他更早的保存点。

❑ 链事务(Chained Transactions)
链事务(Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平 事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。

链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。

❑ 嵌套事务(Nested Transactions)
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top- level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。

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

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


事务的实现


事务隔离性由第6章讲述的锁来实现。原子性、一致性、持久性 通过数据库的redo log 和 undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。

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

redo

重做日志用来实现事务的持久性,即事务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 的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。

undo

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

用户通常对undo有这样的误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子——但事实并非如此。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。

这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。

purge

delete和update操作可能并不直接删除原有的数据。例如,对上一小节所产生的表t执行如下的SQL语句:
DELETE FROM t WHEREa=1;
表t上列a有聚集索引,列b上有辅助索引。对于上述的delete操作,通过前面关于undolog的介绍已经知道仅是将主键列等于1的记录deleteflag设置为1,记录并没有被删除,即记录还是存在于B+树中。其次,对辅助索引上a等于1,b等于1的记录同样没有做任何处理,甚至没有产生undolog。而真正删除这行记录的操作其实被“延时”了,最终在purge操作中完成。

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

group commit

若事务为非只读事务,则每次事务提交时需要进行一次fsync操作,以此保证重做日志都已经写入磁盘。当数据库发生宕机时,可以通过重做日志进行恢复。虽然固态硬盘的出现提高了磁盘的性能,然而磁盘的fsync性能是有限的。为了提高磁盘fsync的效率,当前数据库都提供了groupcommit的功能,即一次fsync可以刷新确保多个事务日志被写入文件。


事务的隔离级别


SQL 标准定义的四个隔离级别为:
❑ READ UNCOMMITTED
从名字可以猜想得到,可以读到其他事务未commit的数据,脏读。

❑ READ COMMITTED
参考上面的,这个的意思就是只能读到已经提交的数据,这种级别不会出现脏读。单这种级别还不能避免一种情况叫“不可重复读”,因为这种级别是可以读到其他事务已经提交的数据,所以会出现时间T1(这个时候其他事务未提交)读取到的数据,时间T2(这个时候有其他事务提交)再去读取同样的数据,会出现两份不一样的结果,就是所谓的“不可重复读”。

❑ REPEATABLE READ
这个是mysql默认的隔离级别,这个级别解决了上面两种级别所存在的问题:脏读和不可重复读。但是这种级别又不能解决一种现象称之为“幻读”。大概就是说在同一个事务里面前后两次查询一样的条件出来的结果不一样,这个跟不可重复读有点类似,经过网络的大量搜索,幻读与不可重复读的区别是:不可重复读一般是指由其他事务的update、delete造成,而幻读一般由其他事务的insert造成。

❑ SERIALIZABLE
串行级别,也比较好理解,这种级别下则是通过锁解决了上面所描述的三种问题。

不同的隔离级别有各自对数据读写的影响,主要有以下三种:
1.脏读(dirty read):一个事务可以读取另一个尚未提交事务的修改数据。

2.非重复读(nonrepeatable read):在同一个事务中,同一个查询在T1时间读取某一行,在T2时间重新读取这一行时候,这一行的数据已经发生修改,可能被更新了(update),也可能被删除了(delete)。

3.幻像读(phantom read):在同一事务中,同一查询多次进行时候,由于其他插入操作(insert)的事务提交,导致每次返回不同的结果集。

下面是隔离级别和对应上面几种影响个关系:


猜你喜欢

转载自blog.51cto.com/13475644/2326826