【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的

----以MySQL的InnoDB介绍

目录

  • 前言
    • 事务,事务到底是什么?
  • 一、事务的特征:
  • 二、事务特征具体保证
    • 1、Redo Log(重做日志) ---保证事务的持久性
      • 1.1、刷盘时机
      • 1.2、redo log记录形式
      • 1.3、redo log日志的好处
    • 2、undo log(回滚日志)---保证事物的原子性
      • 2.1、undo log格式
    • 3、MVCC(多版本并发控制)---保证事务的隔离性
      • 3.1、隔离级别
        • 问题概述
      • 3.2、MVCC概述
      • 3.3、ReadView
      • 3.4、undo log 版本链
      • 3.5、举例演示:
      • 引用

前言

事务,事务到底是什么?

事务其实就是以一组访问或者更新数据库的各种数据项的一个执行单元,可以是一条也可以是多条执行操作。

  • 在针对查询时,只要是在本事务内执行的查询操作,不管数据库被如何被其他事务修改了,只要本事务内没修改,事务内查询的结果都会返回一致的结果.
  • 针对修改操作时,不管多少条修改语句,在执行过程中会出现三种情况,执行成功提交事务,数据库全部修改成功;系统中断,全部回滚,全部回退到事务开启之前的结果;一条执行失败,全部回滚,全部回退到事务开启之前的结果(可以通过设置事务保存点让只回退到指定保存点,可以避免语句过多时全部回退);

理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的ACID特性。值得注意的是,虽然理论上定义了严格的事务要求,但是数据库厂商出于各种目的,并没有严格去满足事务的ACID标准。例如,对于MySQL的NDB Cluster引擎来说,虽然其支持事务,但是不满足D的要求,即持久性的要求。对于Oracle数据库来说,其默认的事务隔离级别为READ COMMITTED,不满足I的要求,即隔离性的要求。虽然在大多数的情况下,这并不会导致严重的结果,甚至可能还会带来性能的提升,但是用户首先需要知道严谨的事务标准,并在实际的生产应用中避免可能存在的潜在问题。对于InnoDB存储引擎而言,其默认的事务隔离级别为READ REPEATABLE,完全遵循和满足事务的ACID特性。

一、事务的特征:

事务四个典型特性,即ACID,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability

  • 原子性:
    事务作为一个整体被执行,包含在其中的对数据库的操作要么全部都执行,要么都不执行。
    • ——原子性的实现主要是通过undo log日志进行实现的,如果事务中一个SQL语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。事务的原子性表明事务就是一个整体,当事务无法成功执行的时候,需要将事务中已经执行过的语句全部回滚,使得数据库回归到最初未开始事务的状态。
  • 一致性:
    指在事务开始之前和事务结束以后,数据不会被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。
    • ——其他三个特性保证的数据的一致性。换句话说,ACID里的AID都是数据库的特征,也就是依赖数据库的具体实现。而唯独这个C,实际上它依赖于应用层,也就是依赖于开发者。这里的一致性是指系统从一个正确的状态,迁移到另一个正确的状态。什么叫正确的状态呢?就是当前的状态满足预定的约束就叫做正确的状态。而事务具备ACID里C的特性是说通过事务的AID来保证我们的一致性。
  • 隔离性:
    多个事务并发访问时,事务之间是相互隔离的,一个事务不应该被其他事务干扰,多个并发事务之间要相互隔离。
    • ——隔离性主要通过锁机制进行保证,事务之间的隔离,是通过锁机制实现的。当一个事务需要对数据库中的某行数据进行修改时,需要先给数据加锁。加了锁的数据,其它事务是不运行操作的,只能等待当前事务提交或回滚将锁释放。
    • 通过悲观锁、乐观锁(MVCC)
  • 持久性:
    表示事务完成提交后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
    • ——redo log是实现持久性的关键,通过redo log一直写入磁盘保证数据的持久性

二、事务特征具体保证

1、Redo Log(重做日志) —保证事务的持久性

物理级别——只针对InnoDB

重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的;而通过执行操作时一直往redo log buffer中记录数据,然后再写入到redo log file中去,使得数据库可以在数据丢失或系统崩溃时,通过重新执行日志中的操作来还原数据,从而保证数据的持久性。

因为在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer写入redo log file实际上是先写入OS Buffer,然后再通过系统调用fsync()将其刷到redo log file中,因此redo log file包括写入的数据和写入的位置。过程如下:

1.1、刷盘时机

理想情况下,事务一提交就会进行刷盘操作,但是实际上是刷盘的时机是根据策略来决定的。

