您现在的位置是: 网站首页 > 程序设计  > PostgreSQL 

PostgreSQL内核原理之vacuum

2025年5月15日 23:59 35人围观

简介我们知道,MVCC是数据库实现并发一致性的重要手段,但是各个数据库在实现MVCC的时候不尽相同,主要差异在于版本链的管理。以Oracle为例的undo派把历史版本单独放在undo段中,数据页上只存放最新版本,相当于是新老数据分开存储,线程回收历史版本时不会对业务产出影响;而以Postgres为例的append派把历史版本和最新版都存放在数据页上,回收老版本需要遍历所有数据页,会产生较高的IO开销

一、为什么需要VACUUM

我们知道,MVCC是数据库实现并发一致性的重要手段,但是各个数据库在实现MVCC的时候不尽相同,主要差异在于版本链的管理。以Oracle为例的undo派把历史版本单独放在undo段中,数据页上只存放最新版本,相当于是新老数据分开存储,线程回收历史版本时不会对业务产出影响;而以Postgres为例的append派把历史版本和最新版都存放在数据页上,回收老版本需要遍历所有数据页,会产生较高的IO开销。

二、死元组的产生

1. DELETE产生的死元组

如图所示,页面上有3条元组,当执行delete from t1 where ctid='(0, 2)'删除第二条元时,delete操作只是给 Tuple上打一个删除标记,行指针和Tuple本身不会做任何修改,此时Tuple2会一直残留在页面上。

2. UPDATE跨页产生的死元组

前面提到,Postgres是append更新,因此当我们执行update后老元组就会变成死元组,如图所示,当执行update t1 set a=a+1 where ctid='(0, 1)'来更新Tuple1时,新元组Tuple1'被写到了其他页面,这是Tuple1就是页面上的死元组。

3. HOT UPDATE产生的死元组

Postgres中为了提升性能,当满足条件时可以执行HOT UPDATE,如图所示,将Tuple1更新成Tuple4,此时Tuple1成了死元组,但是要注意的是在清理的时候LP1并不能清除,这个下面会说到。

如图所示,当我们使用HOT UPDATE更新Tuple1时,假如更新后的元组是Tuple4,则Tuple1上的ctid字段会指向LP4,此时的Tuple1也可以认为是死元组,当执行vacuum时能够被清掉,但是LP1仍保留。

三 VACUUM相关参数

我们可以使用select name, min_val, max_val from pg_settings where name ilike '%vacuum%';来查看所有和VACUUM相关的参数,结果如下:

postgres=# select name, min_val, max_val, boot_val from pg_settings where name ilike '%vacuum%'; 
                 name                  | min_val |  max_val   |  boot_val   
---------------------------------------+---------+------------+------------ 
 autovacuum                            |         |            | on 
 autovacuum_analyze_scale_factor       | 0       | 100        | 0.1 
 autovacuum_analyze_threshold          | 0       | 2147483647 | 50 
 autovacuum_freeze_max_age             | 100000  | 2000000000 | 200000000 
 autovacuum_max_workers                | 1       | 262143     | 3 
 autovacuum_multixact_freeze_max_age   | 10000   | 2000000000 | 400000000 
 autovacuum_naptime                    | 1       | 2147483    | 60 
 autovacuum_vacuum_cost_delay          | -1      | 100        | 2 
 autovacuum_vacuum_cost_limit          | -1      | 10000      | -1 
 autovacuum_vacuum_insert_scale_factor | 0       | 100        | 0.2 
 autovacuum_vacuum_insert_threshold    | -1      | 2147483647 | 1000 
 autovacuum_vacuum_scale_factor        | 0       | 100        | 0.2 
 autovacuum_vacuum_threshold           | 0       | 2147483647 | 50 
 autovacuum_work_mem                   | -1      | 2147483647 | -1 
 log_autovacuum_min_duration           | -1      | 2147483647 | 600000 
 vacuum_buffer_usage_limit             | 0       | 16777216   | 256 
 vacuum_cost_delay                     | 0       | 100        | 0 
 vacuum_cost_limit                     | 1       | 10000      | 200 
 vacuum_cost_page_dirty                | 0       | 10000      | 20 
 vacuum_cost_page_hit                  | 0       | 10000      | 1 
 vacuum_cost_page_miss                 | 0       | 10000      | 2 
 vacuum_failsafe_age                   | 0       | 2100000000 | 1600000000 
 vacuum_freeze_min_age                 | 0       | 1000000000 | 50000000 
 vacuum_freeze_table_age               | 0       | 2000000000 | 150000000 
 vacuum_multixact_failsafe_age         | 0       | 2100000000 | 1600000000 
 vacuum_multixact_freeze_min_age       | 0       | 1000000000 | 5000000 
 vacuum_multixact_freeze_table_age     | 0       | 2000000000 | 150000000 
(27 rows) 

这里只介绍几个重要参数

  • autovacuum:这个是总开关,决定着是否开启AUTO VACUUM,但是需要注意的是,即使设置autovacuum=off 也可能会触发拉起Worker。
  • autovacuum_naptime:这个参数控制着Launcher检测拉起Worker的周期,注意的是这个值的真是意图时能够在naptime时间内对所有库都执行一轮清理,因此假设有N个库,那么Launcher每隔naptime/N的时间就会在一个库上拉起一个Worker
  • autovacuum_analyze_scale_factor、autovacuum_vacuum_scale_factor:这两个参数分别控制触发autoanalyze和autovacuum的阈值,分别是0.1和0.2,但这并不是说触发autovacuum的时候一定会触发autoanalyze,实际上他们的触发没有必然联系
  • autovacuum_analyze_threshold 、autovacuum_vacuum_threshold:这两个值是前面两个阈值的基准值,可以设置这两个值来控制小表的触发时机;

四、VACUUM的原理过程

上面简单介绍了死元组的产生场景,接下来介绍VACUUM的原理。

实际严格来说,VACUUM并不是一个进程,而是包括Launcher和Worker两个进程。其中Launcher是常驻进程,负责拉起分配Worker。