Transaction

什么是事务


事务是一组不可分割的SQL query语句,或者说是一个最小的工作单元
### 事务与锁的关系

为什么提出这个问题

在阅读《Java虚拟机并发编程》(Programming Concurrency on the JVM - Materning Synchronization, STM and Actors)中STM(Software Transaction Memory)时,我看到transaction特征在concurrency中的神奇应用场景:

  • 原子性:涉及一组操作,这组操作具有原子特征,比如存款和取款的组合操作。这组操作内部的所有更改要么全部成功,要么全部失败。
  • 一致性:所有并行的事务所造成的变更,从外部来看,都是一个接一个发生的。比如:存款,取款这两个独立事务。如果存款的过程中间,取款操作接入,那么取款读取的数据是旧的。待存款恢复并执行完毕,取款想要写回的数据必然无效。取款事务需要重做!所以外观来看这是存款到取款的序列,反之亦然。其实本质上,就是可见性的问题。
  • 不需要显式地运用锁,不论是读锁还是写锁。这样就为程序员提供了比较好的抽象屏障(abstract barrier)。
  • 隔离性:事务在未提交之前,所做的任何更改都不能被其他事务看到。

我看到了很多STM的好处,但是看到处理写偏斜异常(Handling Write Skew Anomaly)(可以简单理解为两个事务修改的变量不是同一个,但是两个变量之间又有约束关系)一章时,作者使用ensure函数给约束变量加了读锁。加读锁的意义在于本事务之外,其他事务无法获得该变量的写锁,自然无法修改它的值。但是这里显式地使用了锁,所以可以明确事务不是锁无关的,而且这让我联想到了数据库事务隔离级别中的可重复读(REPEATABLE_READ)。可重复读也是使用在特定记录行上使用读锁,来防止外部事务修改了该条记录行。

微妙的关系

有趣的事情来了,事务原子性能确保数据的完整性,而事务的一致性和隔离性则侧重于数据的可见性。可见性的保证在并发当中绝对和锁相关。我刚说了,事务给锁提供了抽象屏障,而且事务的隔离级别依旧仰仗锁的粒度,所以不要将事务看做银弹,以为有了事务,锁就不值一提。

数据库事务的隔离级别


为什么提出这个问题

一直被《高性能MySQL》里的解释弄得稀里糊涂,纠结于脏读、不可重复读和幻读之间的关系。而且某些解释看似合理,但完全没有指导价值。比如:阐述隔离级别,却没法从中得出我们如何结合应用场景选择合适的隔离级别。

隔离级别

  • Uncommited Read
    一个事务未提交,另一个事务却能读到该事务所做的更改。因为有可能读到未提交到数据库里的脏数据,这一级别会导致脏读。适用于只读场景下。

  • Commited Read
    未提交之前,事务之间是不可见的,所以可以阻止脏读。
    但是会导致不可重复读问题,也就是在本事务内,读取一条记录,另一个事务修改了此条记录并提交,本事务再读取同一条记录时,发现得到记录和前一条不一致的场景。这一级别也被称为不可重复读级别。
    但上述场景没有半点指导意义!
    如果你的应用场景是这样的——你想查询的变量是通过本事务里只读但是对于外部事务可写的变量作为条件查询出来的,那么这个只读变量很可能不可重复读,导致这个事务会失效。举个例子:如果你的查询语句是这样的SELECT USER.age into age FROM USERS name='YOU';
    这时候外部事务修改了名字UPDATE SET name='ME' WHERE name='YOU';并提交。那么这时候,age是无效的状态,你再拿来用就有问题了。这一级别适合于读多写少且写偏斜不存在的场景。

  • Repeatable Read (MySQL的默认隔离级别)
    可重复读,可以理解给只读的记录行加了读锁。这样,外部事务无法获得写锁,本事务内部这条记录始终有效,待事务结束即可解锁。反之,外部事务先得写锁,那么本事务无法获取记录行的读锁,导致重试发生。显然,如果你的应用场景里,读多写少且读写操作同一条记录的可能性很大的时候适合。

  • 幻读
    可重复读级别无法防止幻读。幻读是这样一种场景,本事务读取一个范围内,范围内,范围内(重要的事情写三遍)的数据集,但是另一事务又向这个范围内插入一条记录,导致数据集发生变化了,像是出现了幻觉,所以称为幻读(我很痛恨一些奇葩的科学家起的不合理的名字,这就是其一。按着这种逻辑,不可重复读不也可以说是出现幻觉吗?)。那么为什么会出现这种情况,原因是新插入的记录以前不存在于数据库中,所以你没法为它加锁。而且可重复读只是为每行记录加锁,没有用到Range Lock,这一幻影插入操作总能成功。
    不过MySQL中InnoDB存储引擎提供了MVCC(多版本并发控制)技术,为每条记录设置一个递增的事务编号,大于本次事务编号的记录,不准插入记录。
    可重复读+MVCC即可解决并发中的大部分问题。

  • Serializable Read
    顾名思义,串行读,事务之间是串行的,同步的。换言之,并发性剧减。

STM的隔离级别


  • 隔离级别处于提交读。

事务的级别


描述的是事务本身的属性

  • ReadOnly:所有的操作都是读取操作,不涉及任何产生副作用的操作。