PostgreSQL的事务隔离分析

事务隔离

不懂的同学先补补概念
Reference: WiKi

隔离级别(Isolation levels)

有四种隔离级别:

  • 可序列化(Serializable)
  • 可重复读(Repeatable reads)
  • 提交读(Read committed)
  • 未提交读(Read uncommitted)

正文

昨天被问了一个问题
当存在表test(id int)并有id=1一条记录, 那么以下两种操作会有什么行为

  1. SessionA启动事务后,SessionB做了更新id=2操作后,此时SessionA进行 UPDATE test SET id=id+2,结果如何。
  2. SessionA启动事务后,SessionB在事务中做了更新id=2操作后,先不提交,此时SessionA进行 UPDATE test SET id=id+2,结果如何。

顺着源码分析一下Postgres的是如何实现这种行为的。

提交读(PostgreSQL的默认设置)

在提交读的隔离级别下,
1.行为a的结果,SessionA 进行更新操作前查询id变为2,更新操作成功后,查询id为4。
2.行为b的结果,在SessionB提交前,SessionA的查询id为1,如进行update操作,会一直阻塞直到SessionB提交或回滚,如SessionB成功提交查询id为4,若SessionB回滚,查询id结果为3。

行为1分析

SessionA的运行流程和普通的更新流程类似,先得到需要更新的row,然后进入heap_update,对选定的row使用HeapTupleSatisfiesUpdate进行版本(MVCC)检查,由于SessionB的事务已经提交,所以会得到HeapTupleMayBeUpdated的状态,然后真正进行更新操作。(其中包括hot-update等各种流程就不在此描述)

行为2分析

此时的运行流程会和上面略有不同,当获取目标row时候会得到尚未更新的那行row(因为此row虽然被标记为已删除,但是因为SessionB尚未提交,所以仍然可见),对row进行更新版本检查时,发现此row已经删除,且SessionB还未提交,标记为HeapTupleBeingUpdated,接着尝试取得该row的锁(会等待直到SessionB提交或者回滚),之后检查此row,如果被更新成功(SessionB提交),则进行row的refresh,对refresh后的row重新进行之前的操作,如果更新失败(SessionB回滚),则直接更新。

可重复读

在可重复读的隔离级别下,
1.行为a的结果, SessionA 进行更新操作前查询id为1,若进行更新操作, 则报错 “could not serialize access due to concurrent update”。
2.行为b的结果, 在SessionB提交前,SessionA的查询id为1,如进行update操作,会一直阻塞直到SessionB提交或回滚,如SessionB成功提交则报错 “could not serialize access due to concurrent update”, 若SessionB回滚,则sessionA 的 UPDATE操作成功,查询id结果为3,

行为1分析

和提交读隔离级别的行为有点类似,但由于是可重复读的快照,所以一开始取得的目标row是更新前的row,也就是id=1(提交读id=2)的行,于是在更新操作的mvcc版本检查中会认为此row是HeapTupleUpdated状态,需要重新refresh row,在refresh对隔离级别进行检查,如果大于等于可重复读的级别,则抛错。

行为2分析

和提交读隔离级别的代码路径一致,只是在 refresh row 时 对隔离级别进行检查,因为此时为可重复读,所以抛错。

上一篇:arcgis catalog 连接sde时出现 Target state not found in the STATES table 错误


下一篇:Google App Engine使用简介