6.6 完整清理(FULL VACUUM)

虽然并发清理对于运维至关重要,但光有它还不够。比如,即使删除了许多死元组,也无法压缩表大小的情况。

图6.8给出了一个极端的例子。假设一个表由三个页面组成,每个页面包含六条元组。执行以下DELETE命令以删除元组,并执行VACUUM命令以移除死元组:

图6.8 并发清理的缺陷示例图6.8 并发清理的缺陷示例

testdb=# DELETE FROM tbl WHERE id % 6 != 0;
testdb=# VACUUM tbl;

死元组虽然都被移除了,但表的尺寸没有减小。 这种情况既浪费了磁盘空间,又会对数据库性能产生负面影响。 例如在上面的例子中,当读取表中的三条元组时,必须从磁盘加载三个页面。

为了解决这种情况,PostgreSQL提供了完整清理 模式。 图6.9概述了该模式。

图6.9 完整清理模式概述图6.9 完整清理模式概述

  1. 创建新的表文件:见图6.9(1) 当对表执行VACUUM FULL命令时,PostgreSQL首先获取表上的AccessExclusiveLock锁,并创建一个大小为8 KB的新的表文件。 AccessExclusiveLock锁不允许任何其他访问。
  2. 将活元组复制到新表:见图6.9(2) PostgreSQL只将旧表文件中的活元组复制到新表中。
  3. 删除旧文件,重建索引,并更新统计信息,FSM和VM,见图6.9(3) 复制完所有活元组后,PostgreSQL将删除旧文件,重建所有相关的表索引,更新表的FSM和VM,并更新相关的统计信息和系统视图。

完整清理的伪代码如下所示:

伪代码:完整清理

(1)     FOR each table
(2)         获取表上的AccessExclusiveLock锁
(3)         创建一个新的表文件
(4)         FOR 每个活元组 in 老表
(5)             将活元组拷贝到新表中
(6)             如果有必要,冻结该元组。
            END FOR
(7)         移除旧的表文件
(8)         重建所有索引
(9)         更新FSM与VM
(10)        更新统计信息
            释放AccessExclusiveLock锁
        END FOR
(11)    移除不必要的clog文件

使用VACUUM FULL命令时,应当考虑两点。

  1. 当执行完整清理时,没有人可以访问(读/写)表。
  2. 最多会临时使用两倍于表的磁盘空间;因此在处理大表时,有必要检查剩余磁盘容量。

什么时候该使用VACUUM FULL

不幸的是,并没有关于什么时候该执行VACUUM FULL的最佳实践。但是扩展pg_freespacemap可能会给出很好的建议。

以下查询给出了表的平均空间空闲率。

testdb=# CREATE EXTENSION pg_freespacemap;
CREATE EXTENSION

testdb=# SELECT count(*) as "number of pages",
       pg_size_pretty(cast(avg(avail) as bigint)) as "Av. freespace size",
       round(100 * avg(avail)/8192 ,2) as "Av. freespace ratio"
       FROM pg_freespace('accounts');
 number of pages | Av. freespace size | Av. freespace ratio 
-----------------+--------------------+---------------------
            1640 | 99 bytes           |                1.21
(1 row)

从上面的结果可以看出,没有多少空闲空间。

如果删除几乎所有的元组,并执行VACUUM命令,则可以发现每个页面几乎都是空的。

testdb=# DELETE FROM accounts WHERE aid %10 != 0 OR aid < 100;
DELETE 90009

testdb=# VACUUM accounts;
VACUUM

testdb=# SELECT count(*) as "number of pages",
       pg_size_pretty(cast(avg(avail) as bigint)) as "Av. freespace size",
       round(100 * avg(avail)/8192 ,2) as "Av. freespace ratio"
       FROM pg_freespace('accounts');
 number of pages | Av. freespace size | Av. freespace ratio 
-----------------+--------------------+---------------------
            1640 | 7124 bytes         |               86.97
(1 row)

以下查询检查特定表中每个页面的自由空间占比。

testdb=# SELECT *, round(100 * avail/8192 ,2) as "freespace ratio"
                FROM pg_freespace('accounts');
 blkno | avail | freespace ratio 
-------+-------+-----------------
     0 |  7904 |           96.00
     1 |  7520 |           91.00
     2 |  7136 |           87.00
     3 |  7136 |           87.00
     4 |  7136 |           87.00
     5 |  7136 |           87.00
....

执行VACUUM FULL后会发现表被压实了。

testdb=# VACUUM FULL accounts;
VACUUM
testdb=# SELECT count(*) as "number of blocks",
       pg_size_pretty(cast(avg(avail) as bigint)) as "Av. freespace size",
       round(100 * avg(avail)/8192 ,2) as "Av. freespace ratio"
       FROM pg_freespace('accounts');
 number of pages | Av. freespace size | Av. freespace ratio 
-----------------+--------------------+---------------------
             164 | 0 bytes            |                0.00
(1 row)
下一节:在8.3版本中实现的HOT特性,使得更新行的时候,可以将新行放置在老行所处的同一个数据页中,从而高效地利用索引与表的数据页;HOT特性减少了不必要的清理过程。