6.3 冻结过程

冻结过程有两种模式,依特定条件而择其一执行。为方便起见,将这两种模式分别称为惰性模式(lazy mode)和迫切模式(eager mode)。

并发清理(Concurrent VACUUM) 通常在内部被称为“惰性清理(lazy vacuum) ”。但是,本文中定义的惰性模式是冻结过程(Freeze Processing) 执行的模式。

冻结过程通常以惰性模式运行;但当满足特定条件时,也会以迫切模式运行。在惰性模式下,冻结处理仅使用目标表对应的VM扫描包含死元组的页面。迫切模式相则反,它会扫描所有的页面,无论其是否包含死元组,它还会更新与冻结处理相关的系统视图,并在可能的情况下删除不必要的clog。

第6.3.1和6.3.2节分别描述了这两种模式;第6.3.3节描述了改进后的迫切模式冻结过程。

6.3.1 惰性模式

当开始冻结处理时,PostgreSQL计算freezeLimit_txid,并冻结t_xmin小于freezeLimit_txid的元组。

OldestXmin是当前正在运行的事务中最早的事务标识(txid) 。 举个例子,如果在执行VACUUM命令时,还有其他三个事务正在运行,且其txid分别为100,101,102,那么这里OldestXmin就是100。如果不存在其他事务,OldestXmin 就是执行此VACUUM命令的事务标识。 这里vacuum_freeze_min_age是一个配置参数(默认值为50,000,000)。

图6.3给出了一个具体的例子。这里Table_1由三个页面组成,每个页面包含三条元组。 执行VACUUM命令时,当前txid50,002,500且没有其他事务。在这种情况下,OldestXmin就是50,002,500;因此freezeLimit_txid2500。冻结过程按照如下步骤执行。

图6.3 冻结元组——惰性模式图6.3 冻结元组——惰性模式

  • 第0页: 三条元组被冻结,因为所有元组的t_xmin值都小于freezeLimit_txid。此外,因为Tuple_1是一条死元组,因而在该清理过程中被移除。
  • 第1页: 通过引用可见性映射(从VM中发现该页面所有元组都可见),清理过程跳过了对该页面的清理。
  • 第2页: Tuple_7Tuple_8被冻结,且Tuple_7被移除。

在完成清理过程之前,与清理相关的统计数据会被更新,例如pg_stat_all_tables视图中的n_live_tupn_dead_tuplast_vacuumvacuum_count等字段。

如上例所示,因为惰性模式可能会跳过页面,它可能无法冻结所有需要冻结的元组。

6.3.2 迫切模式

迫切模式弥补了惰性模式的缺陷。它会扫描所有页面,检查表中的所有元组,更新相关的系统视图,并在可能时删除非必需的clog文件与页面。 当满足以下条件时,会执行迫切模式。

在上面的条件中,pg_database.datfrozenxid是系统视图pg_database中的列,并保存着每个数据库中最老的已冻结的事务标识。细节将在后面描述;因此这里我们假设所有pg_database.datfrozenxid的值都是1821(这是在9.5版本中安装新数据库集群之后的初始值)。 vacuum_freeze_table_age是配置参数(默认为150,000,000)。

图6.4给出了一个具体的例子。在表1中,Tuple_1Tuple_7都已经被删除。Tuple_10Tuple_11则已经插入第2页中。执行VACUUM命令时的事务标识为150,002,000,且没有其他事务。因此,OldestXmin=150,002,000freezeLimit_txid=100,002,000。在这种情况下满足了上述条件:因为1821 < (150002000 - 150000000) ,因而冻结过程会以迫切模式执行,如下所示。

(注意,这里是版本9.5或更早版本的行为;最新版本的行为将在第6.3.3节中描述。)

图6.4 冻结旧元组——迫切模式(9.5或更早版本)图6.4 冻结旧元组——迫切模式(9.5或更早版本)

  • 第0页: 即使所有元组都被冻结,也会检查Tuple_2Tuple_3
  • 第1页: 此页面中的三条元组都会被冻结,因为所有元组的t_xmin值都小于freezeLimit_txid。注意在惰性模式下会跳过此页面。
  • 第2页: 将Tuple_10冻结,而Tuple_11没有冻结。