InnoDB存储引擎为redo log的刷盘策略提供了innodb_flush_log_at_trx_commit参数,它支持三种策略:

  • 0:设置为0的时候,事务提交时不会将redo log buffer(缓存)中日志写入到os buffer,而是每秒写入os buffer(page cache)并调用fsync()写入到redo log file(文件)中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,
    • 当mysql服务崩溃时,会丢失1秒钟的数据。
  • 1:设置为1的时候,事务每次提交都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中,
    • 这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,1O的性能较差。
  • 2:设置为2的时候,每次提交事务时都只把redo log buffer写入os buffer,然后是每秒调用fsync()os buffer中的日志写入到redo log file,这就是说如果mysql挂了,
    • 因为事务一提交已经写到系统缓存了,所以不会出现数据丢失,但如果操作系统挂了的话就会出现1秒的数据丢失

1秒写入是通过Innodb存储引擎后台的一个线程进行实现的,他会每秒去把redo log buffer中的内容写入到文件系统缓存os buffer,然后调用fsync刷盘

【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第1张图片

1.2、redo log记录形式

redo log是固定大小的,所以只能循环写,从头开始写,写到末尾就又回到开头,相当于一个环形。当日志写满了,就需要对旧的记录进行擦除,但在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。在redo log满了到擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求,所以有可能会导致MySQL卡顿。(所以针对并发量大的系统,适当设置redo log的文件大小非常重要!!!)

1.3、redo log日志的好处

  • redo日志降低了刷盘频率;
  • redo日志占用的空间非常小;

存储表空间ID、页号、偏移量以及更新的值,所需的存储空间是很小的,刷盘快。

特点:

  • redo日志是顺序写入磁盘的
    • 在执行事物的过程中,每执行一条语句,就可能产生若千条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是顺序IO,效率比直接写表的随机IO快,
  • 事务执行过程中,redo log不断记录
    • redo log跟bin log的区别,redo log是存储引擎层产生的,而bin log是数据库层产生的。假设一个事务,对表做10万行的记录插入,在这个过程中,一直不断的往redo log顺序记录,而bin
      log不会记录,直到这个事务提交才一次写入bin log文件中。

2、undo log(回滚日志)—保证事物的原子性

逻辑级别——只针对InnoDB

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

undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改
当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作(也就是会把原来执行的操作撤销,而不是单纯的回退到之前那个时间节点去)。

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

存放位置
undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undosegment)。undo段位于共享表空间内。可以通py_innodb_page_info.py工具来查看当前共享表空间中undo的数量。

2.1、undo log格式

  • insert undo log

    • insert undo log是指在insert操作中产生的undo log, 仅用于事务回滚. 因为insert操作的记录, 只对事务本身可见, 对其它事务不可见, 所以该日志可以在事务commit后直接删除. 不需要进行purge(后台清除线程)操作
      【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第2张图片
  • update undo log

    • update undo log是对delete和update操作产生的的undo log. 该undo log可能需要提供MVCC机制, 因此不能在事务commit后就进行删除,等待purge线程(后台清除线程)进行最后的删除
      【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第3张图片

3、MVCC(多版本并发控制)—保证事务的隔离性

3.1、隔离级别

先了解MVCC之前我们得先知道事务的隔离级别,才好知道方便是如何解决事务的隔离性问题的!
在mysql中事务一共分为了四个隔离级别,并且分别可以依次排除掉不同的问题

隔离级别 允许脏读 允许不可重复读 允许幻影读 并发性 性能
READ UNCOMMITTED(未提交读)
READ COMMITTED(已提交读)
REPEATABLE READ(可重复读) 是(InnoDB不会出现)
SERIALIZABLE(串行化)
问题概述
  • 脏读:

    • 脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。

      【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第4张图片

    • 解决方案:

      • 通过把隔离级别设置为READ COMMITTED,实现在读取数据时,避免了去读取未提交的事务的数据,有效的解决了脏读的问题。
  • 不可重复读:

    • 对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

      【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第5张图片

    • 解决方案:

      • 通过把隔离级别设置为REPEATABLE READ,通过mvcc具体实现,为每个事务创建一个数据快照,并在事务提交时释放旧的数据快照,从而解决了不可重复读的问题。这确保了事务之间的隔离性和数据一致性,使得每个事务都可以在不受其他事务干扰的情况下进行读取和修改数据。
  • 幻影读:

    • 幻读是针对数据**插入(INSERT)**操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

      【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第6张图片

    • 解决方案:

      • InnoDB中,把隔离级别设置为REPEATABLE READ,也会解决掉幻影读的问题,在生成快照的基础上,事务会对读取的数据通过加锁的方式,以防止其他事务对这些行进行修改。这意味着当一个事务正在读取某一行时,其他事务不能修改该行,从而避免了幻读问题。
      • 而如果是范围查询时也会通过加间隙锁(Next-Key Locking)的方式,对查询范围内的间隙(不存在的数据)进行锁定,以防止其他事务在该范围内插入新数据,从而避免了幻读。
      • 如果是SERIALIZABLE,则是每个事务对一个数据进行查询等操作时,其他的事务不会进行等待,等待该事务执行完后在执行,以串行的方式执行操作.

