把学习当成一种习惯
选择往往大于努力,越努力越幸运

InnoDB的隔离性(一)

直奔主题,InnoDB存储引擎有四种隔离级别 : 本文实践演示只演示前三个,重点是读提交和可重复读.

  • 读未提交(Read Uncommitted):可以读取到其他事务未提交的数据;也就是说其他事务对对数据行R进行更新或删除操作,即使没有提交,当前事务也可以读到数据行R的状态.该隔离级别会出现脏读、幻读、不可重复读.
  • 读提交(Read Committed,RC):只要其他事务提交了,当前事务就可以读取到数据;也就是说当前事务只能读取到其他事务已提交的数据.该隔离级别解决了脏读,但还存在幻读、不可重复读.
  • 可重复读(Repeatable Read,RR):其他事务提交了,当前事务也不能读取到其他事务已提交的数据,但是当前的事务进行更新或删除操作的时候,会进行当前读(读取最新数据,即其他事务已提交的数据).该隔离级别解决了脏读、不可重复读,但对于普通的SELECT操作还存在幻读.可重复读是MySQL的默认的隔离级别.
  • 串行化(Serializable):最高隔离级别,解决了脏读、幻读、不可重复读.但是执行效率、性能、吞吐量低.

传统隔离级别的实现方式 --- 锁(LBCC)

  传统的隔离级别方式是基于锁的并发控制实现.我们知道,对数据的操作要么是读操作或者是写操作;
  锁的兼容性分类可以分为读锁、写锁,读操作是加读锁(S锁),又叫共享锁;写操作是加写锁(X锁),又叫排他锁;
  读锁和写锁存在读读、读写、写读和写写,这四种"竞争"关系,要么互斥、要么就不互斥 : 不同隔离级别有不同实现,例如在 RC和RR 的隔离级别下,实现了读写不互斥;也就是说当前事务在对数据行R进行读操作的时候,其他事务对数据行R的写操作同样不阻塞的.
  锁的粒度又可以分为表锁、行锁、页锁,锁的粒度越小,越可以提高并发度,但开销较高,并且要实现的复杂度也增加了,甚至会出现死锁的现象.行锁的粒度明显小于表锁和页锁,InnoDB存储引擎是支持行锁的,而MyIsam只支持表锁.

四种隔离级别的加锁实现策略 --- 这里只是单单锁的实现,并没有MVCC

  1. 读未提交(Read Uncommitted):事务进行读操作的时候不加S锁,而写操作的时候加X锁.
  • 当前事务对数据行R进行读操作并不阻塞其他事务对数据行R的读操作和写操作;
  • 当前事务对数据行R写操作会阻塞其他事务对数据行R的写操作,但不会阻塞对数据行R的读操作;
  • 读读、读写、写读不互斥,写写互斥,即使当前事务对数据行R进行了写操作没提交,其他事务也可以读到未提交的对数据行R.
  • 存在脏读、幻读、不可重复读问题
  1. 读提交(Read Committed,RC):写操作加持有X锁,对读操作加 "临时持有S锁".
  • 当前事务对数据行R读操作不阻塞其他事务对数据行R的读操作和写操作(读操作临时持有S锁,读完就释放临时S锁,其他事务才可以对数据行R进行写操作);
  • 当前事务对数据行R写操作会阻塞其他事务对数据行R的写操作和读操作(写操作持有X锁,写完后提交事务就释放X锁,其他事务才可以对数据行R进行读操作或写操作);
  • 读读、读写不互斥,写读、写写互斥,即每次都可以读到已提交的数据 :
  • 对数据行R读操作的时候进行加临时S锁,这个临时S锁读完后就释放了(这里不是事务提交才释放),其他事务就可以对数据行R进行写操作了,所以可以说读读、读写是不互斥的;
  • 对数据行R写操作的时候获取X锁,当前事务对数据行R写完提交后其他事务才可以对数据行R进行读操作或者写操作(这里是事务在写操作提交后才释放X锁),所以写读、写写是互斥的.
  • 不可重复读 : 在RC隔离级别下,存在读写不互斥,即在一个事务中对数据行R进行的读操作,会加临时S锁,读完后释放这个临时S锁后其他事务就可以对数据行R进行写操作(并且更新或删除数据行R后提交事务),如果当前事务再次进行读数据行R的操作,就可能读到不一样的记录,这就是不可重复读了.
  • 解决了脏读,存在幻读、不可重复读问题
  1. 可重复读(Repeatable Read,RR):写操作加持有X锁,读操作加持有S锁;
  • 当前事务对数据行R进行读操作并不阻塞其他事务对数据行R的读操作,但是会阻塞对数据行R的写操作;
  • 当前事务对数据行R进行写操作的时候会阻塞其他事务对数据行R的写操作或读操作;
  • 读读不互斥、写读、读写、写写互斥;
  • 当前事务对数据行R进行读操作的时候将持有S锁,直到当前事务提交释放S锁后其他事务才可以对数据行R进行写操作;
  • 当前事务对数据行R进行写操作的时候持有X锁,直到当前事务提交释放X锁后其他事务才可以对数据行R进行写操作和读操作;
  • 解决了脏读、不可重复读问题,存在幻读
  1. 串行化(Serializable): 为了解决RR隔离级别的幻读问题.
  • 解决了所有并发问题,但是并发度低

