首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PostgreSQL vacuum 在不使用 full 的情况下,为什么有时也能回收空间

PostgreSQL vacuum 在不使用 full 的情况下,为什么有时也能回收空间

作者头像
AustinDatabases
发布于 2024-01-14 07:00:08
发布于 2024-01-14 07:00:08
35600
代码可运行
举报
文章被收录于专栏:AustinDatabasesAustinDatabases
运行总次数:0
代码可运行

最近是不知道怎么回事,年底了自己的公司,群里都在关于磁盘的空间部分,MySQL怼完架构师,PostgreSQL 也让我想起曾经有一个资深的架构提出一个问题,PostgreSQL 不非要使用 vacuum full 就能回收空间的谣言,也让我给怼了一顿。所以今天说说这个问题,众所周知vauum full的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2024-01-10 01:24:00.771 EST [1575] psql 00000 client backend test VACUUM STATEMENT:  vacuum full test;
2024-01-10 01:24:00.771 EST [1491]  00000 stats collector   DEBUG:  received inquiry for database 58209
2024-01-10 01:24:00.771 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/global.stat"
2024-01-10 01:24:00.771 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/db_58209.stat"
2024-01-10 01:24:00.772 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/db_0.stat"
2024-01-10 01:24:00.795 EST [1487]  00000 background writer   DEBUG:  snapshot of 1+0 running transaction ids (lsn 7/5C0165A0 oldest xid 878886 latest complete 878885 next xid 878887)
2024-01-10 01:24:00.797 EST [1575] psql 00000 client backend test VACUUM DEBUG:  vacuuming "public.test"
2024-01-10 01:24:00.798 EST [1575] psql 00000 client backend test VACUUM DEBUG:  "test": found 0 removable, 3 nonremovable row versions in 1 pages
2024-01-10 01:24:00.798 EST [1575] psql 00000 client backend test VACUUM DETAIL:  0 dead row versions cannot be removed yet.
 CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
2024-01-10 01:24:00.798 EST [1575] psql 00000 client backend test VACUUM DEBUG:  drop auto-cascades to type pg_temp_58574
2024-01-10 01:24:00.798 EST [1575] psql 00000 client backend test VACUUM DEBUG:  drop auto-cascades to type pg_temp_58574[]
2024-01-10 01:24:00.805 EST [1575] psql 00000 client backend test VACUUM LOG:  duration: 34.487 ms
2024-01-10 01:24:01.030 EST [1576]  00000 autovacuum worker   DEBUG:  autovacuum: processing database "test"
2024-01-10 01:24:01.030 EST [1491]  00000 stats collector   DEBUG:  received inquiry for database 58209
2024-01-10 01:24:01.030 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/global.stat"
2024-01-10 01:24:01.030 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/db_58209.stat"
2024-01-10 01:24:01.030 EST [1491]  00000 stats collector   DEBUG:  writing stats file "pg_stat_tmp/db_0.stat"
2024-01-10 01:24:01.057 EST [1483]  00000 postmaster   DEBUG:  server process (PID 1576) exited with exit code 0

这里我们在PG14 版本中,运行一下这个命令,然后将PG的日志也模拟成MySQL 的genernal log 的方式,上面就是我们记录后整体的操作,这里蓝色的部分是我标记,其中主要的功能如下

在PG接受到你要进行vacuum full 操作的时候,他会针对你要操作的表的统计信息先进行数据的写入,并且要对这个表进行快照,来发现这个表是否正在被事务占用,并且要记录当前在使用他的事务的ID信息,如果此时没有事务对这个表进行操作,则他就开始针对表的一些物理特性进行分析比如到底有多少行,行版本中的live and dead 的情况。

同时会生成临时表来对数据进行周转,在周转完毕后临时表会被清理掉,然后在将刚才所做的镜像的信息恢复到新的表上,整体的处理完毕。

