MySQL高级进阶(八)、事务

小景哥哥博客

MySQL高级进阶(八)、事务

InnoDB存储引擎中的事务完全符合ACID特性:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

原子性(Atomicity)指整个数据库事务是不可分割的工作单位,事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

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

隔离性(Isolation)要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。

持久性(Durability)事务一旦提交,其结果是永久性的,即便系统故障也不会丢失。

事务的分类:

  • 扁平事务
  • 带有保存点的扁平事务
  • 链事务
  • 嵌套事务
  • 分布式事务

在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORKROLLBACK WORK结束,其间的操作都是原子操作,要么多执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。

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

链事务可视为保存点模式的一种变种。带有保存点的扁平事务,当系统发生宕机时,所有的保存点都消失,因为保存点是易失的而非持久的。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。链事务的设计思想是,在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。提交事务和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。

链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个保存点。链事务在执行COMMIT后即释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。

嵌套事务是一个层次结构框架。有一个顶层事务控制着各个层次的事务。顶层事务之下嵌套的事务称为子事务,其控制着每一个局部的变换。

  • 嵌套事务是由若干个事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。
  • 处在叶子节点的事务是扁平事务。但是每个子事务从根节点到叶节点的距离可以是不同的。
  • 处于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称为父事务,事务的下一层称为儿子事务。
  • 子事务既可以提交也可以回滚。但它的提交操作并不马上生效,除非其父事务已经提交。任何子事务都在顶层事务提交后才真正生效。
  • 树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留ACI特性,不具备D特性。

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

对于InnoDB存储引擎来说,其支持扁平事务、带有保存点的扁平事务、链事务、分布式事务。对于嵌套事务,其并不原生支持。

原子性、一致性、持久性通过数据库的redo logundo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。

redo log恢复提交事务修改的页操作,而undo log回滚行记录到某个特定版本。redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。

redo

重做日志用来实现事务的持久性,即事务的ACID中的D。其由两部分组成:一是内存中的重做日志缓存(redo log buffer),其是易失性的;二是重做日志文件(redo log file),其实持久的。

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

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

MySQL数据库中还有一种二级制日志(binlog),其用来进行POINT-IN-TIME(PIT)的恢复及主从复制环境的建立。从表面上看其和重做日志非常相似,都是记录了对于数据库操作的日志。然而,从本质上来看,两者有着非常大的不同。首先,重组日志是在InnoDB存储引擎层产生的,并且二进制日志不仅仅针对InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对每个页的修改。此外,两种日志记录写入磁盘的时间点不同。二级制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。

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

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

重做日志块除了日志本身之外,还由日志块头(log block header)及日志块尾(log block tailer)两部分组成。重做日志头一共占用12字节,重做日志尾占用8字节。故每个重做日志块实际可以存储的大小为492字节。

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

log group是一个逻辑上的概念,并没有一个实际存储的物理文件来表示log group信息。log group由多个重做日志文件组成,每个log group中的日志文件大小是相同的,且在InnoDB1.2版本之前,重做日志文件的总大小要小于4GB。从InnoDB1.2开始重做日志总大小的限制提高为512GB

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

由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。

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

  • 重做日志写入的总量
  • checkpoint的位置
  • 页的版本

假如页P1LSN10000,而数据库启动时,InnoDB检测到写入重做日志中的LSN13000,并且事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1页中。

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

由于checkpoint表示已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint开始的日志部分。

undo

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

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

当用户执行ROLLBACK时,会将插入的事务进行回滚,但是表空间的大小并不会因此而收缩。因此,当InnoDB存储引擎回滚时,它实际上做的是与先前相反的工作。对于每个INSERTInnoDB存储引擎会完成一个DELETE,对于每个DELETEInnoDB存储引擎会执行一个INSERT,对于每个UPDATEInnoDB存储引擎会执行一个相反的UPDATE,将修改前的行放回去。

除了回滚操作,undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo完成的。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。

undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。

