提示位

来自 PostgreSQL Wiki
跳转到导航跳转到搜索

PostgreSQL 的 MVCC 机制提供了许多有用的功能,但实现中也存在一些令人困惑的副作用。其中一个涉及提示位处理,即使您只是从数据库表中读取数据,也可能导致对数据库表进行大量的写入操作。

提示位用于标记元组,表明它们是由已知已提交或已中止的事务创建或删除的。要确定元组的可见性,而不设置这些位,您需要查询 pg_clog,甚至可能查询 pg_subtrans,因此这是一个昂贵的检查。另一方面,如果元组已设置位,则其状态已知(或者,在最坏情况下,可以从您的当前快照中轻松计算出来,而无需查看 pg_clog)。

共有四个提示位

  • XMIN_COMMITTED -- 创建事务已知已提交
  • XMIN_ABORTED -- 创建事务已知已中止
  • XMAX_COMMITTED -- 删除事务相同
  • XMAX_ABORTED -- 同上

如果两个 XMIN 位都没有设置,则

  • 创建事务仍在进行中,您可以通过检查共享内存中正在运行的事务列表来确认;
  • 您是第一个检查它的人,在这种情况下,您需要查询 pg_clog 以了解事务的状态,并且如果发现其最终状态,您可以更新提示位。

如果元组已被标记为已删除,则类似的说明适用于 XMAX 位。

对元组的任何检查(无论是通过 vacuum 还是任何普通 DML 操作)都会更新其提示位,以匹配插入/删除事务(s)在检查时的提交/中止状态。对整个表的简单 SELECT、count(*) 或 VACUUM 会检查每个元组的可见性并设置其提示位。

另一个需要注意的点是,提示位是在每个元组的基础上检查和设置的。虽然简单扫描会访问页面上的所有元组并立即更新所有提示位,但逐块访问(例如,通过索引扫描获取单个元组)可能会随着时间的推移导致更新各种提示位而对同一个页面进行多次写入。

提交日志

此处的一些细节在 src/backend/access/transam/README 中

  • "pg_clog 记录每个已分配 XID 的事务的提交状态。"
  • "事务和子事务仅在/如果它们第一次执行需要一个 XID 的操作时才会被分配永久 XID --- 通常是插入/更新/删除元组,尽管还有一些其他地方需要分配 XID。"

仅在子事务或主事务结束时更新 pg_clog。当分配事务 ID 时,检查包含该事务 ID 的 clog 页是否已存在,如果不存在,则初始化该页。

pg_clog 以 8kB 的页面分配。每个事务需要 2 位,因此在一个 8kB 页面上可以容纳 4 个事务/字节 * 8k 字节 = 32k 个事务。

在分配时,页面被清零,这是“事务正在进行”的位模式。因此,当事务开始时,它只需要确保包含其状态的 pg_clog 页面被分配,但不需要写入任何内容。在 8.3 及更高版本中,这并非在事务开始时发生,而是在分配 Xid 时发生(即,当事务第一次调用读写命令时)。在之前的版本中,这发生在第一次获取快照时,通常是在执行任何类型的第一个命令时,只有少数例外。

这意味着每 32K 个写入事务中只有一个事务确实需要在分配 XID 时做额外的工作,即创建并清空 pg_clog 的下一页。这不仅会减慢相关事务的速度,还会减慢随后几个希望获得 XID 但在清空过程中到达现场的家伙的速度。这可能有助于解释报告的行为,即事务执行时间会受到不可预测的峰值的影响。

CLOG 页面不会立即写入磁盘,直到内部 CLOG 缓冲区已满,此时最不常使用的缓冲区将被驱逐到永久存储。

源材料