Background

最近在看分布式事务相关的论文,很多论文设计的系统中都实现的是快照隔离这一层次的机制,其中 Epoxy 最为典型,直接把 Postgres 的快照隔离机制在中间层重新实现了一遍。

之前看关于 Postgres 快照隔离机制的文章,找到了这个:PostgreSQL并发控制,讲得非常好,逻辑非常清晰,理论和实际例子相结合。

这篇文章中关于 Visibility Check Rules 的部分讲的非常详细,但是没啥规律,可归纳性不强,我时不时就会回来看看这一段,但每次看的时候好像都要从头再重新理解一遍,于是最近我整理了一下这十条规则,力求达到清晰有序。

Rules

我先把原文中提到的十条规则列出来,方便下文做参考。

可以把这些规则简单地按照 t_xmin 的状态分为三部分:

Status of t_xmin is ABORTED:

  • Rule 1: If Status (t_xmin) = ABORTED ⇒ Invisible

Status of t_xmin is IN_PROGRESS:

  • Rule 2: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax = INVAILD ⇒ Visible
  • Rule 3: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax ≠ INVAILD ⇒ Invisible
  • Rule 4: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin ≠ current_txid ⇒ Invisible

Status of t_xmin is COMMITTED:

  • Rule 5: If Status (t_xmin) = COMMITTED ∧ Snapshot (t_xmin) = active ⇒ Invisible
  • Rule 6: If Status (t_xmin) = COMMITTED ∧ (t_xmax = INVALID ∨ Status (t_xmax) = ABORTED) ⇒ Visible
  • Rule 7: If Status (t_xmin) = COMMITTED ∧ Status (t_xmax) = IN_PROGRESS ∧ t_xmax = current_txid ⇒ Invisible
  • Rule 8: If Status (t_xmin) = COMMITTED ∧ Status (t_xmax) = IN_PROGRESS ∧ t_xmax ≠ current_txid ⇒ Visible
  • Rule 9: If Status (t_xmin) = COMMITTED ∧ Status (t_xmax) = COMMITTED ∧ Snapshot (t_xmax) = active ⇒ Visible
  • Rule 10: If Status (t_xmin) = COMMITTED ∧ Status (t_xmax) = COMMITTED ∧ Snapshot (t_xmax) ≠ active ⇒ Invisible

整理分析

其实我们可以分情景来讨论。

未修改情况下的可见性

  • pre_txid 指的是上一个修改了该 record 的事务 id。
  • some_txid 指的是某个事务的 id。
Recordt_xmint_xmaxVisibilityComment
Record 7some_txidinvalidinvisible某个修改过此记录的事务已经中止(ABORTED)
Record 8pre_txidinvalidvisible此记录还未被其他事务修改过
  • Rule 1 对应 Record 7 的情况。
  • Rule 6 对应 Record 8 的情况。

事务自行修改的可见性

  • cur_txid 指的是目前正在进行中的事务 id。
  • pre_txid 指的是上一个修改了该 record 的事务 id。
  • very_old_txid 可以代表上上个修改了该 record 的事务 id。

注意,下表中的 Record 都指的是同一条数据,这些数据的元数据不同。

Recordt_xmint_xmaxVisibilityComment
Record 1very_old_txidpre_txidinvisible非常老的记录,等待被垃圾回收
Record 2pre_txidcur_txidinvisible本事务开始之前的记录(t_xmax 已经被修改)
Record 3cur_txidcur_txidinvisible本事务第一次修改
Record 4cur_txidinvalidvisible本事务第二次修改

很明显,这种情况下,我们只能看到最新的一条修改记录:

  • Rule 10 对应 Record 1 的情况
  • Rule 7 对应 Record 2 的情况
  • Rule 3 对应 Record 3 的情况
  • Rule 2 对应 Record 4 的情况

并发事务修改的可见性

这里也要细分两种情况:

  • 在本事务读某个数据时,对应的并发事务已经修改了该数据但还未提交。
  • 在本事务读某个数据时,对应的并发事务已经修改了该数据并且已经提交。

这两种情况在 Record 中的表现形式是一样的,我们的结论是:无论并发事务是否已经提交,我们都只能看到修改之前的旧数据。

  • pre_txid 指的是上一个修改了该 record 的事务 id。
  • concur_txid 指的是并发事务的 id。
Recordt_xmint_xmaxVisibilityComment
Record 5pre_txidconcur_txidvisible只能看到并发事务修改之前的记录
Record 6concur_txidinvalidinvisible并发事务做出的修改都应该不可见
  • Rule 8(并发事务还未提交)和 Rule 9(并发事务已经提交)对应 Record 5 的情况。
  • Rule 4(并发事务还未提交)和 Rule 5(并发事务已经提交)对应 Record 6 的情况。

总结

从上文可以看出,我们根据情景来归纳整理,比单纯地根据 t_xmin 事务的状态来整理有逻辑得多。

这里我们也可以根据 t_xmin 的事务状态反过来整理一下:

  • Status (t_xmin) = ABORTED
    • 一定不可见
      • Rule 1:已经中止的事务写入的记录不可见
  • Status (t_xmin) = IN_PROGRESS
    • 只有自己更新的,最后一条记录可见
      • Rule 10:非常老的记录不可见
      • Rule 7:修改前的记录不可见
      • Rule 3:不是最新的修改记录不可见
      • Rule 2:最新的修改记录可见
    • 如果被还未提交的并发事务修改了,那么
      • Rule 8:修改前记录可见
      • Rule 4:修改后记录不可见
  • Status (t_xmin) = COMMITED
    • 上轮事务提交的,本轮中未被修改的记录可见
      • Rule 6:最新的,未被修改的记录可见
    • 如果被已经提交的并发事务修改了,那么
      • Rule 9:修改前的记录可见
      • Rule 5:修改后的记录不可见