为什么vacuum full也无法回收空间?
今天在处理个磁盘告警时,对一些膨胀率较高的表进行了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也无法回收空间?相关推荐
- PostgreSQL 怎么通过vacuum 加速事务ID回收的速度 (翻译)
此文为翻译文字原文连接在文字最下方 经历上次transaction id 回收报警的问题后,除了上次总结后,发现对于transaction id 的回收的问题还是处于一个急需在学习的过程,所以有了下面 ...
- 使用 diskpart 实现无损数据回收空间再分区
Vista 下的 Diskpart 工具更加的使用强劲,不仅可以轻松地制作出来可引导的移动存储设备分区,而且可以在不损害当前分区数据的前提前,利用空闲容量创建一个新的分区.是不是很令人兴奋,我们从此可 ...
- [原]Oracle删除大表并回收空间的过程
近日在查询某项日志的时候,发现查询非常缓慢,根据以往的经验这是由于某个日志表过大引起的,为了加快查询,决定将大部分的历史数据迁移到另外一个表中,本文主要记录删除这个大表的过程,就解决问题而言还有很多方 ...
- mysql 回收空间_MySQL表的碎片整理和空间回收小结
MySQL表碎片化(Table Fragmentation)的原因 关于MySQL中表碎片化(Table Fragmentation)产生的原因,简单总结一下,MySQL Engine不同,碎片化的原 ...
- oracle 大表删除数据后,回收空间的问题。
在oracle中由于表结构设计不合理或者需要清楚老数据的时候,经常需要对大表数据进行清理. 一般有一下几种方法: 1. 删除大部分数据,留下小部分数据.我们可以把需要保留的数据转移到别的表,然后再把大 ...
- truncate数据后回收空间_Truncate用法详解
前言: 当我们想要清空某张表时,往往会使用truncate语句.大多时候我们只关心能否满足需求,而不去想这类语句的使用场景及注意事项.本篇文章主要介绍truncate语句的使用方法及注意事项. 1.t ...
- oracle 如何回收空间,Oracle回滚段空间回收步骤
是谁"偷偷的"用了那么多空间呢(本来有几十个G的Free硬盘 空间的)? 检验 数据库表空间占用空间情况: SQL> select tablespace_name,sum(b ...
- oracle如何删除可回收归档,Oracle正确删除归档并回收空间的方法
一个Oracle归档日志经常满,表现为/oraarchive 这个文件空间占用100%大家一定抱怨Oracle为何没有归档维护工具,很多人直接删除了事,错了,Oracle有,而且很智能,可以正确的删除 ...
- PostgreSQL vacuum原理一功能与参数
从上篇"PostgreSQL MVCC 源码实现"中,我们知道,PG并没有像Oracle那样的undo来存放旧版本:而是将旧版本直接存放于relation文件中.那么带来的问题就是 ...
最新文章
- 2021年大数据ELK(十一):Elasticsearch架构原理
- MediaWiki安装
- Java基础之移位运算,为什么高位补1?
- 【整理】SAP PM工厂维护模块初识
- Linux环境下压缩与解压命令大全
- 《大话设计模式》——外观模式
- 百度推出海外版网盘:竟免费不限速
- python的__name__
- Ubuntu增加Swap分区大小
- MySQL报错113_连接 MySQL 报错'NoneType' object has no attribute '__getitem__'
- 极域课堂管理系统软件V6.0 2016 豪华版
- SSL/TLS 单双向认证代码示例
- codevs1013 求先序排列 string黑科技[三星]
- java 空格 char_java中如何判断char是否是空格
- 【python 题练】
- Android上的RNDIS
- read()函数:读文件函数
- 【厚积薄发系列】Python项目总结1—后端常驻程序的基本要求
- opencv处理图像开始注意的几点
- C语言实现复数的几个基本操作(四则运算,初始化,销毁...)