冻结一张表后,目标表的pg_class.relfrozenxid将被更新。 pg_class是一个系统视图,每个pg_class.relfrozenxid列都保存着相应表的最近冻结的事务标识。本例中表1的pg_class.relfrozenxid会被更新为当前的freezeLimit_txid(即100,002,000),这意味着表1中t_xmin小于100,002,000的所有元组都已被冻结。

在完成清理过程之前,必要时会更新pg_database.datfrozenxid。每个pg_database.datfrozenxid列都包含相应数据库中的最小pg_class.relfrozenxid。例如,如果在迫切模式下仅仅对表1做冻结处理,则不会更新该数据库的pg_database.datfrozenxid,因为其他关系的pg_class.relfrozenxid(当前数据库可见的其他表和系统视图)还没有发生变化,如图6.5(1)所示。如果当前数据库中的所有关系都以迫切模式冻结,则数据库的pg_database.datfrozenxid就会被更新,因为此数据库的所有关系的pg_class.relfrozenxid都被更新为当前的freezeLimit txid,如图6.5(2)所示。

图6.5 pg_database.datfrozenxidpg_class.relfrozenxid之间的关系

如何显示pg_class.relfrozenxidpg_database.datfrozenxid

如下所示,第一个查询显示testdb数据库中所有可见关系的relfrozenxid,第二个查询显示testdb数据库的pg_database.datfrozenxld

testdb=# VACUUM table_1;
VACUUM

testdb=# SELECT n.nspname as "Schema", c.relname as "Name", c.relfrozenxid
             FROM pg_catalog.pg_class c
             LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
             WHERE c.relkind IN ('r','')
                   AND n.nspname <> 'information_schema' 
                   AND n.nspname !~ '^pg_toast'
                   AND pg_catalog.pg_table_is_visible(c.oid)
             ORDER BY c.relfrozenxid::text::bigint DESC;

   Schema   |            Name         | relfrozenxid 
------------+-------------------------+--------------
 public     | table_1                 |    100002000
 public     | table_2                 |         1846
 pg_catalog | pg_database             |         1827
 pg_catalog | pg_user_mapping         |         1821
 pg_catalog | pg_largeobject          |         1821

...

 pg_catalog | pg_transform            |         1821
(57 rows)

testdb=# SELECT datname, datfrozenxid FROM pg_database 
            WHERE datname = 'testdb';
 datname | datfrozenxid 
---------+--------------
 testdb  |         1821
(1 row)

FREEZE选项

带有FREEZE选项的VACUUM命令会强制冻结指定表中的所有事务标识。虽然这是在迫切模式下执行的,但这里freezeLimit会被设置为OldestXmin(而不是OldestXmin - vacuum_freeze_min_age)。 例如当txid=5000的事务执行VACUUM FULL命令,且没有其他正在运行的事务时,OldesXmin会被设置为5000,而t_xmin小于5000的元组将会被冻结。

6.3.3 改进迫切模式中的冻结过程

9.5或更早版本中的迫切模式效率不高,因为它始终会扫描所有页面。 比如在第6.3.2节的例子中,尽管第0页中所有元组都被冻结,但也会被扫描。

为了解决这一问题,9.6版本改进了可见性映射VM与冻结过程。如 第6.2.1节 所述,新VM包含着每个页面中所有元组是否都已被冻结的信息。在迫切模式下进行冻结处理时,可以跳过仅包含冻结元组的页面。

图6.6给出了一个例子。 根据VM中的信息,冻结此表时会跳过第0页。在更新完1号页面后,相关的VM信息会被更新,因为该页中所有的元组都已经被冻结了。

图6.6 冻结旧元组——迫切模式(9.6或更高版本)图6.6 冻结旧元组——迫切模式(9.6或更高版本)