什么是脏读、不可重复读、幻读

  • 脏读: 在读未提交隔离级别情况下,当前事务可读取到其他未提交事务的数据,就是脏读.
  • 不可重复读: 在RC隔离级别的情况下,读写不互斥,即一个事务中的多次读操作可能会出现不同的结果,这就是不可重复读.
  • 幻读: 在读未提交、RC、RR隔离级别的情况下,都存在幻读,这三种隔离级别锁的粒度都是行锁,因为行锁不能阻止其他事务的插入操作,所以幻读只跟Insert操作有关,那什么是幻读呢,这里不好描述,下文实践起来你就知道了.

二阶段锁协议

  四种隔离级别的实现是基于不同的用锁策略实现的,这四种不同的加锁策略实际上又称为封锁协议.

  • 读未提交 : 一级封锁协议,只在写操作上加X锁.
  • 读提交 : 二级封锁协议,在事务读操作的时候加临时S锁,读完后就释放临时S锁;在事务写操作的时候加X锁,事务提交后才释放X锁.
  • 可重复读 : 三级封锁协议,又称二阶段锁协议,即事务进行读操作的时候,会持有S锁,直到提交当前事务才释放S锁,当前,其他读操作事务也不会阻塞,读读不互斥、读写互斥;事务进行写操作的时候,会持有X锁,直到提交当前事务才释放X锁,当前写读、写写互斥.
  • 串行 : 四级封锁协议.

  在二阶段锁协议中规定,加锁阶段只允许加锁,不允许解锁:而解锁阶段只允许解锁,不允许加锁.这种方式虽然无法避免死锁,但是二阶段锁协议可以保证事务的并发调度是串行化的.

实践是检验真理的唯一标准

// 创建 t8 表
CREATE TABLE `t8` (
	`id` INT ( 11 ) NOT NULL,
	`c` INT ( 11 ) DEFAULT NULL,
	`d` INT ( 11 ) DEFAULT NULL,
	PRIMARY KEY ( `id` ),
KEY `c` ( `c` )) ENGINE = INNODB;

// 插入数据
INSERT INTO t8 VAKUES(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);

读未提交 --- Read Uncommitted

// 设置隔离级别为 读未提交 
// session:设置当前会话的隔离级别
// global:设置当前系统的隔离级别
set global transaction isolation level read uncommitted;
set session transaction isolation level read uncommitted;
// 查看当前隔离级别
select @@transaction_isolation;

开启两个事务,分别为事务1、事务2:确保两个事务都是 读未提交 隔离级别,如下图:

验证一 : 事务可以读取到未提交的数据,事务1 可读取到 事务2 未提交的数据,下图执行顺序为T1 T2 T3

验证二 : 写操作有加X锁,只阻塞写操作不阻塞读操作,下图执行顺序为T1 T2

验证三 : 读操作没有加S锁,不阻塞读操作和写操作,下图执行顺序为T1 T2 T3 T4

读提交 --- Read Committed

// 设置隔离级别为 读提交 
// session:设置当前会话的隔离级别
// global:设置当前系统的隔离级别
set global transaction isolation level read committed;
set session transaction isolation level read committed;
// 查看当前隔离级别
select @@transaction_isolation;

开启三个事务,分别为事务1、事务2、事务3:确保三个事务都是 读提交 隔离级别,如下图:

验证一 : 事务只可以读取到其他事务已提交的数据,即事务1只能读取到事务3提交的数据,并不能读取到事务2未提交的数据,下图执行顺序为T1 T2 T3 T4 T5 T6 T7

开启两个事务,分别为事务1、事务2,确保两个事务都是 读提交 隔离级别
验证二 : 出现不可重复读现象,也就是读写不互斥出现的不可重复读现象,下图执行顺序为T1 T2 T3 T4 T5

验证三 : 写读互斥,也就是事务1写操作持有X锁,事务2读操作会阻塞,需要事务1提交事务释放X锁后才可以读操作,因为为了保证不出现脏读,必须等待写操作提交之后才能读操作,下图执行顺序T1 T2 T3 T4

