数据库事务隔离级别和传播级别详解

作为数据库系统的核心概念,ACID几乎是一个耳熟能详的缩写。 但是,在日常开发中,大部分情况下事务使用不多,很多人也就逐渐忘了这几个特性是什么,以及更加重要的,为什么要有这几个隔离级别、传播级别,如果没有他们,会引起什么问题,以及,在数据库中,他们的通常实现是什么。 通过一定的资料翻阅,本文尝试
对这几个概念做一些基本介绍

Isolation 事务隔离级别

为什么有?

如果两个事务没有操作相同的数据(或者说叫做修改某行Row), 那么他们可以安全的并行操作。但是当两个事务都打算修改某个数据,或者一个事务读取另外一个事务修改的数据的时候,彼此对于数据的查找或者更新就发生了冲突,这也就是并发编程中的race condition,竞态条件。
一般来说,并发BUG很难调试 - 至少复现是的机率的随机的,而且,在一个大型应用中,并发的问题也很难考量-你不知道有哪些代码在访问数据库。 对于数据库操作来说,多个并发用户都可能在修改数据库中的数据,这使得相关的访问数据库代码在处理竟态条件时变得异常麻烦。这可以参考Java中的volatile变量的效果来考虑这个问题。
所以数据库事务有几个隔离级别的设计初衷就类似于Java中的Synchronizedvolatile,前者可以绝对的保证不发生竞态条件以及可见性问题,相应的就是性能上的下降,后者就只能保证可见性。 ,至于不同的数据库支持的具体级别则是属于数据库设计上的考量。
由于这个原因,数据库系统为了减轻开发者处理并发操作数据库中的难度,提供了一个叫做transaction isolation的功能。
理论上说,transaction isolation极大的降低了并发问题的处理难度,极端上,serializable级别使得数据库保证事务像线性一样运行-也就是没有并发问题。但是,技术上很多情况下都是没有完美解的,transaction isolation也是一样的 –serializable 级别有着较大的性能开销,对于很多数据库来说是不可接受的,所以数据库系统也提供了很多稍微(weak isolation)一级的transaction isolation这些一级的transaction isolation解决了部分并发问题,但不是全部。 注意这个描述,他们仅仅解决了部分并发问题,所以使用不当仍然可能导致并发问题。
下面分别介绍这几个weak isolation(nonserializable)的实际使用,竟态条件的发生情况,以便于可以正确使用它们。

分类

- READ_COMMIMTED

基本定义
最基础的事务隔离级别,它保证了两方面:

  • 当从数据库读数据的时候,仅能读到已经提交commit的数据(比如其他事务中修改了但是没有提交的数据就看不到)。 这个也叫做没有dirty read
  • 当写数据库到库中的时候,仅能覆盖已经提交commit的数据。 这个也叫做没有dirty write

事务隔离级别为READ_COMMIMTED的事务必须不能发生脏读(dirty read),也就是对于如下的事务情况,用户1在事务中设置x=3, 同时用户2在另外一个事务中读取数据时,看到的x仍必须是旧的数据2. 对于读取y也是一样的道理,当且仅当事务提交后,用户2才能看到x为3,y为4。
非脏读
在这些情况下防止脏读是非常有意义的:

  • 一个事务在做多个更新操作,另外的事务不应该看到这个“未完成”的数据
  • 如果一个事务发生了异常,那么它可以回滚。 如果这时允许脏读,其他事务就可能看到一个已经“回滚”了的数据,必然会出现问题。

如果多个事务同时写相同的数据库数据,那么具体的写的顺序是未知的,但是可以确定的是后写入的一个数据会覆盖前面一个数据。 但是,如果前面一个写是处于没有提交的事务中的时候,第二次写是不是覆盖这个“未提交”的数据? 这就是脏写。 防止脏写通常情况下会延迟第二次写知道第一次已经提交。
脏写可能带来如下的并发问题:

  • 如果事务做多次更新操作,脏写就可能带来问题。考虑如下情况,一次商品购买操作需要两次数据库更新:更新订单表记录买家和商品;更细消息通知表买家和商品,在允许脏写的情况下,第一个买家(用户1)首先更新了订单表的,更新买家和商品记录,然后用户2覆盖了这个“未提交”的操作,用户2更新了自己的下单:更新订单表和消息通知表,然后用户1才来完成更新消息通知表的操作。 这时候,结果就是消息通知表认为该商品卖给了用户1,而订单表则认为是用户2.
    dirty write
  • read committed没有解决两个事务自增的冲突,防止脏写使得第二次的写在第一次事务提交之后才发生-但是结果仍然是不对的
    race condition in counter

还必须注意的是,由于不能脏读,所以对于一个事务中针对某个条件的两次查询,两次查询的数据很可能是不一致的。着也被称为non-repeatable read,如果读取的结果是多条数据,两次读取有多出数据,则称为phantom read

实现
READ_COMMITTED被广泛支持,它是Oracle 11g, PostgreSQL, SQL Server 2012的默认级别。注意,MySQL的默认级别不是这个,是REPEATABLE READ