MySQL事务隔离原理(四种隔离级别的具体实现) 您所在的位置:网站首页 神豪是什么级别的人物 MySQL事务隔离原理(四种隔离级别的具体实现)

MySQL事务隔离原理(四种隔离级别的具体实现)

2024-07-16 14:47| 来源: 网络整理| 查看: 265

一、事务的定义

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

这里给出事务的一个比较正式的定义:

事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单元。

事务的开始与结束可以由用户显式控制。如果用户没有显式地定义事务,则由数据库管理系统默认规定自动划分事务。在SQL中,定义事务的语句一般有三条:

BEGIN TRANSACTION; COMMIT; ROLLBACK;

事务通常以BEGIN/START TRANSACTION开始,以COMMIT或ROLLBACK结束。COMMIT表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据库的更新写回到磁盘的物理数据库中去,事务正常结束。ROLLBACK表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态。这里的操作指对数据库的更新操作。

需要注意的是,BEGIN TRANSACTION命令并不代表事务的开始,在执行到它之后的第一个操作InnoDB表的语句,事务才真正启动,例如下面示例中,select * from xxx 才是事务的开始

begin; select * from xxx; commit; -- 或者 rollback;

如果我们想要马上启动一个事务,可以使用transaction with consistent snapshot。

另外,可以通过以下语句来查询当前有多少事务正在运行:

select * from information_schema.innodb_trx; 二、事务的四个特性

具体见:MySQL事务的四大特性

三、事务的并发问题

MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。那么在同时处理多个事务的时候,就可能出现脏读、不可重复读、幻读的问题。下面的例子说明了这些问题是如何发生的。

3.1 脏读

脏读指一个事务「读到」了另一个「未提交事务修改过的数据」。

脏读最大的问题就是可能会读到不存在的数据。比如在上图中,事务B的更新数据被事务A读取,但是事务B回滚了,更新数据全部还原,也就是说事务A刚刚读到的数据并没有存在于数据库中。从宏观来看,就是事务A读出了一条不存在的数据,这个问题是很严重的。

在这里插入图片描述

3.2 不可重复读

在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。

下图中事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致: 在这里插入图片描述

3.3 幻读

在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A修改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 在这里插入图片描述 注意不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

四、隔离性与隔离级别

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,SQL 标准提出了四种隔离级别来规避这些现象。

在谈隔离级别之前,有一点需要申明,我们设置的隔离级别越高,那么事务并发执行的效率就会越低。因此很多时候,我们都要在隔离级别与效率之间寻找一个平衡点。SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable ),其解释如下:

读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。读提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到。可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。这是MySQL InnoDB 引擎的默认隔离级别;串行化(serializable ):对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

下面用一个例子说明这几种隔离级别。假设数据表 T 中只有一列,其中一行的值为 1,下面是按照时间顺序执行两个事务的行为。

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

在这里插入图片描述

我们来看看在不同的隔离级别下,事务 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。 五、事务隔离级别的实现

这里我们展开说明“可重复读”。

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

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

在这里插入图片描述

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候。基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有 20GB,而回滚段有 200GB 的库。最终只好为了清理回滚段,重建整个库。除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。

5.1 读未提交

在此隔离级别下,事务之间可以读取彼此未提交的数据。但注意在所有写操作执行时都会加排它锁,那还怎么读未提交呢?该级别主要的特点是释放锁的时机与众不同:在执行完写操作后立即释放,而不像其他隔离级别在事务提交以后释放。因此极易出现脏读(不可重复读和幻读就更不用说了)

但该级别的并发性能也正因为锁释放得很早而变得很高,就连写写操作都很难产生锁竞争。

5.2 读提交

读提交将锁的释放时机延迟到事务提交之后,解决了脏读。但是,锁的释放时机延迟了,不仅写与写操作之间会产生锁竞争,在锁释放之前,也无法执行读操作,这对并发性产生了很大的影响。为了提高并发性,MySQL采用了一种名为MVCC的解决方案:无视当前持有锁的事务,读取最新的历史版本数据。

(这里很奇怪,别的事务运行期间不是会修改吗,这个修改不会被看到?) 因此,在读提交的级别下,我们每次执行select操作时都会通过MVCC获取当前数据的最新快照,不加任何锁,也无视任何锁(因为历史数据是构造出来的,身上不可能有锁),完美解决读写之间的并发问题,和读未提交的并发性能只差在写写操作上。

而为了进一步提升写写操作上的并发性能,该级别下不会使用间隙锁,无论什么查询都只会加行锁,而且在执行完WHERE条件筛选之后,会立即释放掉不符合条件的行锁。

但是,正因为对并发性能的极致追求或者说贪婪,该级别下还是遗留了不可重复读和幻读问题:

MVCC版本的生成时机: 是每次select时,这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读锁的范围: 因为没有间隙锁,这就意味着,如果我们在事务A中多次执行select * from user where age>18 and age


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有