• MySQL事务


    目录

    一、事务的特性

    二、事务的分类

    1. 扁平事务

    2. 带有保存点的扁平事务

    3. 链事务

    4. 嵌套事务

    5. 分布式事务

    三、事务的实现

    1. 重做日志文件(redo log file) 

            ①. 基本概念

            ②. 重做日志格式

            ③. 日志块(log block) 

            ④. 日志组(log group)

            ⑤. 日志序列编号(LSN)

            ⑥. 恢复

    2. undo日志(undo log)

            ①. 基本概念

            ②. undo日志格式

            ③. undo存储管理 

    3. purge操作

    4. 组提交(group commit)

    三、事务的隔离级别

    四、隐式提交的SQL

    五、参考资料


    一、事务的特性

            事务有ACID特性:A原子性、C一致性、I隔离性、D持久性。InnoDB存储引擎中,redo log(重做日志文件,称为日志)实现原子性和持久性;锁实现隔离性;undo log(回滚日志)实现一致性。

            如下表所示,ACID特性的对比。事务的持久性则采用Force Log at Commit机制实现,即:事务提交时,必须先将该事务的所有日志写入到redo log(重做日志文件)进行持久化,然后事务提交后才算真正完成。

    特性概念实现

    原子性

    (A _ Atomicity)

    1. 事务中的所有SQL语句,要么都成功或失败;

    2. 事务是不可分割的工作单位

    redo log

    一致性

    (C _ Consistency)

    1. DB从一种状态转到另一个一致的状态;

    2. 事务是一致性的单位。

    undo log

    隔离性

    (I _ Isolation)

    事务提交之前对其他事务都不可见lock

    持久性

    (D _ Durability)

    事务提交后即使发生宕机,则也是永久性的redo log

    二、事务的分类

            InnoDB存储引擎支持扁平事务、保存点的扁平事务、链事务、分布式事务原生不支持嵌套事务,下图事务分类总结。

    类型特点
    扁平事务

    1. 所有操作处于同一层次,不能提交部分操作,其是最频繁的事务;

    2. 应用程序中原子操作的基本组成模块

    带有保存点的扁平事务1. 支持扁平事务外,还支持回滚到某个保存点
    链事务

    1. 事务提交后隐式地开启下一个事务;

    2. 事务提交和开启下一个事务为一个原子操作

    3. 链事务回滚仅限于当前事务,即:只能回滚到最近一个保存点;

    4. 链事务:提交后释放当前事务所持有的锁;    

        带有保存点的扁平事务:不影响迄今为止所持有的锁。

    嵌套事务

    1. 顶层事务控制子事务;

    2. 任何子事务都在顶层事务提交后才真正的提交

    3. 子事务仅保留A、C、I特性,不具有D特性;

    4. 嵌套事务中任意一个事务回滚,则所有子事务一同回滚。

    分布式事务

    1. 多个独立的事务资源参与到一个全局事务中;

    2. InnoDB存储引擎支持XA事务(主从复制)

    1. 扁平事务

            扁平事务(Flat Transaction)指所有操作处于同一层次中,由BEGIN开始,COMMIT或ROLLBACK结束,实际生产中使用最频繁的事务。因此,扁平事务是应用程序成为原子操作的基本组成模块。

            如下图所示,扁平事务中的三种情况。看出,扁平事务的主要限制不能提交或回滚事务的一部分

    2. 带有保存点的扁平事务

            带有保存点的扁平事务(Flat Transaction with Savepoint) 指事务执行过程中回滚到同一事务中较早的一个状态,也支持扁平事务。

            保存点(Savepoint)用来通知系统记住当前事务的状态,以便之后发生错误时,事务能回滚到保存点当时的状态。注意,保存点在事务内部是单调递增,即使回滚后再开始操作,也不影响保存点的计数。如下图所示。

    事务中使用保存点

            事务开始时,隐式地设置一个保存点。那么在扁平事务中,只有一个保存点,因此只能回滚到事务的开始状态。

    3. 链事务

            链事务(Chained Transaction)指当前事务提交和开始下一个事务合并为一个原子操作。在当前事务提交后,释放数据对象,将必要的处理上下文隐式地传给下一个事务,即:下一个事务会看到上个事务的处理结果

            链事务与带有保存点的扁平事务的区别:

    • 回滚:带有保存点的扁平事务能回滚到任意正确的保存点;链事务只能回滚最近的一个保存点
    • 锁:带有保存点的扁平事务不影响迄今为止所持有的锁;链事务提交后释放当前事务持有的锁

    4. 嵌套事务

            嵌套事务(Nested Transaction)指事务中嵌套事务,顶层事务(父事务)控制着各个层次的事务(子事务)

            嵌套事务的特点:

    • 位于根节点的事务称为顶层事务(父事务),其他事务称为子事务;
    • 任何子事务提交后不是立即提交,而是顶层事务提交后才真正提交
    • 任何事务回滚都会引起其他事务一同回滚,故子事务有A、C、I特性,不具有D特性。

    5. 分布式事务

            分布式事务(Distributed Transaction)指在多个独立的事务资源参与到一个全局事务中。InnoDB存储引擎对XA事务的支持。

            XA事务由一个或多个资源管理器、一个事务管理器、一个应用程序组成:

    • 资源管理器(Resource Managers):提供访问事务资源的方法(DB就是资源管理器)
    • 事务管理器(Transaction Manager):协调参与全局事务中的各个事务
    • 应用程序(Application Program):定义事务边界,指定全局事务的操作

            XA分布式事务使用的是两阶段提交方式:

    • 第一阶段:所有参与全局事务的节点开始准备(Prepare)_ 告诉事务管理器准备好提交;
    • 第二阶段:事务管理器告诉资源管理器执行事务提交或回滚。

            MySQL数据库中还存在另外一种分布式事务,不同存储引擎之间的事务,称为内部XA事务。最常见的内部XA事务存在于binlog与InnoDB存储引擎之间。在事务提交时,先写二进制日志,再写InnoDB重做日志,两个写入要求是原子操作,否则主从数据库存在数据不一致。如下图所示,数据库宕机发生在①、②之后,③之前,则会导致主从数据库存在数据不一致。

            为了解决这个问题,如下图所示,采用XA事务。当事务提交时,InnoDB存储引擎会先做一个PREPARE操作,将事务xid写入。数据库发生宕机恢复后,先检查准备的UXID事务是否已经提交,若没有,InnoDB存储引擎层会再一次执行提交操作

    三、事务的实现

    1. 重做日志文件(redo log file) 

            ①. 基本概念

            重做日志文件(redo log file)记录提交事务修改页的操作,是物理日志(默认数据目录下:ib_logfile0、ib_logfile1)。其目的:实现事务的原子、持久特性,数据库的恢复使用。

            事务由两部分组成:

    • 重做日志缓冲(redo log buffer):内存存储;易丢失、写入快
    • 重做日志文件(redo log file):物理文件;持久存储,写入慢

            事务日志的产生过程:

    • step1:事务开始时,产生对页修改的操作日志,写入重做日志缓冲中;
    • step2:事务提交时,重做日志缓冲写入文件系统缓存;
    • step3:写入文件系统缓存后,InnoDB调用一次fsync确保日志写入到redo log file

            需要注意的是:step3写入磁盘执行时间最慢,磁盘的性能决定事务提交的性能,也决定DB的性能。参数innodb_flush_log_at_trx_commit控制重做日志刷新到磁盘的策略,默认1(事务提交同步一次fsync) 。

            事务的持久性则采用Force Log at Commit机制实现:事务提交时,必须先将该事务的所有日志写入到redo log file(重做日志文件)进行持久化,然后事务提交后才算真正完成,即:事务提交时,先写重做日志再提交事务

            重做日志(redo log)与二进制日志(binlog)的区别,如下表所示。

    区别redo logbinlog
    产生层次InnoDB存储引擎层MySQL数据库层 (任何引擎都会产生)
    内容形式物理日志,记录页的修改逻辑日志,记录对应的SQL或行
    写入磁盘的时机

    1. 事务进行中不断的被写入磁盘,不是事务提交的顺序写入;

    2. 每个事务对应多条日志,写入是并发的

    1.事务提交后完成一次写入;

    2.按事务提交顺序写入,一个事务只能对应一个日志

            如下图所示,二进制日志对每一个事务仅保存一个日志,且按事务提交的顺序写入;重做日志对每个事务则对应多个日志条目(不同的页修改操作)且事务并发写入,不是事务提交的顺序写入

    redo log和binlog写入的时间点不同

            ②. 重做日志格式

            重做日志格式组成如下图所示,可以看出redo log是记录页的修改操作

            根据不同的重做日志类型,会有不同的redo log body,如下图所示。

    插入和删除的重做日志格式

            ③. 日志块(log block) 

            InnoDB存储引擎中,重做日志以512字节(块)存储,即:重做日志缓存、重做日志文件都是以块的形式进行保存,称之为“重做日志块”(redo log block)。重做日志块大小与磁盘扇区大小一样都是512字节,因此重做日志写入可以保证原子性,无需doublewrite技术。若修改页的重做日志大于512字节,则需要分割多个重做日志块进行存储。

            重做日志块的组成:块512字节 = 头部12字节 + 内容 492(512-12-8)字节 + 尾部8字节,如下图所示。内容存储重做日志(重做日志格式见上小节)。

            注意:其中LOG_BLOCK_FIRST_REC_GROUP表示块中第一个事务开始位置的日志偏移量, 如下图所示。

            ④. 日志组(log group)

            重做日志组(log group)是个逻辑概念,由多个重做日志文件组成。组内的文件大小相同,默认总大小512GB。默认情况下,一个InnoDB存储引擎只有一个日志组。

            重做日志文件存储的就是重做日志缓冲里面的重做日志块,也是以块的形式管理。InnoDB存储引擎运行过程中,重做日志缓存刷新到磁盘的时机如下:

    • 事务COMMIT时
    • log buffer中一半内存空间已被占用时
    • log buffer的Checkpoint时

            日志块追加到重做日志文件的尾部,当文件被写满时,则写入下一个重做日志文件,即:采用round-robin方式写入。       

            ⑤. 日志序列编号(LSN)

            日志序列编号(Log Sequence Number _ LSN)表示写入重做日志的字节总数,占用8字节且单调递增。

            LSN不仅在重做日志,而且还在每个页中。LSN的含义如下:

    • 重做日志写入的字节总量
    • Checkpoint的位置
    • 页的版本(判定页是否刷新、是否需要进行恢复)

            ⑥. 恢复

            InnoDB存储引擎启动时,不管上次是否正常关闭,则都会尝试进行恢复数据操作。读取重做日志文件,根据数据页Checkpoint的LSN与重做日志文件中修改页的LSN进行比较,来判定数据页是否需要恢复操作

            重做日志是物理日志,则幂等。而二进制日志是逻辑日志,恢复慢。所以,重做日志恢复速度比二进制日志恢复快。

    2. undo日志(undo log)

            ①. 基本概念

            对数据库修改时,不仅会产生redo log,而且还会产生undo log。回滚日志(undo log)记录修改行的操作,是逻辑日志,存放在共享表空间的undo段(undo segment)。其目的:事务回滚,MVCC技术来实现事务一致性。

            当InnoDB存储引擎回滚时,实际做的事与先前相反的工作,如下所示。

    • INSERT操作:回滚则完成一个DELETE                    
    • DELETE操作:回滚则完成一个INSERT                     
    • UPDATE操作:回滚则完成一个相反的UPDATE

            MVCC技术是通过undo log完成,当读取行时,判断该行是否被其他事务占用,若是则当前事务通过undo读取之前的行版本信息,实现非锁定读。

            事务提交对undo log的处理:

    • 将undo log放入链表中,供purge操作
    • 判断undo log所在页是否可以重用,若可以分配下一个事务

            需要注意的是,写入undo log日志的过程,也伴随着产生redo log,即:undo log也需要通过redo log来持久化

            ②. undo日志格式

            回滚日志格式分类,如下:

    • insert undo log:INSERT操作;事务提交后直接删除,无需purge操作
    • update undo log:DELETE/UPDATE操作;事务提交后不直接删除,放入链表等purge操作

            注意,对于INSERT操作回滚时,则直接删除,符合事务的隔离性(只对当前事务可见,其他事务不可见),而DELETE/UPDATE操作,则放入链表等purge操作真正删除或修改;undo log是逻辑日志,只是将数据逻辑的恢复之前的样子,则回滚被逻辑的取消,但是表空间大小不会因回滚而收缩。

            ③. undo存储管理 

            InnoDB存储引擎对undo segment的管理采用段的方式,支持同时在线的事务数量为:128 * 1024

    回滚段(rollback segment):一个undo段(undo segment)支持128个rollback segment

    undo日志段(undo log segment):每个rollback segment有1024个undo log segment,在该段中进行undo页的申请

            与rollback segment相关的参数有:

    • innodb_undo_directory:rollback segment所在的路径,默认./(ibdata1)
    • innodb_undo_logs:rollback segment的个数,默认128
    • innodb_undo_tablespaces:undo日志空间数量,即:构成rollback segment的文件数

            事务提交时,判定undo页是否重用

    • step1:事务提交时,undo log放入链表中
    • step2:判断所在页使用空间是否小于3/4
    • step3:若可以重用,则之后的undo log记录在当前undo log的后面

    3. purge操作

            当DELETE删除一条记录时,将记录delete flag设置为1,记录并没有删除,还存在于B+树中。所以DELETE和UPDATE操作并不直接删除或修改原有数据,而是“延迟”在purge操作中完成。因此purge用于完成DELETE、UPDATE操作

            purge用于完成DELETE、UPDATE操作,这样的设计InnoDB存储引擎支持MVCC,若该行记录不被其他事务引用,则可以真正删除操作。根据之前undo log的介绍,一个undo页有多个事务的undo log存在,后面的事务总在最后,但不是事务提交顺序。因此,InnoDB存储引擎有个history list,目的是按事务提交的顺序,将undo log进行链接(先提交放在尾端)

    purge操作的过程

            如上图所示, purge操作的过程如下。清除undo page,避免大量随机读取操作,提高purge效率

    • step1:从history list找到第一个需被清理的记录,如:trx1;
    • step2:清理step1的记录后,该记录所在页中继续找需被清理的记录,如:trx3、trx7,而trx5被其他事务引用,不能删除;
    • step3:记录所在页没有被清理的记录后,再次去history list找下一个记录,如:trx2。

            与purge有关的相关参数,innodb_purge_batch_size越大,每次回收undo页就越大,但是太大会导致CPU和磁盘过于集中处理导致性能下降。

    参数描述
    innodb_purge_batch_size每次purge操作需要清理的undo page的数量,默认300
    innodb_max_purge_lag控制history list的最大长度,默认0(不对history list做任何限制)
    innodb_max_purge_lag_delay更新行,最大延迟的毫秒数,默认0

    4. 组提交(group commit)

            对于非只读事务时,每次提交事务后需调用一次fsync操作,来保证redo日志已经写入磁盘,而对磁盘的操作效率很慢。因此MySQL提供组提交的功能,其目的是一次fsync刷新多个事务日志写入重做日志文件(提高磁盘fsync效率)

            MySQL5.6版本之前,开启二进制日志后,group commit功能会失效,而MySQL5.6采用BLGC(Binary Log Group Commit)实现方式。提交顺序放入队列,第一个为leader,其他follower,leader控制follower的行为,如下图所示。BLGC的三个阶段

    • Flush阶段:每个事务的二进制日志写入内存中
    • Sync阶段:内存中的binlog刷新到磁盘,若队列有多个事务,则仅一次fsync(BLGC)
    • Commit阶段:leader根据顺序调用InnoDB事务提交(支持group commit)
    BLGC的三阶段

            当有一组事务在进行Commit阶段时,其他新事务可以进行Flush阶段,从而使group commit不断生效。参数binlog_max_flush_queue_time控制group commit时的flush阶段中等待时间,默认0(不等待),当一组事务完成提交,当前一组事务也不马上进入Sync阶段,而是等待一段时间。

    三、事务的隔离级别

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

    • READ UNCOMMITTED:读未提交,会产生脏读(会读取删除/修改的数据)
    • READ COMMITTED:读已提交,会产生不可重复读
    • REPEATABLE READ:可重复读,会产生幻读(读到被事务提交删除的数据)
    • SERIALIABLE:序列化读,并发量低

            REPEATABLE READ是2.9999⁰的隔离,没有幻读的保护;而SERIALIABLE称为隔离,或是3⁰的隔离。SQL和SQL2标准默认事务隔离级别是SERIALIABLE。

            需要注意的是InnoDB存储引擎默认事务隔离级别是REPEATABLE READ,与SQL标准不同的是,InnoDB存储引擎在该级别下使用Next-Key Lock锁的算法,避免的不可重复读,已经达到了事务的隔离性要求,即达到了SERIALIABLE的隔离性要求。

            InnoDB存储引擎的隔离性是通过锁实现的。READ COMMITTED隔离级别,则采用Record Lock算法,关闭了Gap Lock;REPEATABLE READ隔离级别时,采用Next-Key Lock(Record Lock + Gap Lock);SERIALIABLE隔离级别时,对每个SELECT语句自动加上LOCK IN SHARE MODE,加共享锁,因此读占用了锁,对一致性非锁定读不支持,此时隔离级别符合数据库理论的要求。

    四、隐式提交的SQL

            隐式提交是指执行完这些SQL语句后,会有一个隐式COMMIT操作。如下所示,列出隐式提交有哪些SQL语句。注意TRUNCATE TABLE与DELETE一样,但是不能回滚。

    MySQL隐式提交的SQL

    五、参考资料

    MySQL锁_爱我所爱0505的博客-CSDN博客

    SQL提交数据三种类型 - 走看看

    数据库事务的四种隔离级别 - Nausicaa0505 - 博客园

  • 相关阅读:
    qt调试代码时跳转到汇编代码处
    NSSCTF中的pop、babyupload、cve版本签到、奇妙的MD5、easy_html
    如何显示标注的纯黑mask图
    描述符——设备描述符
    k8s--基础--18.5--存储卷--类型--PVC理论
    【java核心技术】Java知识总结 -- 异常
    市场饱和,你得抓住Android初级开发破局关键点---Framework
    asp.net高铁站购票管理信息系统VS开发sqlserver数据库web结构c#编程计算机网页项目
    SpringBoot+Vue实现前后端分离的校园健康检测管理系统
    跑通官方的yolov7-tiny实验记录(yolov7-tiny可作为yolov5s的对比实验网络)
  • 原文地址:https://blog.csdn.net/m0_37543627/article/details/127241265