验证三疑问解答 : 原本验证的是,在RC隔离级别下,写读是互斥的,但是在上图中,我们可以看到T2时刻是可以进行读操作的(并没有阻塞),说明RC隔离级别下的写读并不互斥,是上面知识点错了吗?其实不是,在RC隔离级别下,InnoDB存储引擎不知单单使用了锁的技术来解决并发的问题,还使用了MVCC多版本并发控制来实现写读、读写不互斥.在RC隔离级别下,原本为了保证不出现脏读现象,必须等待写操作提交之后才能读操作,但是这样影响了并发度,所以InnoDB存储引擎引入了MVCC技术,即使你写操作,我也可以进行读操作,互不干扰,提高了并发度;现在,写写互斥,读读、读写、写读不互斥,但是读写不互斥,会出现不可重复读现象.

验证四 : 是否存在幻读现象?下图执行顺序T1 T2 T3,幻读只跟Insert有关

由验证四可知,Read Committed存在幻读

可重复读 --- Repeatable Read

// 设置隔离级别为 可重复读 
// session:设置当前会话的隔离级别
// global:设置当前系统的隔离级别
set global transaction isolation level repeatable read;
set session transaction isolation level repeatable read;
// 查看当前隔离级别
select @@transaction_isolation;

开启两个事务,分别为事务1、事务2:确保两个事务都是 可重复读 隔离级别,如下图:

验证一 : 读写互斥,在RR隔离级别下,为了解决 RC 隔离级别下的 不可重复读 问题;在RC隔离级别下,读操作的 "临时S锁"是读完就释放临时S锁后就可以写操作(不是提交事务才释放临时S锁),即同一事务会出现读取数据不一样的现象(不可重复读);而RR隔离级别下,读操作持有S锁在提交事务释放S锁后才可进行写操作(没有不可重复读现象).下图执行顺序T1 T2 T3

RC 隔离级别一样,写读互斥也是为了保证不出现脏读现象,这里就不验证了,在RR隔离级别下,也是写读不互斥,与我们上面锁的实现分析相反.
验证一疑问解答 : 原本验证的是,在 R R隔离级别下,读写、写读都是互斥的,但是上面的验证中,我们可以看到读写、写读是不互斥的,为什么?因为在 RR 隔离级别下,InnoDB存储引擎也不只单单使用了锁的技术来解决并发的问题,同样使用了MVCC多版本并发控制来实现读写、写读不互斥.在 RR 隔离级别下,InnoDB存储引擎也使用了MVCC技术,即使当前事务在对行数据R进行写操作,其他事务也可以对行数据R进行读操作;再或者进行读操作,也可以进行写操作,两者互不干扰,提高了并发度;现在,写写互斥,读读、读写、写读不互斥

验证二 : 是否存在幻读现象?下图执行顺序T1 T2 T3,幻读只跟insert有关

由验证二可知,Repeatable Read存在幻读

小结

  重点说一下 RC 和 RR 的区别 : RC : 解决了脏读,存在不可重复读、幻读问题; RR : 解决了脏读、不可重复读,对于普通的SELECT,存在幻读问题.

  • 在 RC 隔离级别下, 读写不互斥,衍生出了不可重复读问题;
  • 在 RR 隔离级别下,读写互斥,解决了不可重复读问题;
  • S锁与X锁互斥,可以解决了脏读、不可重复读现象;【PS : 这些S锁与X锁互斥的现象,是基于传统锁的并发控制来实现】
      基于传统锁的并发控制来实现,读写或写读互斥的情况下解决了脏读、不可重复读现象,但是并发度降低了.那么有什么办法既可以提高并发度又可以解决了脏读、不可重复读现象吗?答案就是InnoDB存储引擎引入的MVCC多版本并发控制,即可以实现读写、写读不互斥的情况下解决了脏读、不可重复读现象,提高了并发度.

多版本并发控制(MVCC)

  虽然数据库的四种隔离级别通过 锁(LBCC) 技术都可以实现,但是在RC\RC隔离级别下,它最大的问题是只实现了并发的读读,对于并发的读写还是冲突的,写时不能读,读时不能写,当读写操作都很频繁时,数据库的并发性将大大降低,针对这种场景,MVCC 技术应运而生.
  InnoDB存储引擎 通过 MVCC 实现了读写并行,但是在不同的隔离级别下,读的方式也是有所区别的.首先要特别指出的是,在 Read Uncommitted 隔离级别下,每次都是读取最新版本的数据行,所以并没有使用到 MVCC 的多版本,而 Serializable 隔离级别每次读取操作都会为记录加上读锁,也和 MVCC 不兼容,所以只有 RC 和 RR 这两个隔离级别才有使用 MVCC.
  MVCC并不是解决了幻读,而是实现了读写并行,提高了并发度.

结束语

  下一篇文章重点说明InnoDB存储引擎提供的各种锁以及MVCC的具体实现,还有RR隔离界别下的间隔锁(范围锁)、对于SELECT显示加锁解决了幻读的问题.
  希望看完这篇文章的你有所收获!


目录