当然与其他数据库如optimize table 的mysql一样,如果此时表被其他的事务占用,比如在插入数据,那么此时vacuum full 会无法执行,或等待锁释放获得锁在进行,或直接在配置的等待锁超时的设置下,直接跳出执行失败。

不过说到这里还没有说到主题,就是为什么vacuum 有的时候也能达到vacuum full的功能,运行完毕,磁盘空间释放给操作系统。实际在PostgreSQL 操作中会对于vacuum 操作中调用freeSpaceMapVacuum中的函数来通过页面的偏移码来进行数据页面的释放,而vacuum本身会对页面的偏移码进行改变,因为每个页面都有最大偏移量的标记,这个部分在每个页面的最尾部存储本页的偏移量,而当vacuum 对于页面的偏移量进行更改后,会对于当前的数据文件进行判断是否调用释放空间的功能来释放空间,这里在调用中会会对于FSM文件来进行维护,对于页面空闲空间的数据的重新写入,并检查空间空间的位图。

所以如果通过vacuum 来操作表后,发现表空间被释放了,那说明你有效数据后面在合并数据块后,都是没有数据存在,没有数据存在就可以释放页尾后面的数据空间,所以拜托某些“架构师” 不要在说 vacuum 也能释放空间,是的他能但是你说的那个能你说的他能就差你买一个500万的彩票。

下面是vacuum.c 和 freespace.c 两个关于执行vacuum也能释放空间的部分代码。

下面这段代码的大致注释:

1 在客户运行vacuum 命令时根据参数来判断输入的参数并根据参数判断是 vacuum full or 其他,并且开启一个事务,用vacuum open relation 的函数,获取相关表结构,并且针对命令来对相关的表进行加锁的工作,不同的模式使用不同的锁来应对,在此还需要判断当前操作的用户是否对表有权限操作,并且判断表的类型是否是用户表等不是临时表,如果这些都不符合则自动报错退,但如果是分区表则会降级为 vacuum analyze 的操作,基于分区表的一些特性,是不能对根表进行除analyze 以外的操作。更多详细操作还请参看源代码,相关代码为pg14 代码与网上展示的代码可能有出入。

2 FSM 部分代码是一个实现空间映射搜索的函数,通过一个循环从FSM根地址进行搜索空闲的空间,通过将FSM 读取到内存缓冲区的方式,用fsm_readbuffer的函数来对表进行扫描,在上传后,对于上传你的部分进行一个锁定,此时不能进行DDL 相关的操作,并且通过fsm_search_avail来鉴别空闲的位置,最终确定 fsm_get_max_avail 函数来确认缓冲区中最大的可用的空闲的空间,周而复始的,遍历完毕。

