vacuum 和 autovacuum

概述

VACUUM 收回由死亡元组占用的存储空间。在通常的 PostgreSQL 操作中,被删除或者被更新废弃的元组并没有在物理上从它们的表中移除(这是 PostgreSQL 独特的 MVCC 实现方式所然),它们将一直存在,直到一次 VACUUM 被执行,需要引入 vacuum 清理留下的死元祖,空间复用。

image

==注意:vacuum 命令不能在事务中执行,vacuum 不加参数是对所有表进行操作==

vacuum 分类

对于 VACUUM 清理死元组,它提供了两种模式:VACUUM 和 VACUUL FULL。这两种模式,都可以实现清理表中死元组的过程,但在底层实现即具体处理细节上有所不同

vacuum

VACUUM 的主要工作是回收被标记为死元组占用的存储空间,不过它不会将回收的空间归还给操作系统,而是在同一页中进行碎片整理,因此它们只是可供将来在同一表中插入数据时重用。由于 VACUUM 操作特定表时,使用的是共享锁,所以同时刻其他读/写操作也可以同时在一个表上进行,而不会被阻塞。

VACUUM 删除指定表中死元组的过程主要分为以下 3 ​个步骤:

获取死元组,并删除死元组指向索引元组

在该过程里,VACUUM 会从指定的表中依次获取每个表。在表文件底层结构布局文档中描述过,当表文件大于 `1GB`​,会重新创建一个表文件,该表文件的命名方式是“表 `OID` + `.`​” + “从 `1` ​开始递增的序号”。所以这里强调“从指定表获取每个表”。

之后为表获取 ShareUpdateExclusiveLock 锁,该锁是一个共享锁,它允许从其它事务中读取数据,与大多数查询不冲突,但是不能创建索引、不能创建触发器、不能刷新物化视图、不能做 DDL 等。对于“VACUUM (non-FULL)ANALYZECREATE INDEX CONCURRENTLY”这几个操作都将使用该锁。锁定义如下:
#define NoLock					0
#define AccessShareLock			1	/* SELECT */
#define RowShareLock			2	/* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock		3	/* INSERT, UPDATE, DELETE */
#define ShareUpdateExclusiveLock 4	/* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY */
#define ShareLock				5	/* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock	6	/* like EXCLUSIVE MODE, but allows ROW SHARE */
#define ExclusiveLock			7	/* blocks ROW SHARE/SELECT...FOR UPDATE */
#define AccessExclusiveLock		8	/* ALTER TABLE, DROP TABLE, VACUUM FULL,
									 * and unqualified LOCK TABLE */
#define MaxLockMode				8
然后将扫描所有页(page)以获取所有的死元组,并在必要的时候冻结旧元组。如果存在,则删除指向各自死元组的索引元组。当 PostgreSQL 在扫描目标表所有页以获取死元组的时候,它会将得到的所有死元组构建成一个列表。该列表存储在 #maintenance_work_mem#​缓存中。该缓存默认大小是 64MB(==标准化为 64MB==)最小值不能低于 1MB。

之后,PostgreSQL 将通过引用该缓存(maintenance_work_mem)中的死元组列表来删除索引元组数据。当缓存(maintenance_work_mem)已满,且扫描未完成时,则 PostgreSQL 会进入第(2)个步骤,执行该步骤的操作;之后返回到步骤(1)中,继续执行扫描剩余页,获取死元组,并将死元组放入缓存(maintenance_work_mem)中。

删除死元组,更新 FSM 和 VM

删除缓存中的所有死元组,并重新分配(排列)该表页中的剩余活元组,以进行碎片整理。之后,更新目标表的“可见性映射(`VM`)”文件和“可用空间映射(`FSM`)”文件。

fsm 文件

名字以\_fsm 结尾的文件是数据文件对应的 FSM(free space map)文件,==用位图方式来标识哪些 block 是空闲的==,记录了 max\_fsm\_pages 和  max\_fsm\_relations,其中 PG 提供了 pg\_freespace 函数来对查看对应 blk 中可以利用的空余的空间,第一次对表进行 vacuum 时才会建立 fsm 文件,FSM 文件使用的是树形结构来记录,空闲的页块,通过代码来看也是从左到右的查找

vm 文件

以\_vm 结尾的文件是数据文件对应的 VM(visibility map),在做多版本并发控制时是通过在元组头上标识“已无效”来实现删除或更新的,最后通过 VACUUM 功能来清理无效数据回收空闲空间,visual map 主要的作用为,在 update, delete 行后,这一行 tuple 并不会马上被清理掉,而是要通过 vacuum ,autovacuum 等操作将这些死元组清理, vm 文件的主要作用是显示占用的 tuple ,扫描的时候会跳过这些 tuple

