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 级别 SERIALIZABLE
或 REPEATABLE 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)中找到死元组(碎片)?