PostgreSQL不使用IN-PLACE更新机制,因此按照DELETE和UPDATE命令的设计方式,
每当执行DELETE操作时,它会将现有的元组标记为DEAD,而不是物理删除这些元组。
同样,无论何时执行UPDATE操作,都会将相应的现有元组标记为DEAD并插入新的元组(即UPDATE操作= DELETE + INSERT)。
因此,每个DELETE和UPDATE命令将导致一个DEAD元组,该元组将永远不会使用(除非存在并行事务)。这些无效的元组将导致不必要的额外空间使用,即使有相同或更少的有效记录。这在PostgreSQL中也称为空间膨胀。由于PostgreSQL被广泛用作OLTP类型的关系数据库系统,在该数据库中频繁执行INSERT,UPDATE和DELETE操作,因此将存在许多DEAD元组,因此产生相应的后果。因此,PostgreSQL需要强大的维护机制来处理这些DEAD元组。VACUUM是一个维护过程,负责处理DEAD元组以及一些其他对优化VACUUM操作有用的活动。让我们了解一些稍后将在本博客中使用的术语。
Visibility Map
顾名思义,它维护有关只包含已知对所有活动事务可见的元组的页面的可见性信息。对于每一页,使用一位。如果该位设置为1,则意味着相应页面的所有元组都是可见的。设置为0的位表示给定页面上没有可用空间,并且所有事务都可以看到元组。
为每个关系(表和索引)维护可见性图,并将其与主要关系一起关联,即,如果关系文件节点名称为12345,则将可见性文件存储在并行文件12345_vm中。
Free Space Map
它维护包含有关关系中可用空间的详细信息的可用空间信息。这也存储在与关系主文件并行的文件中,即,如果关系文件节点名称为12345,则可用空间映射文件将存储在并行文件12345_fsm中。
冻结元组
PostgreSQL使用4个字节存储事务ID,这意味着最多可以产生20亿个事务。现在考虑此时还是一些元组包含初始事务ID(例如100),然后对于新事务(它使用环绕式事务)说5,事务ID 100将进入未来,并且将无法看到添加的数据/由它修改,即使它实际上是过去的。为了避免这种特殊的事务ID,将指定FrozenTransactionId(等于2)。始终认为该特殊事务ID是过去的,并且对所有事务都是可见的。
VACUUM
VACUUM 主要工作是回收由死元组占用的存储空间。回收的存储空间不会返回给操作系统,它们只是在同一页内被碎片化,因此它们只是可供将来在同一表中插入数据时重新使用。当VACUUM 操作在特定的表上进行时,可以在同一表上同时执行其他读/写操作,因为不对特定表执行排它锁。在没有指定表名的情况下,将对数据库的所有表执行VACUUM 操作。VACUUM 操作在ShareUpdateExclusive锁中执行一系列操作:
扫描数据库的所有表(或指定表)的所有页面,以获取所有失效的元组。
如果需要,冻结旧的元组。
删除指向相应DEAD元组的索引元组。
删除与特定表相对应的页面的DEAD元组,然后在页面中重新分配活动元组。
更新可用空间图(FSM)和可见性图(VM)。
如果可能的话,截断最后一页(如果有DEAD元组被释放)。
更新所有相应的系统表。
从上面VACUUM的工作步骤中可以看出,很明显,这是一项非常昂贵的操作,因为它需要处理关系的所有页面。因此,非常需要跳过不需要清理的页面。由于可见性图(VM)给出了页面的信息(如果没有可用空间),因此可以假定不需要VACUUM的相应页面,因此可以安全地跳过此页面。
由于VACUUM无论如何遍历所有页面及其所有元组,因此它有机会做其他重要的任务来冻结合格的元组。
VACUUM FULL
如上一节所述,即使VACUUM删除了所有DEAD元组并对该页面进行了碎片整理以备将来使用,但由于实际上并未向操作系统释放空间,因此它无助于减少表的总存储量。假设表tbl1的总存储量已达到1.5GB,并且在这1GB的死元组已占用其中,那么在VACUUM之后,还有大约1GB的空间可用于进一步的元组插入,但是总存储量仍为1.5GB。
VACUUM FULL通过实际释放空间并将其返回给操作系统来解决此问题。但这是有代价的。与VACUUM不同,FULL VACUUM不允许并行操作,因为它对获得FULL VACUUM的关系进行排他锁定。步骤如下:
对关系对象采取排他锁。
创建一个并行的空存储文件。
将所有活动元组从当前存储复制到新分配的存储。
然后释放原始存储。
释放锁。
因此,从步骤中也可以清楚地看出,它将仅存储剩余数据所需的存储空间。
Auto VACUUM
PostgreSQL支持手动运行VACUUM。每次VACUUM唤醒时(默认情况下为1分钟),它将调用多个work进程(取决于配置autovacuum_worker进程)。
Auto-vacuum工作进程对相应的指定表同时执行VACUUM处理。由于VACUUM不会对表进行任何排他锁,因此它不会(或影响很小)影响数据库工作。
Auto-VACUUM的配置应基于数据库的使用模式进行。它不应该太频繁(因为可能没有死元组或死元组太少,否则会浪费开销)或延迟太多(它将导致大量死元组在一起,从而导致表膨胀)。
VACUUM或VACUUM FULL
理想情况下,数据库应用程序的设计应该不需要VACUUM FULL。如上所述,VACUUM FULL 将重新创建存储空间并放回数据,因此,如果死元组的数量较少,则将立即重新创建存储空间以放回所有原始数据。同样,由于VACUUM FULL 在表上具有排他锁,因此它会阻塞相应表上的所有操作。因此,执行VACUUM FULL有时会减慢整个数据库的速度。
总之,除非知道大多数存储空间是由无效元组占用的,否则应避免使用VACUUM FULL。PostgreSQL扩展pg_freespacemap可用于获得有关可用空间的合理提示。
让我们看一个解释的VACUUM过程的例子。
首先,让我们创建一个表demo1:
postgres=# create table demo1(id int, id2 int); CREATE TABLE
并在其中插入一些数据:
postgres=# insert into demo1 values(generate_series(1,10000), generate_series(1, 10000)); INSERT 0 10000 postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1'); npages | average_freespace_ratio --------+------------------------- 45 | 0.00 (1 row)
现在,让我们删除数据:
postgres=# delete from demo1 where id%2=0; DELETE 5000
并运行手动vacuum:
postgres=# vacuum demo1; VACUUM postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1'); npages | average_freespace_ratio --------+------------------------- 45 | 45.07 (1 row)
现在可以使用此可用空间供PostgreSQL重用,但是如果要将该空间释放给操作系统,请运行:
postgres=# vacuum full demo1; VACUUM postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1'); npages | average_freespace_ratio --------+------------------------- 23 | 0.00 (1 row)
结论
这是VACUUM过程如何工作的简短示例。幸运的是,由于采用了autovacuum,在大多数情况下以及在常见的PostgreSQL环境中,您都无需考虑这一点,因为它是由引擎本身管理的。
文章翻译自:https://severalnines.com/database-blog/overview-vacuum-processing-postgresql




