1. 介绍

事务具备ACID的特性,本次主要回顾下事务隔离性的理解。 这个概念网上讨论的也很多,甚至自己之前也写过一篇文章总结,但是以前的理解还是不够到位,这次再重新回顾下。

2. 定义多种隔离性的原因

事务隔离的不同程度,实际上是通过不同的上锁要求来控制事务并发的能力。所以谈论事务隔离的时候,我们首先要想到“锁”和“并发”。上锁最严格,则意味着事务之间隔离性性越好,效率最低,但是自然也是最安全的。 上锁要求宽松,不仅减少了锁的开销,也提升了事务并发的能力,但是也增大了死锁的概率,以及并发带来的不可重复度、脏读和幻读等问题。

3. 基于锁的事务隔离级别

一般我们说的事务隔离级别都是基于锁的实现方式。这说明也存在无锁的事务并发控制,这是另外的一种事务隔离性,后面会说。下面讨论的四种隔离级别是由ANSI/ISO SQL定义的标准。

3.1 四种隔离级别

四种隔离级别从隔离程度高到程度低分别为:
可序列化(隔离级别最高)->可重复度->读已提交-读未提交

隔离级别越高,说明事务之间隔离性越好,并发程度越低,由于并发产生的问题越少。例如隔离级别最高的可序列化,不会导致一些并发问题。

3.2 理解隔离级别的要点——锁

我们知道DB中存在读锁、写锁。理解基于锁实现的隔离级别的差别,要从不同隔离级别上锁的时间和释放的时机来区分差别。

PS: 下面会提到一个范围锁(gap lock或者range lock),在innodb中,范围锁可以等价于在一个范围上加读锁。 具体可以参考MYSQL官方文档InnoDB Locking

3.3 基于锁的事务隔离在并发时存在的问题

较低的隔离级别会带来以下的问题:

  1. 脏读(Dirty Read):一个事务单元A的读操作读到了另一个未提交的事务单元B写入的数据。
  2. 不可重复读取(Non-Repeatable Read): 一个事务单元中两次读同一行数据,这两次读到的数据不一样。和脏读类似。
  3. 幻读(Phantom Read):幻读主要指1个事务单元针对同一个范围的两次SELECT结果不同。存在不可重复读的问题一般也必然存在幻读问题了。区别不可重复读,幻读主要指范围select时由于隔离性原因造成的执行结果不同。详情见不可重复读的隔离级别说明。
  4. 更新丢失:两个事务单元都同时更新一行数据,一个事务单元对数据的更新把另一个事务单元对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。

举例

下面的例子中,两个事务,事务1执行语句1。接着,事务2执行语句2并且提交,最后事务1再执行语句1. 查询使用如下的数据表。

1.脏读

2.不可重复读

在基于锁的并发控制中“不可重复读”现象发生在当执行SELECT 操作时没有获得读锁或者SELECT操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突的事务回滚也会发生“不可重复读”现象。

3.幻读

“幻读”是不可重复读的一种特殊场景:当事务1两次执行SELECT ... WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句

4. 四种隔离级别详细说明

4.1 可序列化

要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在SELECT 的查询中使用一个“WHERE”子句来描述一个范围时应该获得一个“范围锁”(range-locks)。这种机制可以避免“幻影读”(phantom reads)

可序列化不同事务之间完全隔离,针对同一行记录的读写不会并行,不会有隔离级别低带来的问题。

PS:可序列化(Serializable)隔离级别不等同于可串行化(Serializable)。可串行化调度是避免以上三种现象的必要条件,但不是充分条件。

小总结:可序列化,一个事务内读写锁全部保持到事务结束,并且存在范围锁

4.2 可重复读

要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁”,因此可能会发生“幻影读”。和可序列化的差别就是没有范围锁。

没有范围锁,意味着范围查询的时候,别的事务仍然可以对当前事务查询的范围进行写入,那么针对同一个范围的两次select结果会不同。

小总结:可重复读,一个事务内读写锁全部保持到事务结束,缺少范围锁,会有幻读

4.3 读已提交

对选定对象的写锁一直保持到事务结束,但是读锁在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁”。

读已提交隔离级别中,读锁的保持时间缩短了。假设事务A中读锁一旦释放,另外个事务B就可以对事务A涉及的一些记录做修改,当事务A中再次读取的时候,就可能出现不可重复读的问题。详情见3.3节的不可重复读。

小总结:读已提交,读锁SELECT后马上释放,没有范围锁,写锁一直保持到事务结束,会不可重复读,当然也包括幻读

4.4 读未提交

小总结:读锁、写锁,处理好一条记录均马上释放,还没有范围锁。事务间完全没隔离,所有问题均可能出现

5. 事务级别总结

5.1 隔离级别vs读现象

隔离级别 脏读 不可重复读 幻读
读未提交 YES YES YES
读已提交 NO YES YES
可重复读 NO NO YES
可序列化 NO NO NO

5.2 隔离级别和锁持续时间的关系

C:表示锁会持续到事务提交
S: 表示锁持续到当前语句执行完毕
如果锁在语句执行完毕就释放则另外一个事务就可以在这个事务提交前修改锁定的数据,从而造成混乱。

隔离级别 写操作 读操作 范围操作
读未提交 S S S
读已提交 C S S
可重复读 C C S
可序列化 C C C

5.3 写在最后

搞明白事务隔离性,就是该清楚三件事的关系:

有哪些锁,在事务中持续时间怎样->分别对应哪些隔离级别->不同的锁持续时间引发了那些低隔离性带来的问题

6. 快照隔离(不基于锁的事务并发实现)

快照隔离级别主要就是指的MVCC,利用多版本的方式来控制并发。这种是一种乐观的并发控制策略。大家都喜欢说乐观锁,我觉得这词容易误导,乐观锁实际上是一种并发控制的思路,很多时候以这种思路的时候去实现,都是无锁的,所以叫乐观锁很有歧义,哈哈。

快照隔离是比较不错的事务并发控制手段,大量冲突的时候的缺点也就是产生较多的版本控制文件,以及控制文件寻址的开销,总体上取舍还是比较平衡的。

关于快照隔离,后续再好好写一篇文章分析下。

参考资料:维基百科-事务隔离