InnoDB存储引擎对undo的管理同样采用段的方式。首先InnoDB存储引擎有rollback segment,每个回滚段记录了1024undo log segment,而在每个undo log segment段中进行undo页的申请。共享表空间偏移量为5的页(0,5)记录了所有rollback segment header所在的页,这个页的类型为FIL_PAGE_TYPE_SYS

事务在undo log segment分配页并写入undo log的这个过程同样需要写入重做日志。当事务提交时,InnoDB存储引擎会做以下两件事情:

  • undo log放入列表中,以供之后的purge操作
  • 判断undo log所在的页是否可以重用,若可以分配给下个事务使用

事务提交后并不能马上删除undo logundo log所在的页。因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以最终删除undo logundo log所在的页由purge线程判断。

若为每一个事务分配一个单独的undo页会非常浪费存储空间,特别是对于OLTP类型的应用。在InnoDB存储引擎的设计中对undo页可以进行重用。当事务提交时,首先将undo log放入链表中,然后判断undo页的使用空间是否小于3/4,若是则表示undo页可以被重用,之后新的undo log记录在当前undo log的后面。由于存放undo log的列表是已记录进行组织的,而undo页可能存放着不同事务的undo log,因此purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。

InnoDB存储引擎中,undo log分为:

  • insert undo log
  • update undo log

insert undo log是指在insert操作中产生的undo log。因为insert操作的记录,只对事务本身可见,对其他事务不可见(这时事务的隔离性要求),故该undo log可以在事务提交后直接删除。不需要进行purge操作。

update undo log记录的是对Deleteupdate操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能再事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。update undo log相对于之前介绍的insert undo log,记录的内容更多,所需占用的空间也更大。

purge

Deleteupdate操作可能并不直接删除原有的数据。purge用于最终完成Deleteupdate操作。因为InnoDB存储引擎支持MVCC,所以记录不能在事务提交时立即进行处理,这时其他事务可能正在引用这行,故InnoDB存储引擎需要保存记录之前的版本。而是否可以删除这条记录通过purge来进行判断。若该行记录已不被任何其他事务引用,那么久可以进行真正的Delete操作。可见,purge操作时清理之前的Deleteupdate操作,将上述操作最终完成。而实际执行的操作为Delete操作,清理之前行记录的版本。

为了节省存储空间,InnoDB存储引擎的undo log设计是这样的:一个页上允许多个事务的undo log存在。虽然这并不代表事务在全局过程中提交的顺序,但是后面的事务产生的undo log总在后面。此外,InnoDB存储引擎还有一个history列表,它根据事务提交的顺序,将undo log进行链接。history list表示按照事务提交的顺序将undo log进行组织。在InnoDB存储引擎的设计中,先提交的事务总在尾端。undo page存放了undo log,由于可以重用,因此一个undo page中可能存放了多个不同事务的undo log

InnoDB存储引擎这种先从 history list中找undo log,然后再从undo page中找undo log的设计模式是为了避免大量的随机读取操作,从而提高效率。

若事务为非只读事务,则每次事务提交时需要进行一次fsync操作,以此保证重做日志已经写入磁盘。当数据库发生宕机时,可以通过重做日志进行恢复。为了提高磁盘fsync的效率,当前数据库都提供了group commit的功能,即一次fsync可以刷新确保多个事务日志被写入文件。对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:

  • 修改内存中事务对应的信息,并且将日志写入重做日志缓冲
  • 调用fsync将确保日志都从重做日志缓冲写入磁盘

可以将多个事务的重做日志通过一次fsync刷新到磁盘,这样大大地减少了磁盘的压力,从而提高了数据库的整体性能。对于写入或更新较为频繁的操作,group commit的效果尤为明显。

分布式事务

在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。XA事务允许不同数据库之间的分布式事务,如一台服务器是MySQL数据库,另一台是Oracle数据库的,又可能还有一台服务器是SQL Server数据库,只要参与在全局事务中的每个节点都支持XA事务。

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

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

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

最为常见的内部XA事务存在于binlogInnoDB存储引擎直接。由于复制的需要,因此目前绝大多数的数据库都开启了binlog功能。在事务提交时,先写二进制日志,再写InnoDB存储引擎的重做日志。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勤奋的凯尔森同学

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值