事务隔离级别分为:未提交读(Read Uncommitted,RU),提交读(Read Committed,RC),可重复读(Repeatable Read,RR)和串行化(Serializable),其中 MySQL InnoDB存储引擎默认使用 RR 隔离级别。

不同隔离级别

  • RU:未提交读,两个不同事务 T1 和 T2 可以相互读取各自提交(Commit)以前的数据,显然这一隔离级别不符合事务特征中的“原子性”和“隔离性”原则,因此在 SQL 标准中不被推荐使用,但这种隔离级别下的事务的并发性能最好,因为 RU 级别没有对事务影响到的数据进行额外的加锁操作,但会产生“脏读”(Dirty Read)的问题;

  • RC:提交读,即单独每个事务在执行的过程中(commit 以前),只能读取其他事务提交以后产生的数据,这样造成的后果就是,如果 T1 事务执行中首先读取一部分数据,恰好后续T2事务对这部分数据做了修改(Update 或者 Delete)并提交,如果 T1 事务继续读取这部分数据,就会发现这部分数据已经和第一次读取的结果不一致,也就是产生了“不可重复读取”的问题,因此这一级别也被称为“不重复读”(Non Repeatable Read);

  • RR:可重复读,这一级别解决了 RC 级别中出现的“不可重读”的问题,InnoDB 中通过对事务可能影响到的数据加锁(行锁),避免数据在提交之前被其他事务修改,但有一点需要注意的是,这里的“锁”只能锁定被读取过的行,但无法限制其他事务对数据库的插入操作(Insert),也就是说,及时T1事务对其中 SELECT 使用过的 N 行数据加了锁,在提交T1事务前,T2 事务还是可能对这 N 条数据中插入其他行,这样就造成了读取过的行虽然没有修改,但是出现的新的行,这就是“幻读”(Phantom Read)问题。

按照 RR 的定义,这一级别通常是无法处理“幻读”的问题的,然而 MySQL InnoDB 引擎通过 MVCC (多版本并发控制)和 Gap Lock(间隙锁)有效的解决了幻读的问题,关于这一问题的细节内容十分复杂繁琐,后续文章再抽空整理总结。

  • Serializable:串行化执行,即所有事务都按照串行的方式,顺序执行,显然这样就避免了并发中可能出现的各种问题,但明显这种级别和并发处理的原则的背道而驰的,因此除非是对数据一致性要求十分苛刻的环境,一般情况都不被推荐使用。

隔离级别中可能出现的问题

首先理解几个不同的概念:

  • Dirty Read:脏读,指 T1 事务在执行中读取到 T2 事务没有提交的数据;

  • Non Repeatable Read:不可重读,指 T1 事务执行中,首先执行两次重复 SELECT 操作,在间隙中。另一事务 T2 对 T1 SELECT 获取到的数据做了修改(UpdateDelete)并提交(Commit),由于已提交的数据在这一级别是可读取的,因此 T1 数据再次执行相同 SELECT 操作,就会发现之前读取到的数据被莫名其妙的修改过,因此这一级别的数据不能被重复读取;

-Phantom Read:幻读,幻读的概念和不重复读容易混淆,查阅了不少资料,似乎都讲得不是太明白,美团技术博客的文章《Innodb中的事务隔离级别和锁的关系》中提到,幻读强调插入操作(Insert)对读取的影响,不重复读产生的原因是数据被其他的事务修改(UpdateDelete 操作都被认为是修改操作)

最后总结一下各个级别可能产生的问题:

  • RU:脏读,不可重读,幻读
  • RC:不可重读,幻读
  • RR:幻读
  • Serializable:无