MySQL———事务和事务的隔离性

Posted wtxuebc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL———事务和事务的隔离性相关的知识,希望对你有一定的参考价值。

    事务在现阶段数据库中很常见,因为现版本 InnoDB 代替了 mysql 的原生存储引擎 MyISAM,这与它不支持事务有很大关系;而 InnoDB 是支持事务的,所以说事务的操作是在引擎层的;事务的实现和 InnoDB 的 redo log 密不可分,本篇博客主要讲事务的隔离性及底层实现;学术有限,欢迎大佬补充!

事务:简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败;原子的执行一组数据库操作

​    在 MySQL 中,事务是在引擎层实现的。MySQL 是一个支持多引擎的系统,因为Server层和引擎层是分离开来的,引擎层是插件式的,支持多个存储引擎,其中Server层执行引擎层提供的接口; 但是并不是所有的存储引擎都支持事务。MySQL 原生引擎 MyISAM就不支持事务,这也是MyISAM 被 InnoDB 取代的中啊哟原因之一;

    本篇文章以 InnoDB 为例,刨析 MySQL 在事务支持方面的隔离性实现实现;

事务的ACID

​     事务的 ACID 就是 A(Atomicity原子性)C(Consistency一致性)I(Isolation隔离性)D(Durability持久性);

​     Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
​     Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
​     Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
​     Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。( 感兴趣的同学可以了解一下 redo log )

隔离与隔离级别

​     当数据库上有多个事务同时执行的时候,就有可能出现脏读 ( 读到其他事务为提交的数据 ) 、不可重复读 ( 前后读取的内容不一样,主要是读到更新带来的内容 ) 、幻读 ( 读到了上次没有的内容,单指插入带来的内容 )

​     需要解决上面带来的问题,就需要对事物进行隔离;在谈隔离之前你要知道,隔离的越严实,效率越低;因此很多时候,我们都要在二者之间找一个平衡点。

​     在进行隔离的时候,不同的隔离程度会解决不同程度带来的问题,因此涉及到一个隔离级别;

​ SQL标准的事务隔离级别包括:

​     未提交读(Read uncommitted)(一个事务还没提交时,它做的变更就就被别的事务看到)

​     提交读(read committed)(一个事务提交之后,它做的变更才能被其他事务看到)

​     可重复读(repeatable read) ( 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 )

​     串行化(Serializable)(顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。)

再注意一个窍门:可重复读和串行化的区别是:

    可重复读隔离级别下的事务 A 没提交时,“别的事务 B 已经对他的数据做了修改“,而必须等 A 提交后,B修改的数据才能读到;但是A还没提交时,此时B已经修改了!

而串行化加锁了!B 再修改时就阻塞了!必须等 A 提交后,B才能修改,“此时A还没提交时,B不可能提交成功!,B必须在A后面提交!”

举一个简单的例子更形象的了解隔离级别:

mysql> create table T(c int) engine = InnoDB;
inset into T(c) values(1);

​       [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0tlxhcV-1622621195535)(C:\\Users\\86155\\AppData\\Roaming\\Typora\\typora-user-images\\1622445266150.png)]
​     我们来看看在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图里面 V1、V2、V3 的返回值分别是什么。

  • 若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
  • 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
  • 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
  • 若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

数据库的视图:

    在实现上,数据库里面会创建一个视图,访问程度时候以视图的逻辑结果为准;

    在 “可重复度” 隔离级别下, 这个视图是在事务启动时创建的,整个事务存在期间都用这个视图,事务结束后新的语句执行时会给予最近的数据创建一个新的视图
    在 “读提交” 隔离级别下,这个视图是在每个 SQL 语句来时执行时的时候创建的。因此每个语句在执行时会给予最新的数据创建一个新的视图
    “读未提交” 隔离级别下直接返回记录的最新值,没有视图概念
    而 “串行化” 隔离级别下直接用加锁的方式避免并行访问;

事务隔离的实现

​     在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值;就是说,除了记录变更记录,还会记录一条变更相反的回滚操作记录,前者记录在redo log,后者记录在undo log ( **回滚日志,也就是rollback,记录的是更新的逆过程,如果你执行了insert语句,那么undo log记录的就是delete,以此类推 **)。

    假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。
在这里插入图片描述

     当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的视图 (read-view)。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于视图 A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到

​     同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

​     在可重复读隔离级别中,表中的数据其实已经改变,在前面的视图里,需要查找某条记录时,是通过取当前数据,再取视图对应的回滚段回滚到该视图的值。

那么回滚日志什么时候删除?

​     在不需要的时候!在系统判断当没有事务再需要用到这些回滚日志时,回滚日志会被删除。(评判标准是,当前系统里没有比这个回滚日志更早的视图的时候,就是说这个数据不会再有谁驱使他回滚了! 请看上面图下面的那句话,[对于视图 A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到],若没有像视图A这样比回滚日志更早的视图时,回滚日志会被删除!)

为什么建议尽量不要用长事务?

​     长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间(就是说!长事务导致对应的事务视图长时间存在,那么对应的回滚日志也是会一直存在的,占内存!)。

​ 长事务除了对回滚段的影响,还占用锁资源!

事务的启动方式

​     如前面所述,长事务有这些潜在风险,我当然是建议你尽量避免。其实很多时候业务开发同学并不是有意使用长事务,通常是由于误用所致。MySQL 的事务启动方式有以下几种:

  • 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback

  • set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接

    ​     有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。
    ​     因此,我会建议你总是使用 set autocommit=1, 通过显式语句
    的方式来启动事务。

    ​     在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。你可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。

事务的启动时机:

  • 第一种启动方式:是在执行第一个快照语句时创建的;
  • 第二种启动方式:是在执行 start transaction with consistent snapshot时创建的。

一致性视图没有物理结构,作用是事务执行期间用来定义 ” 我能看到什么数据“

两种操作的 “ 读 ”!

查询逻辑:

一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

    版本未提交,不可见;

    版本已提交,但是是在视图创建后提交的,不可见;

    版本已提交,而且是在视图创建前提交的,可见。

    一致性读 :虽然这一行数据被修改过,但是事务 A 不论在什么时候查询,看到这行数据的结果都是一样的。

更新逻辑:

    更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)

以上是关于MySQL———事务和事务的隔离性的主要内容,如果未能解决你的问题,请参考以下文章

MySQL———事务和事务的隔离性

MySQL———事务和事务的隔离性

MySQL 事务四大特性和事务隔离级别

mysql的事务,隔离级别和锁

MySQL:事务的隔离性

MySQL 事务管理