postgresql / 真空中的大量活/死元组不起作用

Posted

技术标签:

【中文标题】postgresql / 真空中的大量活/死元组不起作用【英文标题】:High number of live/dead tuples in postgresql/ Vacuum not working 【发布时间】:2019-01-21 11:59:09 【问题描述】:

有一个表,有 200 行。但显示的实时元组数量不止于此(大约 60K)。

select count(*) from subscriber_offset_manager;
 count 
-------
   200
(1 row)


 SELECT schemaname,relname,n_live_tup,n_dead_tup FROM pg_stat_user_tables  where relname='subscriber_offset_manager' ORDER BY n_dead_tup
;
 schemaname |          relname          | n_live_tup | n_dead_tup 
------------+---------------------------+------------+------------
 public     | subscriber_offset_manager |      61453 |          5
(1 row)

但是从 pg_stat_activity 和 pg_locks 可以看出,我们无法跟踪任何打开的连接。

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
  USING (pid)
WHERE relation::regclass = 'subscriber_offset_manager'::regclass
  ;
 query | state | locktype | mode 
-------+-------+----------+------
(0 rows)

我也在这张桌子上尝试了全真空,以下是结果:

所有时间都没有删除行 有时所有的活动元组都变成了死元组。

这是输出。

vacuum FULL VERBOSE ANALYZE subscriber_offset_manager;
INFO:  vacuuming "public.subscriber_offset_manager"
INFO:  "subscriber_offset_manager": found 0 removable, 67920 nonremovable row versions in 714 pages
DETAIL:  67720 dead row versions cannot be removed yet.
CPU 0.01s/0.06u sec elapsed 0.13 sec.
INFO:  analyzing "public.subscriber_offset_manager"
INFO:  "subscriber_offset_manager": scanned 710 of 710 pages, containing 200 live rows and 67720 dead rows; 200 rows in sample, 200 estimated total rows
VACUUM

 SELECT schemaname,relname,n_live_tup,n_dead_tup FROM pg_stat_user_tables  where relname='subscriber_offset_manager' ORDER BY n_dead_tup
;
 schemaname |          relname          | n_live_tup | n_dead_tup 
------------+---------------------------+------------+------------
 public     | subscriber_offset_manager |        200 |      67749

10 秒后

SELECT schemaname,relname,n_live_tup,n_dead_tup FROM pg_stat_user_tables  where relname='subscriber_offset_manager' ORDER BY n_dead_tup
;
 schemaname |          relname          | n_live_tup | n_dead_tup 
------------+---------------------------+------------+------------
 public     | subscriber_offset_manager |      68325 |        132

我们的 App 是如何查询这个表的。

我们的应用程序一般会选择一些行,并根据一些业务计算,更新行。

选择查询 -- 根据某个 id 选择

select * from subscriber_offset_manager where shard_id=1 ;

更新查询 -- 更新此选定分片 ID 的其他列

大约 20 个线程并行执行此操作,一个线程仅在一行上工作。

app 是用 java 编写的,我们使用 hibernate 来进行数据库操作。 Postgresql 版本为 9.3.24

另一个有趣的观察结果: - 当我停止我的 java 应用程序然后完全真空时,它工作正常(行数和活动元组变得相等)。因此,如果我们从 java app 中不断选择和更新,就会出现问题。 -

问题/问题

这些活的元组有时会变成死元组,然后又会活过来。

由于上述行为,从表中进行选择会花费时间并增加服务器上的负载,因为那里有很多 live/deadtuples ..

【问题讨论】:

听起来好像有什么严重的错误。 Postgres 9.3 哪一点发布?最新的 9.3.23? SHOW track_counts 能得到什么? Postgres 版本是 9.3.24 。再观察一下 - 当我停止我的 java 应用程序然后进行全真空时,它工作正常。所以如果我们不断选择和更新就会有问题。 您可能会显示用于选择/更新行的查询。 添加了问题:选择查询-基于某些ID选择*来自订阅者偏移量管理器其中shard_id = 1;更新查询 -- 更新此选定分片 ID 的其他列 【参考方案1】:

我知道阻止VACUUM 工作的三件事:

长时间运行的事务。

未提交的已准备事务。

过时的复制槽。

详情请见my blog post。

【讨论】:

我尝试了所有三件事,但返回零行,没有锁,没有准备好的事务,没有复制槽 .... 我得到了问题并发布了答案,但我仍然有疑问,我在答案中提到了,请检查您是否可以回答。谢谢。 我无法应用您的第一点,因为我使用的是 postgres 9.3 并且 backend_xmin 不存在。所以,我选择了所有,没有发现任何长期运行的事务。所以.结论是当有正在运行的事务时,之后创建的死元组将不会被所有表的真空清理,因为事务ID是全局生成的,它检查的事务ID小于最旧事务的事务ID.Thanx . 很抱歉我的查询无效。但是在pg_stat_activity 中很容易找到长事务。很高兴你能解决这个问题。 写了一篇关于会话泄漏可以做什么的博客,它也会回答上面的问题hello-worlds.in/2021/03/28/…【参考方案2】:

我有问题☺。

为了理解这个问题,请考虑以下流程:

线程 1 -

打开休眠会话 对 Table-A 进行一些查询 从 subscriber_offset_manager 中选择 更新 subscriber_offset_manager 。 关闭会话。

Thread-1 类型的许多线程并行运行。

线程 2 -

这些类型的线程是并行运行的。 打开休眠会话 对 Table-A 进行一些选择查询 不关闭会话。(会话泄漏。)

临时解决方案 - 如果我使用 pg_cancel_backend 关闭 Thread-2 建立的所有连接,则吸尘开始工作。

此外,我们已经多次重现该问题并尝试了此解决方案,它确实有效。

现在,有以下疑问仍未得到解答。

    为什么 postgres 没有显示与表“subscriber_offset_manager”相关的任何数据。 如果我们使用 psql 在 Table-A 上运行 select 而不是运行 Thread-2 ,则不会重新创建此问题。 为什么 postgres 与 jdbc 一起工作。

更多令人兴奋的观察:

    如果我们在不同的会话中对“subscriber_offset_manager”运行查询,那么事件也会出现; 我们在这里发现了很多实例,其中线程 2 正在处理第三个表“Table-C”并且问题即将到来 pg_stat_activity 中所有这些类型的事务状态都是“idle_in_transaction”。

@Erwin Brandstetter 和@Laurenz Albe,如果您知道存在与 postgres/jdbc 相关的错误。

【讨论】:

我知道根本原因。因此,结论是当有一个正在运行的事务时,之后创建的死元组将不会被所有表的真空清理,因为事务 id 是全局生成的,它检查的事务 id 小于最旧事务的事务 id。 【参考方案3】:

毕竟可能有锁,您的查询可能会产生误导:

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity USING (pid)
WHERE relation = 'subscriber_offset_manager'::regclass

pg_locks.pid 可以为 NULL,则连接将消除行。 The manual for Postgres 9.3:

持有或等待此锁的服务器进程的进程 ID,如果锁由准备好的事务持有,则为 null

我的大胆强调。 (第 10 页还是一样。)

对于这个简单的查询,你有什么收获吗?

SELECT * FROM pg_locks
WHERE relation = 'subscriber_offset_manager'::regclass;

这可以解释为什么VACUUM 抱怨:

DETAIL:  67720 dead row versions cannot be removed yet.

这反过来又会指向您的应用程序逻辑/查询中的问题,锁定比需要更多的行。

我的第一个想法是长时间运行的事务,即使是简单的SELECT(获取低级ACCESS SHARE 锁)也可以阻止VACUUM 完成其工作。 20 个并行线程可能会无限期地链接并锁定 VACUUM。使您的事务(及其锁定)尽可能简短。并确保您的查询已经过优化,并且不会锁定过多的行。

还有一点需要注意:transaction isolation 级别 SERIALIZABLEREPEATABLE READ 使 VACUUM 更难清理。默认的READ COMMITTED 模式限制较少,但VACUUM 仍可如所讨论的那样被阻止。

相关:

What are the consequences of not ending a database transaction? Postgres UPDATE … LIMIT 1 VACUUM VERBOSE outputs, nonremovable “dead row versions cannot be removed yet”?

【讨论】:

Ran 您建议的查询:死元组大约是 80k,计数为 200,锁没有给出任何信息 SELECT * FROM pg_locks WHERE relationship = 'subscriber_offset_manager'::regclass; 0 行返回 关键是VACUUM试图清理的那一刻没有锁。 是的,这种情况正在发生,但不知道为什么? 您必须学习 Java 应用程序的事务处理和查询。交易开放时间过长? 20 个并行线程可能会无限期地链接并锁定 VACUUM。使事务(及其锁)尽可能简短。 我又添加了一个关于事务隔离级别的提示。

以上是关于postgresql / 真空中的大量活/死元组不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PostgreSQL 8.2(用于 Greenplum)中找到死元组(碎片)?

如何在 postgresql 中获取正在运行的查询的执行计划?

Postgres维护的正确顺序

PostgreSQL的vacuum流程

postgresql 中的真空表

Postgresql 9.2.1 在独立后端模式下完全真空后无法初始化