The Serialization isolation level 是最严格的隔离级别。对所有提交的transactions, 这个level模仿了序列化的transaction执行; 就像并发的transaction是一个接着一个执行的,序列化地,而不是并发地。使用serializable isolation level,应用应该做好重试机制,应对可能serialization failures。Serializable isolation level的内部机制和repeatable read相似,除了serialization会监控查询条件(where 语句),这些查询条件可能导致数据不一致。这个监控不会带来比repeatable read 更多的阻塞,会带来些负载, 会查验where语句是否会造成serialization anomaly以及serialization failure.
Table: mytab
class | value
-------+-------
1 | 10
1 | 20
2 | 100
2 | 200
SELECT SUM(value) FROM mytab WHERE class = 1;
Serializable Transaction1 执行以上语句, 再把结果30和class=2新插入一列。
SELECT SUM(value) FROM mytab WHERE class = 2;
Serializable Transaction2 执行以上语句, 再把结果300和class=1新插入一列。
两个transaction执行完语句后同时commit。先提交的那个transaction会成功,后提交的那个transaction里,select的值已经变了,会抛出异常:
ERROR: could not serialize access due to read/write dependencies among transactions
Serializable Transaction 1 | Serializable Transaction 2 |
begin | begin |
SELECT SUM(value) FROM mytab WHERE class = 1; | SELECT SUM(value) FROM mytab WHERE class = 2; |
Insert mytab (30, 2) | Insert mytab (300, 1) |
Commit; |
|
| Commit; |
|
|
Transaction1 会成功,transaction 2会失败。在transaction 2中,select语句之前查出的是300,transaction1提交一个,应该是330。如果transaction1 和transaction2顺序执行,transaction2的select查出的应该是330,所以transaction 2抛出异常回滚了。
使用serializable transactions时,只读transaction也可能被回滚,只要在这个transaction提交的时候,它读的数据已经过期了。Deferrable read-only transaction例外。应用不能依赖于被回滚的transaction查出来的数据;应用应该retry回滚的事务。
spring 环境下
@Retryable(
value = {SQLException.class},
maxAttempt=2,
backoff=@Backoff(delay=5000)
)
void myService() throws SQLException{
... ...
}
@Recover
若retry仍然不成功,调用@Recover注解的方法
PostgreSQL使用predicate locking 来保证serializable transactions的serialiability。有些写的操作,会导致其他transaction里读的操作过期,这个predicate locking 帮助确保让这个写操作在度的操作前面。Predict locking不会造成阻塞或者死锁。Predicate是用来识别和标识并发serializable transactions 之间的依赖,这些transactions的某些组合会导致serialization anomalies。Read committed 和repeatable read transaction要保证一张表的数据一致性,需要锁表;或者使用select for update, 这会阻塞其他transaction 以及造成 disk access。
Serializable transactions 用简单的方式满足了数据一致性。一堆并发的serializable transactions提交的结果,和这些transaction挨个执行的结果是一样的;也就是说如果单个transaction被认为是对的,那么把它放到并发的环境下也不会错。需要有一个通用的方式来处理serialization failures, 因为这种异常很难预测。对read/write dependencies的监控和重启transaction都要耗费资源,但是相对于select for update 以及其他锁造成的阻塞的开销,serilizable transactions 是the best performance choice.
Serializable transaction会有些问题,这些问题不会再真正序列化的执行中发生。所有serializable transactions在插入记录前,用select做验证,可以避免这种错误。
为了在使用serializable transactions 时,达到最好的性能,我们应该考虑:
可能的话,把transaction 设置为READ-ONLY
使用连接池,并控制连接的数量。在高并发的系统中,这点尤为重要。
别在一个transaction里放太多
别让idle in transaction的时间太长了。
在serialization transactions的保护下,确认select for update语句可以删除,就删除。