今天在处理个磁盘告警时,对一些膨胀率较高的表进行了vacuum full操作,但是经过漫长的等待之后发现竟然没有回收多少空间,一查死元组竟然没啥变化,坑爹啊!

至于原因是啥,我们先来简单了解下vacuum full的过程。

我们都知道vacuum full本质上是创建了一张新的表,会创建该表的一个新拷贝,并且在操作完成之前都不会释放旧的拷贝。因此在进行vacuum full操作的时候是会加上一个ACCESS EXCLUSIVE级别的锁,所以一般只有当我们需要从表中回收大量磁盘空间的,即膨胀率很高的表才会去做vacuum full的操作。

正因为vacuum full会进行数据的重组,保留页面最小大小,释放磁盘空间。所以我们在源码中也可以发现vacuum full不是和普通的vacuum放在一起,而是和cluster放在一块,即在src/backend/commands/cluster.c中。

下面我来看看一张普通的表进行vacuum full的大致过程:

cluster_rel(){rebuild_relation(){make_new_heap()copy_table_data()finish_heap_swap(){swap_relation_files()reindex_relation()performDeletion()RelationMapRemoveMapping()}}}

首先主函数是cluster_rel,cluster_rel调用rebuild_relation,正因为如此,所以vacuum full其实是表重建的一个过程,而rebuild_relation函数会调用三个大的步骤,make_new_heap->copy_table_data->finish_heap_swap。先创建新的临时表,然后拷贝数据,最后调用finish_heap_swap函数完成切换,finish_heap_swap又分为交换数据文件、重建索引、清理临时表、清除映射关系几个步骤。

从源码中看make_new_heap会调用heap_create_with_catalog()函数进行新的临时表创建,表名为pg_temp_%u。

copy_table_data函数的过程是:初始化新旧元组,置use_wal标识(copy过程需要记录wal日志),计算freezexid用以剔除死元组,置新表relfrozenxid值,通过优化器评估使用indexscan还是seqscan算法进行扫描,扫描元组,记录日志,加排他锁,写入新堆表。

拷贝完数据后调用finish_heap_swap函数完成切换,第一步swap_relation_files切换表的oid、tablespace等信息。

        swaptemp = relform1->relfilenode;relform1->relfilenode = relform2->relfilenode;relform2->relfilenode = swaptemp;swaptemp = relform1->reltablespace;relform1->reltablespace = relform2->reltablespace;relform2->reltablespace = swaptemp;swptmpchr = relform1->relpersistence;relform1->relpersistence = relform2->relpersistence;relform2->relpersistence = swptmpchr;

接着再调用reindex_relation函数进行索引的重建。

大致了解了vacuum full的过程后,我们就能大概推断出为什么vacuum full完我的新表仍然有那么多死元组了。问题必然是出在copy_table_data了!

在copy_table_data中调用了vacuum_set_xid_limits函数去设置copy后数据的xid:

   /** Compute xids used to freeze and weed out dead tuples and multixacts.* Since we're going to rewrite the whole table anyway, there's no reason* not to be aggressive about this.*/vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0,&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,NULL);

至于具体的计算过程大家可以去看vacuum_set_xid_limits里面的内容,而在该函数里面我们可以看到:

*oldestXmin =TransactionIdLimitedForOldSnapshots(GetOldestXmin(rel, PROCARRAY_FLAGS_VACUUM), rel);

很显然,和snapshot有关系。那么为什么空间没有回收的原因似乎呼之欲出了,长事务!

简单模拟下就可以发现:

