之前发过一篇文章,简单了解 MySQL 中相关的锁,里面提到了,如果我们使用的 MySQL 存储引擎为 InnoDB
,并且其事务隔离级别是 RR
可重复读的话,是可以避免幻读的。
但是没想到,都 1202
年了都还有人杠,说 InnoDB 的 RR 隔离级别下会出现幻读,只能依靠 gap 和 next-key 这两个锁来防止幻读
,最开始我还以为是他真的不知道这个点,就跟他聊,最后聊下来发现,发现是在钻牛角尖。
这个在下面讲到 可重复读 的隔离级别时会讲。
本来我觉得事务隔离级别这玩意儿太简单没啥可讲的,但是经过了上面这件事,我打算详细的把事务隔离给讲讲。接下来顺便就把 InnoDB
所有的事务隔离级别给搂一遍。
ACID
在聊事务隔离级别之前,我们需要知道 ACID 模型。
分别代表:
Atomicity 原子性 Consistency 一致性 Isolation 隔离型 Durability 持久性
原子性,代表 InnoDB 事务中,所有的操作要么全部成功,要么全部失败,不会处于某个中间状态。说的更通俗一点,如果事务 A 失败,其所做的所有的更改应该全部回滚。
一致性,主要是保护数据的一致性,防止由于数据库的崩溃而导致的数据一致性问题。举个例子,我们更新 MySQL 的数据,更新的数据会先到 InnoDB 的 Buffer Pool 中,如果此时 MySQL 所在的机器突然意外重启了,如果 InnoDB 没有崩溃恢复机制,之前更新的数据就会丢失,数据的一致性问题就出现了。
很多其他的博客写的是事务开要始前后,数据的完整性没有被破坏。我表示看了根本看不懂,太抽象了。
隔离性,主要是指事务之间的隔离,再具体一点,就是我们本篇文章要讨论的事务隔离级别了。
持久性,主要是指我们新增或者删除了某些数据,一旦成功,这些操作应该需要被持久化到磁盘上去。
ACID 模型可以理解成数据库的设计范式,主要关注点在数据数据、及其本身的可靠性。而 MySQL 中的 InnoDB 就完全遵守 ACID 模型,并且在存储引擎层就支持数据一致性的校验和崩溃恢复的机制。
而 ACID 中的隔离型,就是我们这篇文章中讨论的重点。
事务隔离级别
有很多文章上来就直接介绍事务隔离级别的种类,这个种类啥意思,那个种类怎么用。但我认为应该先了解为什么需要事务隔离级别,以及事务隔离级别到底解决了什么问题,这才是关键。
我们知道 InnoDB 中同时会有多个事务对数据进行操作,举一些例子:
假如事务A需要查询 id=1
的数据,但是事务A查询完毕之后,事务B对id=1
的数据做了更新,那此时事务A再次执行查询,应该看到更新前的数据还是更新后的数据?或者还是上面那个例子,事务A读取了事务B的数据,但是如果事务B进行回滚了怎么办?事务A的数据不就变成了脏数据? 又或者事务A读取了 1-3点
的日程安排,有4条,但是事务A读取完成后事务B又向1-3
点这个时间段插入了一条新的安排,那么事务A如果再次读取,应该显示4条日程安排还是5条?
以上的这些问题,就需要事务隔离级别来回答了。其实以上的三种情况分别对应不可重复读、脏读和幻读。InnoDB 通过事务隔离级别分别的解决了上面的问题。所有的事务隔离级别如下:
READ UNCOMMITTED 读未提交 READ COMMITTED 读已提交 REPEATABLE READ 可重复读 SERIALIZABLE 串行化
InnoDB 默认的事务隔离级别为 REPEATABLE READ
。
读未提交
事务A读取了事务B还未提交的数据
如果事务B此时出错了进行了回滚,那么事务A读取到的数据就成为了脏数据,从而造成脏读。
如果事务B又更新事务A读取的数据,那么事务A再次读取,读取到了事务B修改的结果,这造成了不可重复读。
而如果事务B又新增了数据,事务A再次读取,会读取到事务B新增的数据,这造成了幻读。
所以总结来说,在读未提交这个隔离级别下,会造成以下的问题:
脏读 不可重复读 幻读
读已提交
事务A读取了事务B已经提交的数据
如果事务B更新了事务A读取到的数据,并且提交,那么当事务A再次进行读取,就会读取到其他事务的变更,就造成了不可重复读。
同理,如果事务B新增了数据并且提交,事务A再次进行读取时拿到了事务B刚刚提交的数据,这就造成了幻读。
所以总结来说,在读已提交的隔离级别下,会造成:
不可重复读 幻读
可重复读
事务A不会读取到事务B更新的数据,也不会读到事务B新增的数据
在可重复读场景下,不会出现脏读、不会出现不可重复读,可能会出现幻读。
无论事务B做了什么操作,事务A查询到的 id=1
的数据都是张三。
但是,在某些情况下,还是可能会出现 幻读。可重复读 只是在某些情况下会产生幻读,但绝对不是 InnoDB 无法避免幻读
。首先,InnoDB 在 RR 隔离级别下有很明确的解决幻读的方式,那就是——临键锁,一种组合了 gap 锁和记录锁的锁。
接下来举个例子来看在 RR 隔离级别下,什么情况会出现幻读,什么情况下不会出现幻读。首先是 可能会出现幻读。
SELECT * FROM `student` WHERE `id` > 1;
由于 InnoDB 有 MVCC 来进行多事务的并发,此时 SELECT
走的是快照读,不会加锁,所以无论插入多少 id > 1
的数据,在同一个事物内执行上述的 SQL 是不会出现幻读的。
但是如果显示的进行加锁,就会出现问题。
SELECT * FROM `student` WHERE `id` > 1 FOR UPDATE
为啥这样就会有问题呢?对 SELECT
显示的进行加锁之后,无论是加的共享锁还是排他锁,都会进行 当前读,而一旦执行了当前读,就能够读取到其他事物提交的 id > 1
的数据。
串行化
所以事务被强制的串行执行
这样从根本上就避免了并发的问题,但是这样会使得 MySQL 的性能下降。因为现在同一时间只能有一个事务在运行。
EOF
关于事务隔离级别就先介绍到这,之后有时间了就把 事务隔离级别 的底层原理给搂一遍。
本篇文章已放到我的 Github github.com/sh-blog 中,欢迎 Star。微信搜索关注【SH的全栈笔记】,回复【队列】获取MQ学习资料,包含基础概念解析和RocketMQ详细的源码解析,持续更新中。
如果你觉得这篇文章对你有帮助,还麻烦点个赞,关个注,分个享,留个言。