VACUUM VERBOSE 输出,不可移除的“死行版本无法移除”?

oli*_*ver 8 postgresql vacuum

我有一个 Postgres 9.2 DB,其中某个表有很多不可移动的死行:

# SELECT * FROM public.pgstattuple('mytable');
 table_len  | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent 
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
 2850512896 |      283439 | 100900882 |          3.54 |          2537195 |     2666909495 |              93.56 |   50480156 |         1.77
(1 row)
Run Code Online (Sandbox Code Playgroud)

正常的吸尘也显示许多不可移动的死行:

# VACUUM VERBOSE mytable;
[...]
INFO:  "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL:  2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]
Run Code Online (Sandbox Code Playgroud)

该表只有大约 300.000 个实际数据行,但有 230 万个死行(这似乎使某些查询非常慢)。

根据SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;没有访问数据库的旧事务。最旧的交易有几分钟的时间,还没有修改表上的任何内容。

我还检查了select * from pg_prepared_xacts(检查准备好的事务)和select * from pg_stat_replication(检查挂起的复制),这两个都是空的。

在该表上执行了大量插入、更新和删除操作,因此我可以理解正在创建大量死行。但是为什么它们没有被 VACUUM 命令删除呢?

Dan*_*ité 7

最旧的交易有几分钟的时间,还没有修改表上的任何内容。

这还不够。我认为将这些行标记为死需要的是,当这些事务开始时,没有其他事务触及这些行(对它们执行 UPDATE 或 DELETE)。

更新或删除行将保留该行以前版本的物理位置,并将其xmax字段设置为当前事务的 TXID。从其他事务的角度来看,如果该行的旧版本是其快照的一部分,它仍然可见。每个快照都有一个xminxmax,可以比较行版本的xminxmax。关键是 VACUUM 必须将行版本与所有实时快照的组合可见性进行比较,而不是简单地检查是否确实提交了行更改。后者是必要的,但不足以回收旧版本使用的空间。

例如,这里有一系列事件,即使修改它们的事务已经完成, VACUUM 也无法清理死行:

  • t0: 长时间运行的事务 TX1 开始
  • t0+30mn: TX2 启动并设置为可重复读取模式。
  • t0+35mn: TX1 结束。
  • t0+40mn: pg_stat_activity 仅显示 10 百万旧的 TX2
  • t0+45mn: VACUUM 运行但不会消除由 TX1 修改的旧版本行(因为 TX2 可能需要它们)。

  • 有见地!主体甚至可以在没有任何长时间运行的事务的情况下工作。不断的高负载可能会达到同样的效果。 (3认同)
  • @EvanCarroll:AFAIK 访问一行没有标记任何东西。一方面,如果每行读取都会导致写入,则读取性能将被破坏。 (2认同)
  • 所以这意味着重叠事务的“链”(每个执行插入/更新)会阻止清除由重叠事务之一创建的所有死行?这确实可以解释我所看到的问题——我的软件正是按照这种使用模式执行的。我的每笔交易都需要不到一分钟(并且可能会创建 1.000 个死行);但整个链条几天内都没有中断。 (2认同)

Eva*_*oll 6

我能够重新创建这个。本质上,在交易内部时,

  • READ COMMITTED默认事务级别:
  • SERIALIZABLEREPEATABLE READ交易层面:

样本数据

CREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);
Run Code Online (Sandbox Code Playgroud)

作为旁注,如果您bar在创建表后删除任何内容,这些行将变为removableVACUUM您将看到。

INFO:  "bar": removed # row versions in # pages
Run Code Online (Sandbox Code Playgroud)

交易顺序

现在,这是重新创建场景的 txn 表。

txn1       - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1       - SELECT * FROM bar;
      txn2 - DELETE FROM bar;      -- We delete after the select
      txn2 - VACUUM VERBOSE bar;   -- Can't remove the "dead row versions"
Run Code Online (Sandbox Code Playgroud)

VACUUM无法删除这些行版本,因为后续的SELECT * FROM bar;underREPEATABLE READ仍会看到它们!在VACUUM上述生产,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL:  10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
Run Code Online (Sandbox Code Playgroud)

这正是你所看到的。

调试问题

要找出阻止VACUUM清理死行的查询,请运行此..

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
  USING (pid)
WHERE relation::regclass = 'bar'::regclass
  AND granted IS TRUE
  AND backend_xmin IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

这将返回这样的东西..

       query        ?        state        ? locktype ?      mode       
???????????????????????????????????????????????????????????????????????
 SELECT * FROM bar; ? idle in transaction ? relation ? AccessShareLock
Run Code Online (Sandbox Code Playgroud)

解决方案

所以让我们回到我们的 TXN.. 我们需要杀死/提交/回滚 txn1,然后重新运行 VACUUM

txn1       - COMMIT;
      txn2 - VACUUM VERBOSE bar;
Run Code Online (Sandbox Code Playgroud)

现在我们看到,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": removed 10 row versions in 1 pages
INFO:  "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO:  "bar": truncated 1 to 0 pages
DETAIL:  CPU 0.00s/0.00u sec elapsed 0.01 sec.
Run Code Online (Sandbox Code Playgroud)

特别说明

  1. 删除了哪些行以及您选择了哪些行都没有关系。选择获取ACCESS SHARE表上的锁。并且,然后VACUUM无法删除死行,因此它们被标记为“不可删除”。
  2. 我认为这是非常糟糕的行为VACUUM VERBOSE。我很想看。。

    DETAIL:  10 dead row versions cannot be removed yet
             could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
    
    Run Code Online (Sandbox Code Playgroud)

进一步阅读

另外,感谢Daniel Vérité让我查看系统目录和VACUUM在这个目录上的行为。

  • 优秀的帖子。不过,看起来丹尼尔还是成功了。这是他的答案的豪华版本,包含演示、背景、链接和更多解释。 (2认同)