一、事务基本概念
-
与 锁 一样,事务也是数据库区别于文件系统的重要特性之一。 -
事务会把数据库从一种状态转换为另一种一致的状态。<假如整个世界是一个数据库系统,我们的任意行为都是在把世界从一个状态转换为另一个状态,区别是我们只管做,却没有回滚按钮。> -
也可以说:事务是访问并更新数据库中各种数据的一个程序的基本单元。
事务的特性:ACID
A-(atomicity) 原子性:
- 事务内的多个SQL,可以把它们看成一个整体,要么全部成功,要么全不执行,如果某个SQL报错无法执行,那么必须回滚所有已经执行的部分。它代表整个数据库事务是不可分割的工作单元。
C-(consistency) 一致性:
- 任意事务让数据库从一个状态转换为另一个一致的状态,所谓的一致性,可以看成是所有必须遵循的约束,任意事务的提交都不能未被这些约束;例如:主键约束、外键约束等等。举个例子,任意表的主键是不可重复的,如果某个事务提交后导致主键不唯一了,那么就违背了一致性的约束,事务是一致性的单位。
I-(isolation) 隔离性:
- 它也被称为:并发控制、可串行化、锁 。
- 多个事务之间必须是隔离的,未提交事务对任意事务不可见。每个事务读写的对象,与其他事务操作的对象隔离。
D-(durability) 持久性:
- 一旦事务提交,那么事务修改的内容必须是永久的,必须保存到磁盘中固化。
- 当然,保存数据的设备可能因为认为原因损坏,比如:*** rm -rf / *** ,持久性保证的是高可靠性而不是高可用性。
事务类型
1.扁平事务
扁平事务的所有操作的都是在同一层次的,扁平事务是原子的,它是最基本的事务,同样就意味着它是使用最广泛的事务。
2.带保存点的扁平事务
当单个扁平事务太长时,如果为了部分错误回滚整个事务那么成本将难以接受。对此,诞生了带保存点的扁平事务,当错误发生时它允许事务退回到预先设置的某个保存点,而不是将整个事务回滚。
3.链式事务
带保存点的扁平事务在系统发生崩溃时所有的保存点都会丢失,同样带来了上述问题:系统崩溃时,带保存点的 长 扁平事务,依然被全部回滚。对此,出现了链式事务,可以把它理解成带保存点的扁平事务的变种。
链式事务的逻辑是,按序提交事务链中的一个事务时,会释放不需要的对象,对于事务链中的下一个事务需要的资源会自动传递下去。<是不是很像,保存点扁平事务的保存点变成了提交?> 它们的区别如下:
单个长事务被拆分成立多个事务组成的链,发散一下,我想到了小时候家里灌的腊肠,把长长的一条勒成多节带来了非常多的好处,出来好久了,思念家乡的腊肠啊。
4.嵌套事务
它是一个层次的框架,顶层事务之下嵌套的事务可以被称为子事务:
-
嵌套事务可以视作由若干事务构成的一棵树,子树既可以是嵌套事务,也可以是扁平事务; -
嵌套事务的树状结构中,处于叶节点上的事务必须是扁平事务; -
根节点上的事务叫顶层事务; -
嵌套事务中,子事务即可提交也可回滚,但是它的回滚或提交操作并不马上生效,直至它的父事务提交。所以: 任何子事务都在顶层事务提交后才会真正提交。 -
树种任何一个事务的回滚,会引起它的所有子事务一起回滚,所以嵌套事务中的子事务,并不具有持久性。
我的理解是,所有事务都已经执行完成,不论是成功还是失败,不论需要提交还是回滚,顶层事务得到所有信息后统一进行合并操作。此外从最后一条特性也可以得到该结论,如果子事务早于父事务提交,那么该特性将无法实现。
5.分布式事务
分布式事务通常是在分布式环境下运行的扁平事务,需要根据数据库所在的不同位置访问网络中的不同节点,需要保证:要么网络中的所有数据库节点都成功,要么全部不成功。
二、事务的实现概述
我们知道MySQL中的文件包括,redo log 和 undo log,redo log 记录的是物理日志,它记录的页的物理修改操作;undo log 是逻辑日志,它记录的是每个行记录的变化(非锁定一致性读,并发多版本控制:MVCC)。undo log 和 ROW 模式的 binlong 概念有相似的地方,但是它们所支持的功能完全不同,持久化形式也不同,重做日志由Innodb存储引擎维护,binglog 由MySQL数据库层支持。
上一篇文章中所介绍的 锁 保证了事务的隔离性;而由 redo log 保证了事务的原子性和持久性,由undo log 保证事务的一致性(当事务执行破坏了一致性,那么此时需要进行回滚,保证事务的一致性。)、它也被用来支持MVCC功能。
Force Log at Commit 机制 : 当事务提交时,必须先将事务的所有日志写入磁盘文件中进行持久化。
三、redo log
1)组成
- redo log buffer 重做日志缓冲,存在于内存中发生宕机事故时易失
Innodb 会先将重做日志写到重做日志缓冲,在事务提交时或者由Master Thread 定时每秒刷新的磁盘上的重做日志文件中,执行时机如下:
Master Thread 每秒调用 fsync 批量刷新。
每次事务提交后立即调用 fsync 刷新,innodb_flush_log_at_trx_commit = 1 时;
当 redo log buffer 空间使用量到达一半时。(默认为:8M)
- redo log file 重做日志文件,保存在磁盘中,它是持久的。
为了确保每次事务的日志都成功写入磁盘上的重做日志文件中,在每次重做日志缓冲将重做日志写入磁盘后都应该调用一次 fsync 确保数据落盘。fsync的效率取决磁盘性能,所以说磁盘性能决定了事务提交的性能,即为数据库的性能。
可以通过 innodb_flush_log_at_trx_commit 配置重做日志落盘策略:
-
innodb_flush_log_at_trx_commit = 0,事务提交时不进行重做日志写磁盘的操作,而是等待后台线程:master Thread 每秒执行一次fsync落盘。它能带来性能的提升,但是数据库程序发生宕机事故时,可能导致部分事务丢失。 -
innodb_flush_log_at_trx_commit = 1,默认值;它是最严格的策略,能完整支持ACID特性,另外两种配置都破坏了事务的ACID特性;要求每次事务提交时必须调用fsync落盘,它牺牲性能换取了可靠性。 -
innodb_flush_log_at_trx_commit = 2,每次事务提交时将重做日志写入操作系统的文件系统中,不进行fsync,(类redo log buffer 但是它是操作系统的缓冲,redo log buffer 是数据库系统的缓冲)。该配置下,如果只是数据库宕机,操作系统本身没有宕机,那么是不会丢失数据的,但是操作系统宕机也会导致部分提交的事务丢失。
2)redo log 和 binlog
假定有一个足够大的重做日志缓冲,并且有多个事务并发的执行,那么它们的redo log 也会被并发的记录下来,事务开始时间并不能决定不同事务的redo log 的写入顺序。
3)存储
重做日志以块(redo log block:重做日志块)的形式存贮,不论磁盘中的重做日志,还是重做日志缓冲中,重做日志块的大小都是512字节。
在重做日志缓冲(redo log buffer)中,内部的存储结构好比一个以 redo log block 组成的一个循环数组,循环递增的存储、复用。(redo log buffer 刷磁盘的时机之一:redo log buffer 占用空间超过一半时,主动写磁盘。)
4)LSN 重做日志序列号 (log Sequence Number )
lsn 在不同场景中有不同的意义
-
表空间中页上的LSN表示当前页的版本,或者说是该页最后刷新时LSN的大小。(宕机时对比其lsn决定是否需要重做) -
checkpoint 中的 lsn,表示checkpoint (innodb 脏页刷新机制) 执行的位置,它表示小于该LSN的重做日志都已经刷到了磁盘上表空间中的页上。 1. LRU 列表淘汰末位时checkpoint
2. redo log file 无可用空间时checkpoint
3. Master Thread 定时触发checkpoint,将一定数量脏页刷回磁盘
4. mysql系统关闭时触发checkpoint,默认将所有脏页刷回磁盘
-
重做日志文件中的LSN,重做日志文件写入的总量;宕机恢复时如果页的lsn小于重做日志中某个事务的lsn,则该页需要重做。
四、undo log
概念
redo log 记录的是事务的行为,它针对发生变化的行进行逻辑记录,undo log 存在的主要目的为了,当事务发生异常支持回滚操作,除此之外,mysql的一致性非锁定读也可以依赖需要undo log支持的MVCC功能实现。
因为undo log 是根据行记录来进行记录的,回滚时候的操作是执行一个相反的操作。
1. 原事务执行的语句是向某表插入两条数据,那么回滚时根据undo log会执行两个删除语句。
2. 原事务执行的删除那么回滚时执行相反的插入语句。
3.原事务执行的是update操作,回滚时则进行一个反向的update操作。
至于多线程并发问题,那更不用操心了,因为X排它锁的存在当前事务提交前,都不会有任何事务获取到这些行的任意锁。
存储
相较于redo log 存放到重做日志文件中;undo log 存放于表中间中的 undo 段 ( undo log segment ) 中,可以是共享表空间中,也可以配置为存放到独立表空间中。
一个回滚段上可以申请 1024个undo 页,目前MySQL支持128个undo 段,所以MySQL最多支持的在线事务数为:1024 * 128 个。
purge 操作
undo log 正常来说只存在于事务提交前,当事务确定提交后,undo log 就没有意义了。
当事务提交后,并不会直接删除该undo log page ,该undo log page 会放被按事务提交顺序到一个列表中;Innodb存储引擎会进行判断,如果该undo log page 可被重用则会把它分配给别的事务。最后由前文提到的后台线程:purge Thread 来完成最终的删除操作。[判断是否可重用规则:该undo log page 剩余空间是否大于3/4 ]
insert 操作产生的行记录对其它事务来说是不可见的(严格ACID 事务隔离级别下),所以它产生的 undo log 在事务提交时是可以直接删除的。
对于 delete 和 update 操作并不会直接删除索引上的记录,而是把对应的记录改为删除状态。由于需要支持MVCC行多版本控制,需要最后确定该行记录没有被任何事务引用后,由后台线程purge thread 来执行真正的删除操作。
undo log page 可以被重用,说明undo log page 上可能存在着不同事务的 undo 日志,当purge Thread 线程清理undo log page 时是需要离散读取磁盘的,所以该操作较慢。
五、 group commit
1.不使用二进制日志时
任何非只读事务提交时,为了保证重做日志写入磁盘重做日志文件中,应当进行一次 fsync 刷盘操作,但是这个过程一般非常慢,为了更好利用fsync 性能,事务提交时一般会指向如下操作:
- 内存中修改事务信息,比如将缓冲池中数据页的内容更新;然后将对应的重做日志写入redo log buffer 中(重做日志缓冲)。
- 调用fsync 将redo log buffer 中多个事务的重做日志一起刷落磁盘。
上述操作让多个事务的重做再一次操作中刷回磁盘。
2.开启二进制日志
但是在使用了二进制日志时,上述group commit 就不能使用了。
如下为使用二进制日志后事务提交时的操作: 一般事务提交时会执行如下操作:
-
1.存储引擎进行prepare操作。 -
2.MySQL数据库上层写二进制日志( binlog )。 -
3.存储引擎将 redo log 写入重做日志文件中。 第三步可以细分:
a.修改内存中的数据信息,将修改更新到重做日志缓冲。
<这个操作在事务开始执行就在不断的进行,并不是事务提交时才写redo log buffer。>
b.调用fsync,确保从重做日志缓冲写入磁盘。
此时需要确保binlog 写入顺序与 Innodb 存储引擎层,事务的提交顺序一致,否则可能出现主从库数据不一致的情况,为了支持该功能,mysql内部使用了一个锁,导致当有事务从redo log buffer 刷落磁盘时,别的事务不能向redo log buffer 中写入重做日志,依次保证顺序性。<例如:多个事务分别把同一行的同一列修改为了多个值,那么必须保证主从库中有同样的修改顺序,否则就可能导致主从库不一致的情况。>
3.解决方法
解决方法由 MariaDb 数据库开发人员提出,细节不再描述可以讲一下核心思想。
该方法不仅redo log 是 group commit , binlog 也是group commit。
解决的思想是:
- 在数据库层将每个事务的binlog 记录到内存,并在队列中记录它们的顺序
- 将多个事务的binlog 一起落盘
- 最后根据队列中记录的顺序依次去完成事务的提交。
解决该问题后,group commit 的性能受限于每次提交时,队列中的事务数,事务数越多性能越高。
|