MySQL InnoDB如何实现四种事务的隔离级别?

InnoDB isolation level

Posted by 107 Blog on Wednesday, January 3, 2018

目录

首先什么是数据库事务?

事务就是一组原子性的SQL查询或者说是一个独立的工作单元,简单的说,就是一个事务内的语句,要么全部执行成功,要么全部执行失败

那什么是事务的隔离性?

这得先从事务的ACID概念说起,一个运行良好的事务处理系统,必须具备这些特征 : 原子性(A),一致性(C),隔离性(I),持久性(D)

事务ACID特性,其中I代表隔离性(Isolation)。

隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。

但是,不同的隔离级别(Isolation Level),都规定了一个事务中所做的修改,哪些是在事务内和事务间是可见的,哪些是不可见的。

SQL标准中定义了四种事务隔离级别:

  • READ UNCOMMITTED(读未提交)也称为 Dirty Read(脏读)

这种事务隔离级别下,select语句不加锁。可能读取到不一致的数据,即“读脏”。 这是并发最高,一致性最差的隔离级别。在这个 READ UNCOMMITTED 级别,事务中的修改,即使没有提交,对其他事务也都是可见的。 事务可以读取未提交的数据,这个级别一般会导致很多问题,一般很少使用,未满足事务的隔离性。

  • READ COMMITTED (读提交) , nonrepeatable read (不可重复读)

事务在开始到结束之前,所做的任何修改对其他事务都是不可见的。 只能看见已提交的事务所做的修改。 这个级别有时候也叫做,不可重复读 nonrepeatable read , 因为两次执行同样的查询,可能会得到不一样的结果

  • REPEATABLE READ (可重复读),MySQL默认的事务隔离级别

REPEATABLE READ 解决了脏读的问题,该级别保证了同一个事务中,多次读取同样记录的结果是一致的。 但是理论上,REPEATABLE READ 隔离级别,还是无法解决另外一个 【幻读( phantom read)】 的问题。

所谓幻读,指的是 当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行(phantom row)。 innodb和xtradb 存储引擎,通过 多版本并发控制(MVCC,multiversion concurrency control)解决了幻读的问题

  • SERIALIZABLE(可串行化)

SERIALIZABLE 是最高的隔离级别。它通过强制事务 串行执行。避免了前面说的 幻读 问题。简单说,SERIALIZABLE 会在读取每一行数据上都加锁,所以可能导致大量超时和锁争用的问题。这是一致性最好的,但并发性最差的隔离级别。 实际应用中很少用到这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑使用这个级别。

以上的,不同事务的隔离级别,实际上是一致性与并发性的一个权衡与折衷。

上面说了这么多理论,那一个事务怎么会干扰其他事务呢?

咱们举例子来说明,假设有InnoDB表:

t(id PK, name);

表中有三条记录:

1. julia wong
2. senven wong
3. jack ma
  • case 1

事务A,先执行,处于未提交的状态:

insert into t values(4, 'eric wong');

事务B,后执行,也未提交:

select * from t;

如果事务B能够读取到(4, wangwu)这条记录,事务A就对事务B产生了影响,这个影响叫做 “脏读”,读到了未提交事务操作的记录。

  • case 2

事务A,先执行:

select * from t where id=1;

结果集为:

1. julia wong

事务B,后执行,并且提交:

update t set name=xxoo where id=1;  commit;

事务A,再次执行相同的查询:

select * from t where id=1;

结果集为:

1.  xxoo

这次是已提交事务B对事务A产生的影响,这个影响叫做 “不可重复读”,一个事务内相同的查询,得到了不同的结果。

  • case 3

事务A,先执行:

select * from t where id>3;

结果集为: NULL

事务B,后执行,并且提交:

insert into t values(4, wangwu);
commit;

事务A,首次查询了id>3的结果为NULL,于是想插入一条为4的记录:

insert into t values(4, xxoo);

结果集为: Error : duplicate key!

事务A的内心OS是:你TM在逗我,查了id>3为空集,insert id=4告诉我PK冲突?

这次是已提交事务B对事务A产生的影响,这个影响叫做 “幻读”。 就好象发生了幻觉一样。

可以看到,并发的事务可能导致其他事务: 读脏不可重复读幻读

InnoDB怎么实现四种隔离级别?

InnoDB使用不同的 锁策略(Locking Strategy) 来实现不同的隔离级别。

  • 一,读未提交(Read Uncommitted)

这种事务隔离级别下,select语句不加锁。官方的说法是 SELECT statements are performed in a nonlocking fashion. 此时,可能读取到不一致的数据,即“读脏”。这是并发最高,一致性最差的隔离级别。

  • 二,串行化(Serializable)

这种事务的隔离级别下,所有select语句都会被隐式的转化为select … in share mode.

这可能导致,如果有未提交的事务正在修改某些行,所有读取这些行的select都会被阻塞住。

官方的说法是 To force a plain SELECT to block if other transactions have modified the selected rows.

这是一致性最好的,但并发性最差的隔离级别。

在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别。

  • 三,可重复读(Repeated Read, RR)

这是InnoDB默认的隔离级别,在RR下:

(1) 普 通 的 select 使 用 快 照 读 (snapshot read) , 这 是 一 种 不 加 锁 的 一 致 性 读 (Consistent Nonlocking Read),底层使用MVCC(多版本并发控制)来实现。

(2) 加锁的select(select … in share mode / select … for update), update, delete等语句,它们的锁,依赖于它们是否在唯一索引(unique index)上使用了唯一的查询条件(unique search condition),或者范围查询条件(range‑type search condition)

在唯一索引上使用唯一的查询条件,会使用记录锁(record lock),而不会封锁记录之间的间隔,即不会使用间隙锁(gap lock)与临键锁(next‑key lock)

范围查询条件,会使用间隙锁与临键锁,锁住索引记录之间的范围,避免范围间插入记录,以避免产生幻影行记录,以及避免不可重复的读

  • 四,读提交(Read Committed, RC)

这是互联网最常用的隔离级别,在RC下:

(1) 普通读是快照读;

(2) 加锁的select, update, delete等语句,除了在外键约束检查(foreign‑key constraint checking)以及重复键检查(duplicate‑key checking)时会封锁区间,其他时刻都只使用记录锁;此时,其他事务的插入依然可以执行,就可能导致,读取到幻影记录