bill=# \dt+ t3List of relationsSchema | Name | Type  | Owner | Persistence | Size  | Description
--------+------+-------+-------+-------------+-------+-------------public | t3   | table | bill  | permanent   | 10 MB |
(1 row)bill=# \x
Expanded display is on.
bill=# select * from pg_stat_all_tables where relname = 't3';
-[ RECORD 1 ]-------+------------------------------
relid               | 42880
schemaname          | public
relname             | t3
seq_scan            | 17
seq_tup_read        | 920000
idx_scan            |
idx_tup_fetch       |
n_tup_ins           | 510000
n_tup_upd           | 420000
n_tup_del           | 109999
n_tup_hot_upd       | 0
n_live_tup          | 100000
n_dead_tup          | 100000
n_mod_since_analyze | 630000
n_ins_since_vacuum  | 100000
last_vacuum         | 2022-03-25 21:20:28.563957+08
last_autovacuum     | 2022-03-25 17:12:14.787146+08
last_analyze        |
last_autoanalyze    | 2022-03-25 17:12:34.812686+08
vacuum_count        | 1
autovacuum_count    | 6
analyze_count       | 0
autoanalyze_count   | 3bill=# vacuum FULL t3;
VACUUMbill=# \dt+ t3List of relationsSchema | Name | Type  | Owner | Persistence | Size  | Description
--------+------+-------+-------+-------------+-------+-------------public | t3   | table | bill  | permanent   | 10 MB |
(1 row)

而当我们停掉长事务后,再去vacuum full,死元组便被回收了,磁盘空间也降了下来。

bill=# vacuum FULL t3;
VACUUM
bill=# \dt+ t3List of relationsSchema | Name | Type  | Owner | Persistence |  Size   | Description
--------+------+-------+-------+-------------+---------+-------------public | t3   | table | bill  | permanent   | 3552 kB |
(1 row)

这里有个需要注意的地方,vacuum full是不会去更新统计信息的!也就是说如果你执行完vacuum full后去查看pg_stat_all_tables,会发现n_dead_tup仍然没变化,但实际上你的表大小已经降了下来。

bill=# select * from pg_stat_all_tables where relname = 't3';
-[ RECORD 1 ]-------+------------------------------
relid               | 42880
schemaname          | public
relname             | t3
seq_scan            | 19
seq_tup_read        | 1320000
idx_scan            |
idx_tup_fetch       |
n_tup_ins           | 510000
n_tup_upd           | 420000
n_tup_del           | 109999
n_tup_hot_upd       | 0
n_live_tup          | 100000
n_dead_tup          | 100000
n_mod_since_analyze | 630000
n_ins_since_vacuum  | 100000
last_vacuum         | 2022-03-25 21:20:28.563957+08
last_autovacuum     | 2022-03-25 17:12:14.787146+08
last_analyze        |
last_autoanalyze    | 2022-03-25 17:12:34.812686+08
vacuum_count        | 1
autovacuum_count    | 6
analyze_count       | 0
autoanalyze_count   | 3bill=# \dt+ t3List of relationsSchema | Name | Type  | Owner | Persistence |  Size   | Description
--------+------+-------+-------+-------------+---------+-------------public | t3   | table | bill  | permanent   | 3552 kB |
(1 row)

至于为什么vacuum full在有长事务的情况下死元组不一定会被回收掉呢,那是因为为了保证事务的一致性,所以在该长事务的backend_xid或者backend_xmin之前的数据都没法被回收,而是要原封不动的拷贝到新的表中。

而为什么在有长事务存在的情况下,我们执行truncate操作会将表中所有数据包括死元组都回收呢?这是因为truncate并不是一个 MVCC-safe的操作。

TRUNCATE is not MVCC-safe. After truncation, the table will appear empty to concurrent transactions, if they are using a snapshot taken before the truncation occurred

同样的,例如一些alter table之类的操作也不是MVCC-safe的,因此对于生产中时刻都存在事务的情况下,切勿随便乱执行这些“高危”操作,很可能导致应用查询到了错误的数据!

