早上巡查时发现一张业务大表死元组占比高达 60%,autovacuum 早上也才刚刚执行过。第一反应就是 autovacuum 虽然执行了,但是没有执行成功,这个时候看下日志是最好的,但是没有权限登录数据库服务器。只能查询资料总结了下基本的常见情况然后找厂家battle。

1.失效复制

复制槽会保留那些在主库上已经被删除或更新的元组,直到备库确认已经接收到这些元组的所有变更。这种机制确保了数据的一致性和完整性,但也意味着在某些情况下,死元组不会被立即清理,从而导致表的膨胀和存储空间的浪费。

select * from pg_replication_slots;

//删除失效的复制槽
SELECT pg_drop_replication_slot('slot_name');

2.长事务导致

如果数据库中存在长时间运行的事务,VACUUM 操作可能会被阻塞。这是因为 VACUUM 需要确保没有任何事务仍在访问那些它试图清理的行。如果有一个长事务存在,那长事务时间的其它表也没办法Vacuum,因为它不确认你是否会查其它表。

PostgreSQL有一个数据库边界(database horizon)的概念,它是由当前最老的未提交事务的xmin值定义的。VACUUM操作只能清理那些xmin值小于数据库边界的事务产生的死元组。如果存在长事务,数据库边界会被推后,导致VACUUM无法清理那些在长事务开始之前产生的死元组。

//根据事物开启时间排序
 SELECT  datname,
      usename,
      query,
      xact_start,
      now()-xact_start xact_duration,
      query_start,
      now()-query_start query_duration, state
FROM pg_stat_activity
WHERE state<>$$idle$$
    AND (backend_xid is NOT null
    OR backend_xmin is NOT null)
AND pid !=pg_backend_pid()
ORDER BY xact_start DESC;

//大于30min的长事务
 SELECT  datname,
      usename,
      query,
      xact_start,
      now()-xact_start xact_duration,
      query_start,
      now()-query_start query_duration, state
FROM pg_stat_activity
WHERE state<>$$idle$$
    AND (backend_xid is NOT null
    OR backend_xmin is NOT null)
    AND now()-xact_start > interval $$30 min$$
ORDER BY xact_start;

3.存在未提交的prepare事务

VACUUM操作在清理死元组时,会检查每个元组的xmax值。如果xmax对应的事务ID仍然处于活动状态(即未提交或未回滚),那么该元组不会被认为是死元组,因此不会被清理。

SELECT gid, prepared, owner, database, transaction
    FROM pg_prepared_xacts
    ORDER BY age(transaction) DESC;

//大于30min的预处理语句
 SELECT  name,
      statement,
      prepare_time,
      now()-prepare_time,
      parameter_types, from_sql
FROM pg_prepared_statements
WHERE now()-prepare_time > interval $$30 min$$
ORDER BY prepare_time DESC;

4.idle in transaction状态的事务

VACUUM 依赖于事务的快照。如果某个事务正在运行并持有对某些数据的锁,VACUUM 可能会等待这个事务完成才能清理死元组。即使 VACUUM 执行完了,如果相关的事务没有提交,这些死元组仍然存在。

select * from pg_stat_activity where state='idle in transaction';

5.函数等内部结构涉及到表的访问

函数等内部结构涉及到表的访问,可以通过pg_locks去间接验证。

select * from pg_locks where relation='表名'::regclass ;

6.hot_standby_feedback参数问题

hot_standby_feedback参数打开,备库将向主库通报最旧的打开事务,并且主数据库上的 VACUUM 不会删除备用数据库上仍需要的旧行版本。
设置hot_standby_feedback参数之后备库会定期向主库通知最小活跃事务id(xmin)值,
这样使得主库vacuum进程不会清理大于xmin值的事务。

7.索引状态问题

VACUUM 处理表的死元组时也会扫描索引。如果索引存在问题或者不一致,可能导致死元组无法被清理。
解决方案: 确保索引状态,如不正常考虑重建索引。

select indexrelid::regclass,indrelid::regclass,indisvalid,indisready,indislive from pg_index where indisvalid='f';

8.表和索引的并发访问

如果表和索引正在被其他并发操作访问,可能会导致 VACUUM 无法完全清理死元组。

select * from pg_stat_activity where query like '%表名%' AND pid !=pg_backend_pid();

文档来源:https://mp.weixin.qq.com/s/CRo8rZylm2jyx-U-vBzXpA