谈Mysql隔离级别

最近看书和看各种文章被弄得特别晕,所以写下自己综合书籍《高性能Mysql》和官方文档的理解。


数据库的隔离级别通常分为四类

  1. 未提交读 (READ_UNCOMMITTED)
  2. 提交读 (READ_COMMITTED)
  3. 可重复读 (REPEATABLE_READ)
  4. 可串行化 (SERIALIZABLE_READ)

大多数数据库的默认隔离级别都是READ CPMMITTED。但是Mysql不是,Mysql默认的是REPEATABLE_READ可重复读。

1.未提交读

选用未提交读,事务的修改即使没有提交对其他事务也是可见的。当前事务对数据的CUD都对其他事务可见。
这种隔离级别下会产生脏读现象。

脏读指的是当前事务查询的数据是不可靠的, 可能是被其他未提交事务所更改的。是不满足ACID中的I隔离性的。

2.提交读(用到MVCC)

选用提交读,虽然解决了脏读现象,但是由于每次读取都是读取数据的最新快照,因此不可避免的会发生不可重复读现象。

不可重复读指的是同一事务前后查询的同一行数据可能由于另一事务的修改UPDATE提交导致数据不一致。这里的不同指的是同一主键索引下的数据不同。
因此与脏读的现象不同在于导致两者发生的外部事务是否已经提交

又因为其不启用间隙锁(GAP LOCK),因此无法避免的也会出现幻读现象。

幻读指的是同一事务前后两次查询,第二次查询数据集合中出现了第一次查询未出现的行(其他事务Insert新行或Update某行使其满足WHERE条件)。

3.可重复读(用到MVCC)

无论是否锁定SELECT,可重复读都会通过第一次读取时定下一份快照,在这次事务中只对这份快照进行CRUD,从而实现一致性。

若锁定了SELECT,可重复读还会通过查询条件(WHERE)去决定该采用什么锁,当查询条件指向唯一索引时,只会使用行级锁(S共享锁 OR X排他锁),而当查询条件只向一个范围时则会再额外使用下键锁next-key lock(由记录锁FOR UPDATE+间隙锁组合),防止Insert和Delete。

4.可串行化

开启Auto-commit则会默认为SELECT加上共享锁(LOCK IN SHARE MODE),即一定会为SELECT加锁,这样会导致很多多余的上锁释放操作,影响性能。

总结

脏读包括了不可重复读和幻读,但幻读和不可重复读不同,不可重复读针对同一行数据前后异变,而幻读则针对总体是否发生异变(出现新行,幻影行)。
不可重复读用MVCC多版本并发控制固定版本号解决,幻读使用next-key lock解决。

补充

MVCC

MVCC是一个概念,不同存储引擎实现方式不同,分有悲观版本控制和乐观版本控制,悲观版本控制会上锁,导致其他访问对象需要等待,而乐观版本控制则是检查
数据是否符合条件(当前版本是否一致,类似与CAS机制)。

InnoDB两种都有,乐观版本控制是通过为每行记录后面添加两列,一个保存行创建时间,一个保存行删除时间。(实际保存的是版本号)

在可重复读隔离级别下:

SELECT

  1. 版本号先于当前事务版本的行(事务前已存在或当前事务插入的)
  2. 行删除版本为定义或大于当前事务版本(保证这个事务版本下未被删除)

INSERT

  1. 为新插入行保存当前事务版本号为行版本号

DELETE

  1. 为删除的每一行的删除版本号设定为当前版本号

UPDATE

  1. 插入新行,新行版本号锁定为当前版本号,向旧行的删除版本号插入为当前版本号。

MVCC只在REPEATABLE_READREAD_COMMITTED下工作,因为READ_UNCOMMITTED只读最新行,
SERIALIZABLE_READ会默认加锁,采用悲观版本控制,使用MVCC多余。

参考文档 : Mysql官方文档之隔离级别

KAI Mysql