在数据库中读取的方式主要有两种方式:

  • 当前读:官方叫做Locking Reads(锁定读取),读取数据的最新版本. 常见的update/insert/delete、还有 select … for update、select … lock in share mode 都是当前读.
  • 快照读:官方叫做 Consistent Nonlocking Reads(一致性非锁定读取), 也就是 MVCC 生成的 ReadView, 用于普通的 select 的语句.

3.2、MVCC概述

MVCC的工作原理是使用数据在某个时间点的快照来实现的。这意味着,无论事务运行多长时间,都可以看到数据的一致视图,实现方式主要是通过ReadView 和undo log版本链实现。也意味着不同的事务可以在同一时间看到同一张表中的不同数据!如果你之前没有这方面的概念,这句话听起来可能有点让人迷惑,熟悉了以后你会发现还是很容易理解的

3.3、ReadView

ReadView 的主要作用是为每个事务提供一致性的数据视图—-该视图存储的主要是:readview[m_ids], m_low_limit_id 格式存放,m_ids存放的是事务id的list集合

读以提交可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,说的就是ReadView,这也是可重复读和不可重复读的关键,可重复读是在事务开始执行第一次查询的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。

它主要包括:

  • m_ids:当前有哪些事务正在执行,且还没提交,这些事务的id就会存在这里
    • min_trx_id(**m_up_limit_id**),是指 m_ids 里最小的值(当前未提交事务列表中的第一个);
  • max_trx _id(m_low_limit_id):是指下一个要生成的事务 id。下一个要生成的事务 id 肯定比现在所有事务的 id 都大;
  • creator_trx_id:每开启一个事务都会生成一个 ReadView,而 creator_trx_id 就是这个开启的事务的 id。

对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

  1. 当前事务内的更新,可以读到;
  2. 版本未提交,不能读到;
  3. 版本已提交,但是却在快照创建后提交的,不能读到;
  4. 版本已提交,且是在快照创建前提交的,可以读到;

3.4、undo log 版本链

  • undo log 版本链是基于 undo log 实现的。MySQL可从 undo log 日志中,读取到原插入、修改、删除之前的值,最终把值重新变回去,这就是回滚操作。

    • undo log 中主要保存了数据的基本信息,比如说日志开始的位置、结束的位置,主键的长度、表id,日志编号、日志类型

    在这里插入图片描述

  • undo log 还包含两个隐藏字段 trx_id(事务id) 和 roll_pointer(回滚指针)。trx_id 表示当前这个事务的 id,MySQL 会为每个事务分配一个 id,这个 id 是递增的。roll_pointer 是一个指针,指向这个事务之前的 undo log。

undo log日志(只有在更新聚集索引记录时,才写undo log)

举例:每当一个事务修改了数据行,他执行的修改记录会先保持到undo buffer中,然后在持久化到磁盘的undo log文件中,直到该事务提交,并且该事务的undo log没有被别的事物引用到(当一个新的事物对该数据进行操作时就会生成一个快照引用)

  • 多个日志会根据生成时间组成一条undo log链表

【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第7张图片

  • purge线程:

    purge 线程是一个周期运行的垃圾收集线程, 对于没有事务引用的undo log进行清除, 但当purge线程发现undo log没有事务引用时将自动清除, 因为insert undo log在事务完成时直接删除, 所以需要清除的都是update undo log,

3.5、举例演示:

实现在InnoDB下的repeatable read隔离级别下ReadView 和undo log如何
【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第8张图片

  1. 事务100 对user表执行 insert + 提交

说明:这步就是为了构建原始记录

begin;
insert into user(id,name) values(1,'张三');
commit;
  1. 然后, 事务101 对user表执行 update + 提交
  2. 同时, 事务102事务101提交前, 查询了该记录:说明:有事务使用的undo log, purge线程不会清除这条记录
begin;
update user set name = '李四' where id = 1;

commit;
begin;
-- 读到的name为张三
select * from user where id = 1;

【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第9张图片

  1. 然后,事务103 对user表执行 update 未提交
  2. 同时,事务102 再次读取: 在RR级别, 后面所做的更改依然不可见:
  3. 同时, 事务104 执行了查询, 读取到了事务101提交的“李四”(因为事务103尚未提交),然后事务103提交:

【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第10张图片

  1. 然后, 事务105来了, 它读取到的却是“王五”,然后三个查询等都提交,由此发现3个事务分别看到了3个版本
    【2023】从事务的特征以及解决方式上分析MySQL是如何保证事务的_第11张图片

引用

https://zhuanlan.zhihu.com/p/190886874
https://blog.csdn.net/scm_2008/article/details/127985117
MySQL技术内幕:InnoDB存储引擎(第2版)_姜承尧

你可能感兴趣的:(数据库,mysql,数据库,事务,InnoDB)