为什么vacuum full也无法回收空间?相关推荐

  1. PostgreSQL 怎么通过vacuum 加速事务ID回收的速度 (翻译)

    此文为翻译文字原文连接在文字最下方 经历上次transaction id 回收报警的问题后,除了上次总结后,发现对于transaction id 的回收的问题还是处于一个急需在学习的过程,所以有了下面 ...

  2. 使用 diskpart 实现无损数据回收空间再分区

    Vista 下的 Diskpart 工具更加的使用强劲,不仅可以轻松地制作出来可引导的移动存储设备分区,而且可以在不损害当前分区数据的前提前,利用空闲容量创建一个新的分区.是不是很令人兴奋,我们从此可 ...

  3. [原]Oracle删除大表并回收空间的过程

    近日在查询某项日志的时候,发现查询非常缓慢,根据以往的经验这是由于某个日志表过大引起的,为了加快查询,决定将大部分的历史数据迁移到另外一个表中,本文主要记录删除这个大表的过程,就解决问题而言还有很多方 ...

  4. mysql 回收空间_MySQL表的碎片整理和空间回收小结

    MySQL表碎片化(Table Fragmentation)的原因 关于MySQL中表碎片化(Table Fragmentation)产生的原因,简单总结一下,MySQL Engine不同,碎片化的原 ...

  5. oracle 大表删除数据后,回收空间的问题。

    在oracle中由于表结构设计不合理或者需要清楚老数据的时候,经常需要对大表数据进行清理. 一般有一下几种方法: 1. 删除大部分数据,留下小部分数据.我们可以把需要保留的数据转移到别的表,然后再把大 ...

  6. truncate数据后回收空间_Truncate用法详解

    前言: 当我们想要清空某张表时,往往会使用truncate语句.大多时候我们只关心能否满足需求,而不去想这类语句的使用场景及注意事项.本篇文章主要介绍truncate语句的使用方法及注意事项. 1.t ...

  7. oracle 如何回收空间,Oracle回滚段空间回收步骤

    是谁"偷偷的"用了那么多空间呢(本来有几十个G的Free硬盘 空间的)? 检验 数据库表空间占用空间情况: SQL> select tablespace_name,sum(b ...

  8. oracle如何删除可回收归档,Oracle正确删除归档并回收空间的方法

    一个Oracle归档日志经常满,表现为/oraarchive 这个文件空间占用100%大家一定抱怨Oracle为何没有归档维护工具,很多人直接删除了事,错了,Oracle有,而且很智能,可以正确的删除 ...

  9. PostgreSQL vacuum原理一功能与参数

    从上篇"PostgreSQL MVCC 源码实现"中,我们知道,PG并没有像Oracle那样的undo来存放旧版本:而是将旧版本直接存放于relation文件中.那么带来的问题就是 ...

最新文章

  1. 2021年大数据ELK(十一):Elasticsearch架构原理
  2. MediaWiki安装
  3. Java基础之移位运算,为什么高位补1?
  4. 【整理】SAP PM工厂维护模块初识
  5. Linux环境下压缩与解压命令大全
  6. 《大话设计模式》——外观模式
  7. 百度推出海外版网盘:竟免费不限速
  8. python的__name__
  9. Ubuntu增加Swap分区大小
  10. MySQL报错113_连接 MySQL 报错'NoneType' object has no attribute '__getitem__'
  11. 极域课堂管理系统软件V6.0 2016 豪华版
  12. SSL/TLS 单双向认证代码示例
  13. codevs1013 求先序排列 string黑科技[三星]
  14. java 空格 char_java中如何判断char是否是空格
  15. 【python 题练】
  16. Android上的RNDIS
  17. read()函数:读文件函数
  18. 【厚积薄发系列】Python项目总结1—后端常驻程序的基本要求
  19. opencv处理图像开始注意的几点
  20. C语言实现复数的几个基本操作(四则运算,初始化,销毁...)

热门文章

  1. 650c公路车推荐_沉睡十年,再获新生——记家中领导trek 650c公路车落地
  2. 面试题:Statement与PrepareStatement的区别
  3. 分布式理论,看完这篇你定能有所获
  4. JDK1.8源码分析:LinkedHashMap与LRU缓存设计思路
  5. 机器学习中数据的归一化处理
  6. 【应用多元统计分析】CH3 多元正态分布
  7. 多元正态分布-参数估计-书后习题回顾总结
  8. matlab 正态分布面积,多元正态分布Matlab,概率区域
  9. 二元正态分布,多元正态分布
  10. Android Compatibility