概述
VACUUM 收回由死亡元组占用的存储空间。在通常的 PostgreSQL 操作中,被删除或者被更新废弃的元组并没有在物理上从它们的表中移除(这是 PostgreSQL 独特的 MVCC 实现方式所然),它们将一直存在,直到一次 VACUUM 被执行,需要引入 vacuum 清理留下的死元祖,空间复用。
==注意:vacuum 命令不能在事务中执行,vacuum 不加参数是对所有表进行操作==
vacuum 分类
对于 VACUUM 清理死元组,它提供了两种模式:VACUUM 和 VACUUL FULL。这两种模式,都可以实现清理表中死元组的过程,但在底层实现即具体处理细节上有所不同
vacuum
VACUUM 的主要工作是回收被标记为死元组占用的存储空间,不过它不会将回收的空间归还给操作系统,而是在同一页中进行碎片整理,因此它们只是可供将来在同一表中插入数据时重用。由于 VACUUM 操作特定表时,使用的是共享锁,所以同时刻其他读/写操作也可以同时在一个表上进行,而不会被阻塞。
VACUUM 删除指定表中死元组的过程主要分为以下 3
个步骤:
获取死元组,并删除死元组指向索引元组
在该过程里,VACUUM 会从指定的表中依次获取每个表。在表文件底层结构布局文档中描述过,当表文件大于 `1GB`,会重新创建一个表文件,该表文件的命名方式是“表 `OID` + “`.`” + “从 `1` 开始递增的序号”。所以这里强调“从指定表获取每个表”。
之后为表获取 ShareUpdateExclusiveLock 锁,该锁是一个共享锁,它允许从其它事务中读取数据,与大多数查询不冲突,但是不能创建索引、不能创建触发器、不能刷新物化视图、不能做 DDL 等。对于“VACUUM (non-FULL)、ANALYZE、CREATE 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` 个执行步骤。
- 首先会为操作的目标表创建一个互斥锁(
AccessExclusiveLock
),以阻止外部对该表的任何读/写访问;然后再创建一个与目标表结构完全相同的新表。 - 扫描目标表,并将表中的所有活元组复制到新表中。
- 删除目标表,并在新表上创建索引,并更新 VM、FSM 和统计信息,以及相关系统表、系统目录等。然后释放互斥锁(
AccessExclusiveLock
)。
示例:
通过测试可以看出 vacuum full 会删除旧表,创建新表,并将旧表中的所有活元组复制到新创建的表文件中
vacuum 和 vacuum full 的区别
- vacuum 不会锁表,只是单纯的回收空间以被重用,被回收的空间一般情况不会被返还给操作系统,仅仅被保留在同一个表中以备重用。
- 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 相关参数
- 清理基本阈值:#autovacuum_vacuum_threshold#,默认值为 50 --50 个死元组
- 清理缩放系数:#autovacuum_vacuum_scale_factor#,默认值 0.2
- 强制执行 autovacuum 的年龄阈值:#autovacuum_freeze_max_age#,默认值 4000000000
触发条件
- 清理阈值 (死元组数)= 清理基本阈值(autovacuum_vacuum_threshold) + 清理缩放系数(autovacuum_vacuum_scale_factor) * 元组数。 --该结果为死亡元组数量
- 当数据库年龄大于 autovacuum_freeze_max_age 将强制执行 vacuum,无论 autovacuum 是否开启
vacuum 的集中关键变体
- vacuum full : 执行期间需要获取对应表的独占锁,阻塞其他客户端的读写操作。该模式下会将对应表数据重新写入一个新的表空间文件,最后替换为新的表文件,这种方式下可以回收 dead tuple 空间并释放给操作系统。该操作执行消耗是比较大的,且耗时的。
- vacuum freeze : 使用一种激进的方式冻结元祖,相当于把参数 vacuum_freeze_min_age 、 vacuum_freeze_table_age 设置为 0。该模式下 Full 参数指定是多余的,该操作执行消耗同样是比较大的,且耗时的
- vacuum verbose : 执行期间不需要获取对应表的独占锁,允许其他客户端的并发读写操作。该模式下仅仅会将 dead tuple 空间进行回收并释放给数据库,并不会释放给操作系统,vacuum 期间打印每张表详细的垃圾回收记录
- vacuum analyze : 执行期间不需要获取对应表的独占锁,允许其他客户端的并发读写操作,执行完毕 vacuum 后会再次执行 analyza 重新采集相关表的统计信息。该模式下仅仅会将 dead tuple 空间进行回收并释放给数据库,并不会释放给操作系统
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于