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

news/2024/7/9 20:53:37 标签: postgresql, 数据库

这开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题,有需求都可以加群群内,可以解决你的问题。加群请联系 liuaustin3 ,(共1830人左右 1 + 2 + 3 + 4 +5) 4群(360+ 关闭自由申请,新入群的将默认分配达到5群),另欢迎 OpenGauss 的技术人员加入。

2dd11b0ef0d17a37d44d141480dd9688.png

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

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 的情况。

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

8afa76b7f1fd8ddcd30343823fa40ed7.png

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

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

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

cbd08fa8c1db601c6296d51cd997b2d6.png

下面是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

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

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;
  }
 }
}

88918b7bd1ecb3f5eee53cb3783c1f8a.png


http://www.niftyadmin.cn/n/5323969.html

相关文章

GPT编程:运行第一个聊天程序

环境搭建 很多机器学习框架和类库都是使用Python编写的,OpenAI提供的很多例子也是Python编写的,所以为了方便学习,我们这个教程也使用Python。 Python环境搭建 Python环境搭建有很多种方法,我们这里需要使用 Python 3.10 的环境…

第 4 章 链表

文章目录 4.1 链表(Linked List)介绍4.2 单链表的应用实例4.3 单链表面试题(新浪、百度、腾讯)4.4 双向链表应用实例4.4.1 双向链表的操作分析和实现4.4.2 课堂作业和思路提示 4.5 单向环形链表应用场景4.6 单向环形链表介绍4.7 Josephu 问题4.8 Josephu 问题的代码实现 4.1 链…

国家注册信息安全专业人员十五类CISP证书

国家注册信息安全专业人员(Certified Information Security Professiona,简称CISP),是面向党政机关、关键信息基础设施运营单位、各类企事业单位和社会组织以及网络与信息安全企业、测评和咨询服务机构等工作的信息安全人员颁发的…

强化学习应用(三):基于Q-learning算法的无人车配送路径规划(提供Python代码)

一、Q-learning算法介绍 Q-learning是一种强化学习算法,用于解决基于环境的决策问题。它通过学习一个Q-table来指导智能体在不同状态下采取最优动作。下面是Q-learning算法的基本步骤: 1. 定义环境:确定问题的状态和动作空间,并…

服务器经常宕机的原因及解决办法

随着如今互联网信息化时代的不断发展,数据存储和传输在各种网络科技面前也显得越来越重要,对于企业来讲,建站之后服务器的安全稳定是至关重要的选择。那么选择一款好用的服务器愈发重要。 当然,不管是多好的服务器提供商&#xff…

uni-app中实现弹幕的滚动效果

在uni-app中实现弹幕的滚动效果&#xff0c;可以通过以下步骤实现&#xff1a; 在页面中创建一个容器&#xff0c;用于显示弹幕内容。可以使用<view>标签或者其他适合的标签作为容器。 使用CSS样式设置容器的位置和样式&#xff0c;例如设置position: fixed使其固定在页…

【spring源码分析】@Conditional的使用以及分析

Conditional Conditional 一、基本信息二、注解描述三、注解源码四、主要功能五、最佳实践 在Bean上使用在Configuration上使用自定义组合注解 六、时序图七、源码分析八、注意事项九、总结 最佳实践总结源码分析总结 一、基本信息 转载自github&#xff0c;在此作为个人备…

《More Effective C++》学习

条款1&#xff1a;仔细区别 pointers 和 references 引用应该被初始化&#xff0c;指针可以不被初始化。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。指针与引用的另一个重要的不同是指针可以被重新赋值…