行可见性究竟是如何确定的?

dez*_*zso 10 postgresql transaction-log mvcc

在最简单的情况下,当我们向表中插入新行(并且事务提交)时,它将对所有后续事务可见。请参阅xmax此示例中的 0:

CREATE TABLE vis (
  id serial,
  is_active boolean
);

INSERT INTO vis (is_active) VALUES (FALSE);

SELECT ctid, xmin, xmax, * FROM vis;

  ctid ?xmin ? xmax ? id ? is_active 
?????????????????????????????????????
 (0,1) ?2699 ?    0 ?  1 ? f
Run Code Online (Sandbox Code Playgroud)

当我们更新它时(因为该标志是FALSE偶然设置的),它会发生一些变化:

UPDATE vis SET is_active = TRUE;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid ? xmin ? xmax ? id ? is_active 
?????????????????????????????????????
(0,2) ? 2700 ?    0 ?  1 ? t
Run Code Online (Sandbox Code Playgroud)

根据PostgreSQL 使用的MVCC模型,一个新的物理行被写入,旧的无效(这可以从 中看出ctid)。新的仍然对所有后续事务可见。

现在,当我们回滚时发生了一件有趣的事情UPDATE

BEGIN;

    UPDATE vis SET is_active = TRUE;

ROLLBACK;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid  ? xmin ? xmax ? id ? is_active 
??????????????????????????????????????
 (0,2) ? 2700 ? 2702 ?  1 ? t
Run Code Online (Sandbox Code Playgroud)

行版本保持不变,但现在xmax设置为某些内容。尽管如此,后续事务仍可以看到此行(否则未更改)。

在阅读了一些关于此的内容后,您可能会了解有关行可见性的一些信息。有可见性映射,但它只告诉整个页面是否可见 - 它绝对不适用于行(元组)级别。然后是提交日志(又名clog) - 但是 Postgres 如何确定它是否必须访问它?

我决定查看信息掩码位,以了解可见性实际是如何工作的。要查看它们,最简单的方法是使用pageinspect 扩展。为了找出设置了哪些位,我创建了一个表来存储它们:

CREATE TABLE infomask (
  i_flag text,
  i_bits bit(16)
);

INSERT INTO infomask
VALUES 
('HEAP_HASNULL', x'0001'::bit(16)),
('HEAP_HASVARWIDTH', x'0002'::bit(16)),
('HEAP_HASEXTERNAL', x'0004'::bit(16)),
('HEAP_HASOID', x'0008'::bit(16)),
('HEAP_XMAX_KEYSHR_LOCK', x'0010'::bit(16)),
('HEAP_COMBOCID', x'0020'::bit(16)),
('HEAP_XMAX_EXCL_LOCK', x'0040'::bit(16)),
('HEAP_XMAX_LOCK_ONLY', x'0080'::bit(16)),
('HEAP_XMIN_COMMITTED', x'0100'::bit(16)),
('HEAP_XMIN_INVALID', x'0200'::bit(16)),
('HEAP_XMAX_COMMITTED', x'0400'::bit(16)),
('HEAP_XMAX_INVALID', x'0800'::bit(16)),
('HEAP_XMAX_IS_MULTI', x'1000'::bit(16)),
('HEAP_UPDATED', x'2000'::bit(16)),
('HEAP_MOVED_OFF', x'4000'::bit(16)),
('HEAP_MOVED_IN', x'8000'::bit(16)),
('HEAP_XACT_MASK', x'FFF0'::bit(16));
Run Code Online (Sandbox Code Playgroud)

然后检查我的vispageinspect中的内容- 请注意,它显示了堆的物理内容,因此不仅返回了可见行:

SELECT t_xmin, t_xmax, string_agg(i_flag, ', ') FILTER (WHERE (t_infomask::bit(16) & i_bits)::integer::boolean)
  FROM heap_page_items(get_raw_page('vis', 0)),
       infomask
 GROUP BY t_xmin, t_xmax;

 t_xmin ? t_xmax ?                      string_agg                      