vacuum.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static bool
vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
{
 LOCKMODE lmode;
 Relation rel;
 LockRelId lockrelid;
 Oid   toast_relid;
 Oid   save_userid;
 int   save_sec_context;
 int   save_nestlevel;

 Assert(params != NULL);

 /* Begin a transaction for vacuuming this relation */
 StartTransactionCommand();

 if (!(params->options & VACOPT_FULL))
 {
  
  LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
  MyProc->statusFlags |= PROC_IN_VACUUM;
  if (params->is_wraparound)
   MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
  ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
  LWLockRelease(ProcArrayLock);
 }


 PushActiveSnapshot(GetTransactionSnapshot());


 CHECK_FOR_INTERRUPTS();

 
 lmode = (params->options & VACOPT_FULL) ?
  AccessExclusiveLock : ShareUpdateExclusiveLock;


 rel = vacuum_open_relation(relid, relation, params->options,
          params->log_min_duration >= 0, lmode);


 if (!rel)
 {
  PopActiveSnapshot();
  CommitTransactionCommand();
  return false;
 }


 if (!vacuum_is_relation_owner(RelationGetRelid(rel),
          rel->rd_rel,
          params->options & VACOPT_VACUUM))
 {
  relation_close(rel, lmode);
  PopActiveSnapshot();
  CommitTransactionCommand();
  return false;
 }


 if (rel->rd_rel->relkind != RELKIND_RELATION &&
  rel->rd_rel->relkind != RELKIND_MATVIEW &&
  rel->rd_rel->relkind != RELKIND_TOASTVALUE &&
  rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 {
  ereport(WARNING,
    (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
      RelationGetRelationName(rel))));
  relation_close(rel, lmode);
  PopActiveSnapshot();
  CommitTransactionCommand();
  return false;
 }


 if (RELATION_IS_OTHER_TEMP(rel))
 {
  relation_close(rel, lmode);
  PopActiveSnapshot();
  CommitTransactionCommand();
  return false;
 }


 if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 {
  relation_close(rel, lmode);
  PopActiveSnapshot();
  CommitTransactionCommand();
  /* It's OK to proceed with ANALYZE on this table */
  return true;
 }


 lockrelid = rel->rd_lockInfo.lockRelId;
 LockRelationIdForSession(&lockrelid, lmode);


 if (params->index_cleanup == VACOPTVALUE_UNSPECIFIED)
 {
  StdRdOptIndexCleanup vacuum_index_cleanup;

  if (rel->rd_options == NULL)
   vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
  else
   vacuum_index_cleanup =
    ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;

  if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
   params->index_cleanup = VACOPTVALUE_AUTO;
  else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
   params->index_cleanup = VACOPTVALUE_ENABLED;
  else
  {
   Assert(vacuum_index_cleanup ==
       STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
   params->index_cleanup = VACOPTVALUE_DISABLED;
  }
 }


 if (params->truncate == VACOPTVALUE_UNSPECIFIED)
 {
  if (rel->rd_options == NULL ||
   ((StdRdOptions *) rel->rd_options)->vacuum_truncate)
   params->truncate = VACOPTVALUE_ENABLED;
  else
   params->truncate = VACOPTVALUE_DISABLED;
 }


 if ((params->options & VACOPT_PROCESS_TOAST) != 0 &&
  (params->options & VACOPT_FULL) == 0)
  toast_relid = rel->rd_rel->reltoastrelid;
 else
  toast_relid = InvalidOid;


 GetUserIdAndSecContext(&save_userid, &save_sec_context);
 SetUserIdAndSecContext(rel->rd_rel->relowner,
         save_sec_context | SECURITY_RESTRICTED_OPERATION);
 save_nestlevel = NewGUCNestLevel();

 
 if (params->options & VACOPT_FULL)
 {
  ClusterParams cluster_params = {0};

  /* close relation before vacuuming, but hold lock until commit */
  relation_close(rel, NoLock);
  rel = NULL;

  if ((params->options & VACOPT_VERBOSE) != 0)
   cluster_params.options |= CLUOPT_VERBOSE;

  /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
  cluster_rel(relid, InvalidOid, &cluster_params);
 }
 else
  table_relation_vacuum(rel, params, vac_strategy);


 AtEOXact_GUC(false, save_nestlevel);


 SetUserIdAndSecContext(save_userid, save_sec_context);


 if (rel)
  relation_close(rel, NoLock);

 
 PopActiveSnapshot();
 CommitTransactionCommand();

 
 if (toast_relid != InvalidOid)
  vacuum_rel(toast_relid, NULL, params);

 
 UnlockRelationIdForSession(&lockrelid, lmode);


 return true;
}

freespace.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static BlockNumber
fsm_search(Relation rel, uint8 min_cat)
{
 int   restarts = 0;
 FSMAddress addr = FSM_ROOT_ADDRESS;

 for (;;)
 {
  int   slot;
  Buffer  buf;
  uint8  max_avail = 0;

 
  buf = fsm_readbuf(rel, addr, false);


  if (BufferIsValid(buf))
  {
   LockBuffer(buf, BUFFER_LOCK_SHARE);
   slot = fsm_search_avail(buf, min_cat,
         (addr.level == FSM_BOTTOM_LEVEL),
         false);
   if (slot == -1)
    max_avail = fsm_get_max_avail(BufferGetPage(buf));
   UnlockReleaseBuffer(buf);
  }
  else
   slot = -1;

  if (slot != -1)
  {
  
   if (addr.level == FSM_BOTTOM_LEVEL)
    return fsm_get_heap_blk(addr, slot);

   addr = fsm_get_child(addr, slot);
  }
  else if (addr.level == FSM_ROOT_LEVEL)
  {
 
   return InvalidBlockNumber;
  }
  else
  {
   uint16  parentslot;
   FSMAddress parent;


   parent = fsm_get_parent(addr, &parentslot);
   fsm_set_and_search(rel, parent, parentslot, max_avail, 0);

   
   if (restarts++ > 10000)
    return InvalidBlockNumber;

  
   addr = FSM_ROOT_ADDRESS;
  }
 }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AustinDatabases 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Postgresql源码(32)vacuum freeze起始位点逻辑分析
https://blog.csdn.net/jackgo73/article/details/122700702
mingjie
2022/05/12
3300
Postgresql源码(32)vacuum freeze起始位点逻辑分析
【Postgresql】VACUUM 垃圾回收
原始英文文档:PostgreSQL: Documentation: 15: VACUUM
阿东
2022/11/24
2.2K0
openGauss/PostgreSQL vacuum full源码解析
pg和opengauss通过vacuum过程来清理无效的元组,普通的vacuum只清理死元组,释放的页面空闲空间新元组可以使用,但不会反映到磁盘上,而vacuum full会进行重组,保留页面最小大小,释放磁盘空间。
数据库架构之美
2020/11/05
1.2K0
PostgreSql Postgresql 监控你说了不算,谁说了算 ? (5 整理的一些脚本)
一写就写到了第五期,有点写连续剧的味道,可能会有第六期,我想是,今天的内容并不是某些工具,其实工具也是根据数据库的原理,通过各种方式获得数据。那怎么通过PG中的系统表来获得数据就是这期的重点。
AustinDatabases
2020/07/16
7510
PostgreSql  Postgresql 监控你说了不算,谁说了算 ? (5  整理的一些脚本)
Postgresql垃圾回收原理分析
间隔删除数据,使用ctid(页面号,lp号)作为条件,发现数据并没有真正的从页面中删除
mingjie
2022/05/12
8890
Postgresql垃圾回收原理分析
PostgreSQL从小白到高手教程 - 第44讲:pg流复制部署
PostgreSQL从小白到专家,是从入门逐渐能力提升的一个系列教程,内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容,希望对热爱PG、学习PG的同学们有帮助,欢迎持续关注CUUG PG技术大讲堂。
用户5892232
2024/02/02
5730
PostgreSQL从小白到高手教程 - 第44讲:pg流复制部署
PostgreSQL 怎么通过vacuum 加速事务ID回收的速度 (翻译)
经历上次transaction id 回收报警的问题后,除了上次总结后,发现对于transaction id 的回收的问题还是处于一个急需在学习的过程,所以有了下面这篇翻译的文字。
AustinDatabases
2022/07/13
9820
PostgreSQL 怎么通过vacuum 加速事务ID回收的速度 (翻译)
PostgreSQL体系架构介绍
PostgreSQL是最像Oracle的开源数据库,我们可以拿Oracle来比较学习它的体系结构,比较容易理解。PostgreSQL的主要结构如下:
用户8006012
2022/07/26
2.5K0
Postgresql存储结构
如果阅读过手册一定听过postgresql cluster的概念,第一次听到这个概念可能都会有一些困惑。cluster在安装数据库时,由initdb工具生成,initdb后产生的pgdata文件夹可以理解为cluster的物理存储结构。数据库启动、停止时pg_ctl -D参数指定的文件夹即cluster文件夹,所以一个PG Server可以运行在一个PG Cluster上。
mingjie
2022/05/12
1.4K0
Postgresql存储结构
进阶数据库系列(二十一):PostgreSQL 数据目录同步工具 pg_rewind
pg_rewind 相比 pg_basebackup 和 rsync 这样的工具来说,优势是它不需要从源目录拷贝所有的数据文件,而是会对比时间线发生偏离的点,只拷贝变化过的文件,这样对于数据量很大的情况下速度更快。
民工哥
2023/08/22
2.1K0
进阶数据库系列(二十一):PostgreSQL 数据目录同步工具 pg_rewind
【赵渝强老师】史上最详细的PostgreSQL体系架构介绍
PostgreSQL是最像Oracle的开源数据库,我们可以拿Oracle来比较学习它的体系结构,比较容易理解。PostgreSQL的主要结构如下:
赵渝强老师
2024/08/11
1.1K0
【赵渝强老师】史上最详细的PostgreSQL体系架构介绍
PostgreSQL集群篇——PostgreSQL的配置文件解析
日常中我们进行安装PostgreSQL后都需要对其进行配置基础配置,以便其能有效发挥出服务器的性能,下面是我进行整理后的postgresql.conf配置文件的相关注释,方便大家对于各个属性进行熟悉。
cn華少
2021/07/27
4.1K0
史上最全PostgreSQL体系结构
墨墨导读:本文主要从日志文件、参数文件、控制文件、数据文件、redo日志(WAL)、后台进程这六个方面来讨论PostgreSQL的结构。
数据和云
2019/07/22
4.2K0
史上最全PostgreSQL体系结构
【DB宝92】PG高可用之Citus分布式集群搭建及使用
Citus是Postgres的开源扩展,将Postgres转换成一个分布式数据库,在集群的多个节点上分发数据和查询,具有像分片、分布式SQL引擎、复制表和分布式表等特性。
AiDBA宝典
2022/02/23
4.2K0
【DB宝92】PG高可用之Citus分布式集群搭建及使用
【DB宝91】PG高可用之主从流复制+keepalived 的高可用
通过keepalived 来实现 PostgreSQL 数据库的主从自动切换,以达到高可用。当主节点宕机时,从节点可自动切换为主节点,继续对外提供服务。
AiDBA宝典
2022/02/23
3K0
【DB宝91】PG高可用之主从流复制+keepalived 的高可用
Greenplum 7 新特性整理
参考:https://www.xmmup.com/zaidockerzhongkuaisutiyangreenplum-7-0-0.html
AiDBA宝典
2023/10/16
1.5K0
Greenplum 7 新特性整理
Postgresql源码(27)Btree索引相关系统表和整体结构
《Postgresql源码(30)Postgresql索引基础B-linked-tree》
mingjie
2022/05/12
7350
从零开始学PostgreSQL (二): 配置文件
PostgreSQL 使用三个主要的配置文件来控制服务器的行为、网络访问和用户映射。下面是对这三个文件的详细介绍:
DBA实战
2024/09/06
8240
从零开始学PostgreSQL (二): 配置文件
PostgreSQL创建表分析
脚本准备 创建表的脚本 CREATE DATABASE sampledb OWNER perrynzhou; GRANT ALL PRIVILEGES ON DATABASE sampledb TO perrynzhou; 数据登录脚本 psql -h 127.0.0.1 -d sampledb sampledb=# CREATE TABLE stu_xx_01(NAME TEXT NOT NULL,AGE INT NOT NULL); 表创建分析 表创建过程概述 服务进程接受SQ
用户4700054
2022/08/17
2K0
从零开始学PostgreSQL (九):任务进度报告
PostgreSQL 提供了一系列的工具和视图来帮助数据库管理员监控各种维护任务的进度。
DBA实战
2024/09/06
2290
从零开始学PostgreSQL (九):任务进度报告
推荐阅读
相关推荐
Postgresql源码(32)vacuum freeze起始位点逻辑分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档