更新统计信息和系统目录

当 VACUUM 处理完成后,需要更新目标表(以适应最新的查询优化)以及与 VACUUM 处理相关的一些统计信息(比如 pg_class)和系统目录(如:pg_stat)。

vacuum full

VACUUM FULL 和 VACUUM 最大的区别在于,VACUUM FULL 物理删除了死元组,并且将释放的空间归还给了操作系统。其操作过程也可以划分为 `3` ​个执行步骤。
  1. 首先会为操作的目标表创建一个互斥锁(AccessExclusiveLock​),以阻止外部对该表的任何读/写访问;然后再创建一个与目标表结构完全相同的新表。
  2. 扫描目标表,并将表中的所有活元组复制到新表中。
  3. 删除目标表,并在新表上创建索引,并更新 VM、FSM 和统计信息,以及相关系统表、系统目录等。然后释放互斥锁(AccessExclusiveLock​)。

示例:

image

通过测试可以看出 vacuum full 会删除旧表,创建新表,并将旧表中的所有活元组复制到新创建的表文件中

vacuum 和 vacuum full 的区别

  1. vacuum 不会锁表,只是单纯的回收空间以被重用,被回收的空间一般情况不会被返还给操作系统,仅仅被保留在同一个表中以备重用。
  2. vacuum full 会锁表,会将表的整个内容重写到一个新的磁盘文件中,并且不包含额外的空间,这使得没有被 使用的空间被还给操作系统。

autovacuum

autovacuum 守护进程会在每个 #autovacuum_naptime#​(<u>==vastbase 标准化为 20s==</u>)时间间隔内启动 PostgreSQL 的 autovacuum 工作进程(worker process),并且最多只允许同时运行 #autovacuum_max_workers#​(vastbase 标准化为 5)个工作进程;然后每个工作进程将逐一检查数据库中的每个表,并在需要时,执行 VACUUM 或 ANALYZE 操作。如果需要处理的数据库多于 autovacuum_max_workers,则下一个数据库将会在第一个 worker 结束后立即处理。

每当 postmaster 服务启动之后,系统就会 fork 一个 autovacuum 守护进程, 该守护进程将会周期性地检查指定表(若未指定具体表,则检查系统所有表)中的各块是否存在死元组等相关逻辑处理。和另外几个辅助进程一样,VACUUM 进程的运行状态也会受到 postmaster 服务的监视。postmaster 服务会定期去检测 VACUUM 进程的运行情况,一旦发现该进程不存在,则会立刻重新 fork()一个 autovacuum 辅助进程。

autovacuum 相关参数

  1. 清理基本阈值:#autovacuum_vacuum_threshold#​,默认值为 50 --50 个死元组
  2. 清理缩放系数:#autovacuum_vacuum_scale_factor#​,默认值 0.2
  3. 强制执行 autovacuum 的年龄阈值:#autovacuum_freeze_max_age#​,默认值 4000000000

触发条件

  1. 清理阈值 (死元组数)= 清理基本阈值(autovacuum_vacuum_threshold) + 清理缩放系数(autovacuum_vacuum_scale_factor) * 元组数。 --该结果为死亡元组数量
  2. 当数据库年龄大于 autovacuum_freeze_max_age 将强制执行 vacuum,无论 autovacuum 是否开启

vacuum 的集中关键变体

  1. vacuum full : 执行期间需要获取对应表的独占锁,阻塞其他客户端的读写操作。该模式下会将对应表数据重新写入一个新的表空间文件,最后替换为新的表文件,这种方式下可以回收 dead tuple 空间并释放给操作系统。该操作执行消耗是比较大的,且耗时的。
  2. vacuum freeze : 使用一种激进的方式冻结元祖,相当于把参数 vacuum_freeze_min_age 、 vacuum_freeze_table_age 设置为 0。该模式下 Full 参数指定是多余的,该操作执行消耗同样是比较大的,且耗时的
  3. vacuum verbose : 执行期间不需要获取对应表的独占锁,允许其他客户端的并发读写操作。该模式下仅仅会将 dead tuple 空间进行回收并释放给数据库,并不会释放给操作系统,vacuum 期间打印每张表详细的垃圾回收记录
  4. vacuum analyze : 执行期间不需要获取对应表的独占锁,允许其他客户端的并发读写操作,执行完毕 vacuum 后会再次执行 analyza 重新采集相关表的统计信息。该模式下仅仅会将 dead tuple 空间进行回收并释放给数据库,并不会释放给操作系统
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...