我发现有不少做应用的技术人员对于数据库的一些常识概念其实是理解不清的,这就导致了他们设计的应用中往往充斥了数据访问的相关错误却浑然不知。所以其实我还是比较乐意普及一些数据库常识的。
本文就是我在 segmentfault.com 上对 “怎么理解SQL的四个事务隔离级别?“这个问题的回答:
我认为要理解事务隔离级别,就必须先理解在并发事务场景下隔离的重要性。因此,对于并发读事务之间数据可见性的三种现象需要首先理解清楚。
下面我将以下表为例直观说明这三种现象。
并发读的三种现象
脏读
这种现象出现在事物间的隔离级别最差的场景下,写事务对一个元组的更新尚未提交时就被另一个事务读到了。如果在一个业务应用中,写事务后面没提交而是回滚了,那么可以预见这个读事务读到的这个未提交的更新在某些业务场景下可能会带来一些困扰。
不可重复读
这种现象比上面的脏读“好”一点,只有当写事务提交后,这个更新才会被读事务读取到。但是考虑到如上图所示,在同一个事务中的不同时间点意图读取同一个元组却读到了不同的数据,在某些业务场景下可能也会带来一些困扰。
划重点: “脏读”和”不可重复读”这两个现象针对的是对于表中的同一个逻辑上的元组而言.引发”脏读”和”不可重复读”这两个现象的写事务的操作通常是UPDATE
幻读
幻读这一现象针对的已不是表中的单一元组而言,而是指读事务在对表中的某个范围多个元组而言的一种现象,引发幻读的写事务对应的操作通常是INSERT或DELETE。如下图所示:
在同一个读事务中,对于同一个过滤条件查询出了不同的结果集,这在某些业务场景下也有可能带来一定的困扰。
补充说明
上面这些就是关于”脏读”,”不可重复读”以及”幻读”这三个现象的介绍。除此之外,这边还需要再强调两个注意点:
- “脏读”,”不可重复读”以及”幻读”这三个现象不是错误,更不是BUG。它们仅仅是事务并发场景下可能出现的现象。
“脏读”,”不可重复读”以及”幻读”针对的是同一个事务中的读操作而言。为什么要强调这一点,因为有些对数据库理解不深的同学不能很好的理解清楚 会话,事务和语句这三个概念。于是对于以下的例子就会误认为是”不可重复读”的现象:
但实际上,在这个例子中会话2的SQL是按隐式事务来执行,其执行的两条
SELECT name FROM foo WHERE id = 1;
实际上是分属两个不同的读事务。对于后执行的那条SELECT文所属的隐式事务,由于其执行时会话1的更新已经提交,所以它理所当然地可以读到更新后的数据。这与”不可重复读”的概念没有任何关系。
如何理解事务隔离级别
事务隔离级别与上述三现象的关系
理解清楚了以上的这三个”读”的概念后,就可以很容易的理解事务隔离级别了。因为事务隔离级别的设置本质上就意味着让你控制并发事务之间的写事务带来的数据更新的对于同时正在执行的其他事务的可见性——即,你允许业务中的并发事务之间出现怎样的都现象. 由于”脏读”,”不可重复读”以及”幻读”的概念是一种层层递进的概念,因此事务隔离级别从”Read Uncommited”到”Serializable”也是一个比一个严格。
SQL标准中对于隔离级别与事务之间数据可见性的关系定义如下:
需要注意的是,SQL标准中对于这些隔离级别定义中约束的”不允许”现象是强制要求的。数据库厂商在宣称支持某个隔离级别时,必须将上表中对应隔离级别的”不允许”进行实现。但是对于”可能”项则不代表你必须实现成具备某种都现象。比如在PostgreSQL中,由于其MVCC的实现,REPEATABLE READ
对于读事务的行为实现也和SERIALIZABLE
一样是不会出现幻读的,而REPEATABLE READ
和SERIALIZABLE
的区别,主要体现在下文所述的对更新操作的约束力度上。
SERIALIZABLE对写操作的约束
在其他隔离级别中,如果并发的两个事务同时意图对同一个元组进行更新时,后更新的事务会等待直到先更新的事务提交后在继续执行其更新操作。 但是在SERIALIZABLE的情况下,由于此时事务隔离级别最强,会对有可能对读一致性带来影响的写操作必须按照事务的串行执行。在PG的实现中,这表现为尝试对于同一元组进行更新的并发事务会在等待完先更新的事务提交后自己报个错:
在SERIALIZABLE隔离级别下,对于上图中的两个更新事务若都希望成功,需要保证右边会话的更新操作所属事务的START TRANSACTION
必须发生在左边会话的更新事务COMMIT
之后,即两个写事务真正是”串行”的。
以上就是我对事务隔离级别的认识。