????????????????????????????????????????????????????????????????????????
   2699 ?   2700 ? HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2700 ?   2702 ? HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID, HEAP_UPDATED
   2702 ?      0 ? HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED
Run Code Online (Sandbox Code Playgroud)

我从上面的理解是,第一个版本在事务 2699 时生效,然后在 2700 成功替换为新版本。
然后从 2700 开始存活的下一个版本UPDATE在 2702有回滚尝试,从HEAP_XMAX_INVALID.
最后一个从未真正出生,如 所示HEAP_XMIN_INVALID

因此,从上面的猜测,第一种和最后一种情况是显而易见的 - 它们对事务 2703 或更高版本不再可见。
第二个必须在某处查找 - 我想它是提交日志,又名clog.

为了使问题进一步复杂化,随后的UPDATE结果如下:

 t_xmin ? t_xmax ?                     string_agg                     
??????????????????????????????????????????????????????????????????????
   2699 ?   2700 ? HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2702 ?      0 ? HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED
   2703 ?      0 ? HEAP_XMAX_INVALID, HEAP_UPDATED
   2700 ?   2703 ? HEAP_XMIN_COMMITTED, HEAP_UPDATED
Run Code Online (Sandbox Code Playgroud)

在这里,我已经看到两个可能可见的候选人。所以,最后,这是我的问题:

  • 我的假设clog是在这些情况下确定可见性的地方吗?
  • 哪些标志(或标志的组合)告诉系统访问clog?
  • 有没有办法检查里面的东西clogclog在早期版本的 Postgres中提到了损坏,并暗示可以手动构建假文件。这条信息将对此有很大帮助。

jja*_*nes 6

因此,从上面的猜测,第一种和最后一种情况是显而易见的 - 它们对事务 2703 或更高版本不再可见。第二个必须在某处查找 - 我想它是提交日志,又名堵塞。

第二个有HEAP_XMAX_INVALID。这意味着它不必咨询堵塞物,因为有人已经这样做了,看到它xmax被中止,并设置一个“提示位”,以便未来的进程不需要再次访问该行的堵塞物。

哪些标志(或标志的组合)告诉系统访问堵塞?

如果没有heap_xmin_committedor heap_xmin_invalid,那么您必须访问 clog 以查看 xmin 的配置。如果事务仍在进行中,则该行对您不可见,并且您不能设置任何标志。如果事务提交或回滚,则相应地设置heap_xmin_committedheap_xmin_invalid(如果这样做方便的话——这不是强制性的),这样以后的人就不需要查找它了。

如果xmin有效且已提交,如果xmax不为零,并且没有heap_max_committedor heap_max_invalid,那么您必须访问阻塞以查看该事务的处置情况。

有没有办法检查木屐里面的东西?在早期版本的 Postgres 中提到了堵塞损坏,并暗示可以手动构建假文件。这条信息将对此有很大帮助。

我不知道这样做的用户友好的方式。您可以使用“od”以合适的方式转储堵塞文件以检查它们,并使用中定义的宏找出检查的位置src/backend/access/transam/clog.c

我很惊讶 PGXN 上没有可以为您工作的扩展,但我找不到。但我认为它不会那么有用,因为您确实需要能够在服务器未运行时执行此操作。


ale*_*lex 4

看一下HeapTupleSatisfiesMVCC()实现:实际clog检查发生在TransactionIdDidCommit()中,但仅当无法从信息掩码位推断事务状态时才会调用它(HeapTupleHeaderXminCommited() 宏 和朋友)。

我追踪了对pg_clog函数TransactionDidCommit()和的访问TransactionDidAbort(),然后我查找了这些函数的调用位置,代码中与您的问题相关的唯一位置似乎是在 中HeapTupleSatisfiesMVCC()。从这个函数的代码中,您可以看到,只有当元组没有设置相关的 infomask 位时,才会发生实际的阻塞查找:代码首先使用HeapTupleHeaderXminCommitted()et al 检查这些位。仅当未设置位时才会发生阻塞查找。