SQL事务的隔离级别(二)
行转账的例子:
BEGIN;
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;
COMMIT;
从账号7534转账100元到12345,这个事务先给12345加100元,然后给7534减100元。如果两个事务同时进行,那么后一个事务将会在前一个事务的基础上更新,这样就是我们需要的逻辑:给12345
加了200元而不是100元。
下面是另一个例子
BEGIN;
UPDATE website SET hits = hits + 1;
-- run from another session: DELETE FROM website WHERE hits = 10;
COMMIT;
这个例子是两个事务,一个事务把hits加1;另一个事务删除hits==10的记录。
在这种隔离级别下,删除的事务其实是删除不了任何记录的。因为它的where选择hits==10的记录,因为是Read Committed级别,所以它只会找到在这瞬间hits==10的记录,这是前面的事务把hits==10的记录的hits改成了11了,它发现这点,然后重新用where的条件判断一下,发现不满足了,就什么也不做了。
www.2cto.com
Repeatable Read Isolation Level
这个级别的select只能看到这个事务开始前提交的更改。可以这么理解:在开始事务时,给数据库拍个快照,然后所以的操作都在这个快照上进行;而上一个隔离级别呢,在select前个数据库拍个快照。注意:PG的这种实现方式不仅实现了Repeatable Read,而且同时实现了Serializable,这是合乎规范的,规范只是定义了每种隔离级别需要避免哪种现象。我们甚至可以只实现最高的一种隔离级别Serializable然后说自己实现了4种隔离级别。那SQL规范为什么还要定义这么多级别呢?因为实现的级别越高,效率也就越低,我的应用可能只需要较低级别的隔离,你用最高级别的隔离来实现,那么就效率太低了。说过极端的例子,我可以这样实现Serializable隔离级别------不允许事务并发执行,这必然避免了所有的情况,但是有什么意义呢?效率极低!
其实最常用的隔离级别就是第二种和第四种,所以
Oracle只实现了这两种(第一种就是没有隔离),所以不能理解为Oracle没有实现规范,它可以用第4中隔离级别来代替第3种(PostgreSQL也是这么做的),只不过有这样一种可能:有一种算法只实现了第三种级别没有实现第四种级别,它比第四种级别的高效,而没有实现它在某些应用场景下有点可惜!
这个级别的update有所不同,如果它要修改的时候发现别人正在修改,那么它也会等待。如果别人rollback,它正常修改,如果别人commit,那么它就会失败,因为别人已经修改了,那么就不可重复读了,这个事务就得失败。失败后重新来一次,这是读到的数据已经是最新的了。
失败的话会出现 ERROR: could not serialize access due to concurrent update
因此,只有只读的Repeatable Read事务从来不会冲突,有写的事务时才可能冲突。
Note: Prior to PostgreSQL version 9.1, a request for the Serializable transaction isolation level provided exactly the same behavior described
here. To retain the legacy Serializable behavior, Repeatable Read should now be requested.
注意:在9.1之前的版本,这个隔离级别就是最高级别了。
Serializable Isolation Level
前面说了,PG9.1的Repeatable Read Level其实就满足SQL规范的Serializable级别了。那么这个级别是什么呢? www.2cto.com
我们先来看一个例子,看看Serializable和真正串行执行事务的区别。
这个级别以及是SQL规范的最高级别了,但是它还是不能等价于真正的串行化(一个事务完成了另一个事务才能开始),比如下面的例子:
class | value
------+------
1 | 10
1 | 20
2 | 100
2 | 200
事务B:SELECT SUM(value) FROM mytab WHERE class = 1 把这个值以class=2插入
事务A:SELECT SUM(value) FROM mytab WHERE class = 2; 把这个值以class=1插入
先解释一下这个两个事务在干什么:多个事务协作计算这个表格value的值——事务A计算class=1的value和,事务B计算class=2,然后将结果以对方的class保持下来,一般对方可以方便的
求和。
如果以上面的隔离级别,也就是SQL规范的Serializable级别,那么两个事务都能提交,并且它们的结果都是300;而串行的结果呢?至少一个应该得到330
因此PG9.1的Serializable是真正的串行隔离级别,如果A插入(2,30),那么事务B的select或者insert都会出错,强迫回滚事务: www.2cto.com
ERROR: could not serialize access due to read/write dependencies among transactions
这是用户再次重试事务B,它读到的就是330了。
这个级别需要使用谓词锁(Predicate Lock),它会锁定这个事务里用到过的记录,它会同时使用细粒度的tuple locks和粗粒度的page locks,并且不会造成block和死锁,但是它需要跟踪
事务用过的记录,所以还是比上一个级别慢的。另外如果是只读事务的话,那么它如果发现后面的逻辑不依赖于某些锁定的对象的话,它可能提前释放这些对象。所以知道自己不会修改,
那么最好告诉PG。
MySQL InnoDB的隔离级别
To be continued.
作者 fancyerII