如何确定行的可见性?


10

在最简单的情况下,当我们在表中插入新行(并且提交事务)时,所有后续事务都将看到该行。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

当我们更新它时(因为该标志被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

根据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

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

阅读一些有关此内容的知识后,您可能会发现有关行可见性的一些信息。有可见性图,但是它只告诉整个页面是否可见-它绝对不能在行(元组)级别上工作。然后是提交日志(aka clog)-但是Postgres如何确定是否必须访问它?

我决定看一下infomask位,以了解可见性实际上是如何工作的。要查看它们,最简单的方法是使用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));

然后检查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

从上面的内容中我了解到,第一个版本以交易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

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

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

Answers:


6

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

第二个有HEAP_XMAX_INVALID。这意味着它不必查阅阻塞程序,因为已经有人进行了查询,看到异常xmax终止,并设置了“提示位”,这样以后的进程就无需再次为该行访问阻塞程序了。

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

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

如果xmin有效并已提交,并且如果xmax不为零,并且不存在heap_max_committedheap_max_invalid,则必须访问木log以查看该事务的处理方式。

有没有办法检查木log里面有什么?在Postgres的早期版本中提到了木桩损坏的问题,并暗示可以手动构建假文件。这条信息将对其有很大帮助。

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

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


4

看看HeapTupleSatisfiesMVCC()的实现:实际clog检查发生在TransactionIdDidCommit()中,但是仅当无法从信息掩码位(HeapTupleHeaderXminCommitted()宏 和朋友)中推断出交易状态时才调用该检查。

我一直追溯到访问pg_clog到的功能TransactionDidCommit()TransactionDidAbort(),然后我抬头一看,这些地方被称为以及与此问题相关的代码的唯一的地方似乎是HeapTupleSatisfiesMVCC()。从该函数的代码中,您可以看到只有在元组未设置相关的信息掩码位的情况下,才可以进行实际的阻塞查找:该代码从用HeapTupleHeaderXminCommitted()et等检查位开始。仅当未设置位时才进行阻塞查找。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.