PostgreSQL Internals之路 Part-I 第6章 Vaccum和Autovaccum

文摘   科技   2024-09-12 06:02   北京  


6. Vaccum和Autovaccum

6.1 Vaccum

页裁剪执行的非常快,但是它只释放一部分潜在可释放的空间。在单一页面下操作,它并不触及相关的索引(或反过来,它只清理一个索引页而不影响对应的表)。

辅助例程vacuum[1],是主要的vacuum过程,用于执行VACUUM命令[2]。它处理整张表,减少过时的堆元组以及相应的index数据。

Vacuuming可以和其它进程一起并行执行。在被vacuum的同时,表和索引依然被正常使用,如正常的读和写操作(但是并发的执行命令,像CREATE INDEX,ALTER TABLE和其它一些命令是不允许的)。

为避免过多的页扫描,PG运用了可见性的映射。在映射里被跟踪的页直接被跳过,因为它们可以确保只包含当前那些元组,一个页面被回收,仅当它不出现在VM(可见性映射)里头。如果一页中所有剩余的元组在VACUUM以后,都超出了数据库的地平线,可见性映射会被刷新并包含此页。

FSM也会得到更新以反映清除以后的空间。我们创建一个带索引的表,例如:

 create table vac(
 id integer,
 s char(100)
 )
 WITH (autovacuum_enabled = off);

CREATE INDEX vac_s ON vac(s);

autovacuum_enabled存储参数用于关闭autovacuum;我们这样做仅仅是为了实验的目的 ,用于精确控制vacuum的启动时间。

好,接着插入一行,并进行一系列update操作:

INSERT INTO vac(id,s) VALUES (1,'A');
UPDATE vac SET s = 'B';
UPDATE vac SET s = 'C'

那么现在,该表差不多有三个元组了:

mydb=# SELECT * FROM heap_page('vac',0);
 ctid  | state  |  xmin  |  xmax  | hhu | hot | t_ctid
-------+--------+--------+--------+-----+-----+--------
 (0,1) | normal | 1237 c | 1238 c |     |     | (0,2)
 (0,2) | normal | 1238 c | 1239   |     |     | (0,3)
 (0,3) | normal | 1239   | 0 a    |     |     | (0,3)

每个元组也会被index所引用:

mydb=# SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,1) | f
          2 | (0,2) | f
          3 | (0,3) | f
(3 rows)

Vacuuming已经将所有的死元组移除掉,只留下当前那个:

VACUUM vac;
 SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | unused |       |      |     |     |
 (0,3) | normal | 792 c | 0 a  |     |     | (0,3)

又出现情况不致的情况了???书中结果是: (centos1上边是正确的结果)

在页裁剪的情况下,头两个指针会被认为是死指针,但是现在它们变成无用状态,因为没有索引项指向它们。

mydb=# SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,3) | f

带无用状态的指针被认为是自由的,并且可以被新的行版本重用。

现在堆页又出现在可见性映射里;我们可以使用pg_visibility扩展来验证一下:

mydb=# CREATE EXTENSION pg_visibility;
CREATE EXTENSION
mydb=# SELECT all_visible FROM pg_visibility_map('vac',0);
 all_visible
-------------
 t
(1 row)

页头已经收到一个属性显示它的所有元组在所有快照中均可见:

mydb=# SELECT flags & 4 > 0 AS all_visible
mydb-# FROM page_header(get_raw_page('vac',0));
 all_visible
-------------
 t
(1 row)

6.2 数据库地平线再访问

Vacuuming基于数据库地平线检测死元组。这个概念如此基础,以至于我们有必要重新过一遍。

我们重新来一次刚开始的实验:

mydb=# TRUNCATE vac;
TRUNCATE TABLE
mydb=# INSERT INTO vac(id,s) VALUES (1,'A');
INSERT 0 1
mydb=# UPDATE vac SET s = 'B';
UPDATE 1

但是这次,在更新行之前,我们打算打开另一个事务以容纳数据库地平线(它可以是几乎任何事务,除了一个虚拟的事务在读已提交级别上执行以外)。例如,这个事务可以修改另一张表的某些行。

> => BEGIN
> => UPDATE accounts SET amount = 0;
mydb=# UPDATE vac SET s = 'C';
UPDATE 1

现在我们的表包含3个元组,索引包含三个引用。执行一下VACUUM操作,看看有什么变化:

mydb=# vacuum vac;
VACUUM
mydb=# SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | normal | 826 c | 827  |     |     | (0,3)
 (0,3) | normal | 827   | 0 a  |     |     | (0,3)
(3 rows)
mydb=# SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,2) | f
          2 | (0,3) | f
(2 rows)

然而前一个语句运行,只余下该页的一个元组,现在有两个:VACUUM决定了(0, 2)不能被移除,原因是数据库地平线,它由一些未完成的事务来确定:

mydb=# SELECT backend_xmin FROM pg_stat_activity
mydb-# WHERE pid = pg_backend_pid();
 backend_xmin
--------------
          827
(1 row)

我们可以使用VERBOSE子句来观察实际上发生的行为:

mydb=# VACUUM VERBOSE vac;
INFO:  vacuuming "public.vac"
INFO:  table "vac": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages
DETAIL: 1 dead row versions cannot be removed yet, oldest xmin: 834
Skipped 0 pages due to buffer pins, 0 frozen pages.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM

这个输出显示了下述信息:

  • VACUUM发现没有元组可能被移除(0 removable)
  • 两个元组不能被移除 (2 nonremovable)
  • 其中一个nonremovable元组还是死元组(1 DEAD),其它的都在使用
  • 当前地平线被VACUUM发现的最老的XMIN是834, 也是活动事务的地平线

一旦活动事务完成了,数据库的地平线将往前推进,并且vacuum可以继续进行:

COMMIT;
mydb=# VACUUM VERBOSE vac;
INFO:  vacuuming "public.vac"
INFO:  scanned index "vac_s" to remove 1 row versions
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO:  table "vac": removed 1 dead item identifiers in 1 pages
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO:  index "vac_s" now contains 1 row versions in 2 pages
DETAIL:  1 index row versions were removed.
0 index pages were newly deleted.
0 index pages are currently deleted, of which 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  table "vac": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 832
Skipped 0 pages due to buffer pins, 0 frozen pages.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM

VACCUM发现并且 移除了一个死元组(超出数据库地平线)。现在该页里头没有过期的行版本号;唯一的版本就是当前那个值:

mydb=# SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | unused |       |      |     |     |
 (0,3) | normal | 831 c | 0 a  |     |     | (0,3)
(3 rows)

索引也包含那个唯一的记录:

mydb=# SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,3) | f
(1 row)

6.3 Vacuum阶段

vacuuming机制看起来比较简单,但是这个印象容易误导。毕竟,表和索引都是并发处理的,不能阻塞其它进程。要使这个操作能进行,每个表的vacumming必须经历若干个阶段。

它会从扫描表,以找到死元组开始;如果找到了,它们会从索引数据里移除,接着再从表里移除。如果太多的死元组要进行vacuum, 这个处理就不断重复。最后,堆的截断可能被执行。

堆扫描

在第一阶段,堆扫描被执行。扫描过程会从vm开始考虑:这个映射里头所有跟踪的员将被忽略 ,因为它们已经确保没有过时的元组。如果元组超出了地平线,就不再需要了,它的ID就被添加到一个专门的ID数组。这样的元组还不能被移除,因为它们有可能还被一些索引引用到。

这个tid数组会放到Vacuum进程的本地内存里头;分配的内存块的大小由参数:maintenance_work_mem决定。整个块是立即分配而不是按需分配。然而,分配出来的内存永远不会超过最坏情况下的卷,如果表很小,vacuuming可能需要的内存比配置参数里头的要小的多。

Index Vacuuming

第1阶段可能有两种结果:要么表被全扫描,或者操作完成之前内存被tid数组全部填满。无论如何,索引的回收(vacuum)也是要进行的。在这个阶段, 表中的每个索引都会进行全扫描,以寻找所有的指向tid数组中注册的元组的索引项。这些索引项会从索引页中移除。

An index can help you quickly get to a heap tuple by its index key, but there is no way to quickly find an index entry by the corresponding tuple ID. This functionality is currently being implemented for B-trees, but this work is not completed yet. 索引可以帮助您通过它的索引键快速找到堆元组,但是无法通过相应的元组ID快速找到索引项。该功能目前正在为b树实现,但这项工作尚未完成。

如果有些索引比min_parallel_index_scan_size的值还要大,它们可以被后台workers以并行的方式进行回收。除非并行级别被parallel N从句明确定义,VACUUM会为每个合适的索引(在后台workers给定的限制数量以内)启动一个worker进程。一个索引不能同时被多个workers进行回收。

在索引回收阶段,PG会更新FSM并基于vacuuming计算统计信息。然而,如果行只是被插入,这一阶段就会被跳过。在这种情况下不会有死元祖出现。索引扫描可以在最末尾强制执行,用作索引清除的一个独立的阶段。

索引回收阶段会将索引当中对过时的堆元组的引用清除掉,但是元组本身可能还在表里头。这也很还正常:索引扫描找不到任何死元祖,但是表的顺序扫描依赖于可见性规则,会将它们过滤掉。

堆清除(Vacuuming)

接着就开始了堆清除。表会被再次扫描,以清除注册在TID数组中的元组,释放对应的指针。现在所有相关索引的引用已被清除,因而可以安全的执行。

VACUUM恢复的空间,会在FSM里头反映出来,而只包含当前可见元组的页,在所有的快照中都会在VM里头打上标签。

如果在heap扫描阶段,表没被完全读取,tid数组已被清理,那堆扫描将会从上次的位置恢复。

堆截断(Heap Truncation)

回收的heap页会包含一些空闲空间;偶尔情况下,你可能幸运的清掉了整页。如果你在文件末尾有若干空页,vacuum会直接吃掉末尾,并将回收的空间返给操作系统。它发生在堆截断阶段,这是vacuum最后一个阶段。

堆截断需要对表进行一个短暂的排它锁。为避免阻塞其它进程时间太久,它会尝试获取一个锁,不超过5秒钟。

由于表不得不需要锁,截断只在空尾巴至少是表的1/16,或者已经达到1000页的情况下才会执行。这个阈值是hardcode的,并且不能配置。

如果,不管所有这些防备,表锁仍然会导致问题,截断可以禁止使用,通过设置两个存储参数:vacuum_truncate和toast.vacuum_truncate即可以进行设置。

6.4 分析

当我们谈到vacuuming的时候,也不得不提起加一个与它有密切关系的任务,即使它们之间没有什么正式的联系。那就是分析,为查询计划收集统计信息的。收集的统计会包括行数(pg_class.reltuples)以及页数(pg_class.relpages),列的数据分布以及其它一些信息。

你可以使用analyze命令手动的运行analyze,或者组合使用vacuuming,通过调用VACUUM ANALYZE。但是,这两个任务仍然是串行执行的。因此从性能角度上来讲,没啥区别。

Historically, VACUUM ANALYZE appeared first, in version 6.1, while a separate ANALYZE command was not implemented until version 7.2. In earlier versions, statistics were collected by a TCL script.

在动vacuum和分析以类似的方式设置,因此可以将它们放到一起讨论。

6.5 自动Vacuum和分析

除非数据库地平线会占用相当长时间,vacuum例程会正常完成它的工作。那么我们多长时间一次去调用VACUUM命令呢?

频繁更新的表是很少被vacuum的,它会涨的比预想的要大。另外,它会累积太多的变化,下一个VACUUM的运行会经历好多次对index的访问。

如果表频繁的vacuum,服务器会相当多的负载消耗到maintenance,而不是有用的工作上边。

并且,典型的工作负载随时间不断变化 ,拥有固定的vacuum计划也不会有太大的帮助:表更新越频繁 ,它也就越要经常被vacuum。

这个问题是使用autovacuum来解决,它会根据表的更新的频度来调用 vacuum和分析进程。

关于自动vacuum机制

当自动vacuum被启用(autovaccum 配置参数为on),autovacuum守护进程会在系统中一直运行。该进程定义autovacuum计划,并维护着基于使用统计的活动数据库的列表。这些统计会被收集,如果track_counts参数被启用的话。不要关掉这些参数,否则autovacuum将不会工作。

一旦在autovacuum_naptime,autovacuum例程会为每个活动的数据库启动一个autovacuum工作进程(这些worker进程都是由postmaster派生出来的)。最后,如果有N个活动的数据库,N个worker进程会在autovacuum_naptime间隔内派生出来。但是总的worker数量不会超过autovacuum_max_workers值。

Autovacuum workers are very similar to regular background workers, but they appeared much earlier than this general mechanism of task management. It was decided to leave the autovacuum implementation unchanged, so autovacuum workers do not use max_worker_processes slots.

Autovacuum worker与常规的后台进程类似,但是他们比这些任务管理机制出现的要早。所以就决定保持不变,因而没有采用max_worker_process参数。

一旦启动完以后,后台工作进程会连接到具体的数据库,创建两个列表:

  • 所有的以待回收的表、物化视图以及Toast表的列表,
  • 所有的有待分析的表、物化视图的列表 (TOAST表不进行分析,因为他们总是通过索引进行访问)

接着被选中的对象一个一个进行回收或分析,直到整个工作结束,工作进程也就终止。

自动加成上与手动回收类似,但也有一些细微差别:

  • 手动回收将使用的是maintenance_work_mem中的内存块来存储元组ID,但是autovacuum使用相同的限制是不可想像的,因为它可能导致相当大的内存开销:因为同时有可能有很多个autovacuum工作进程并行执行,每个都有可能获取maintenance_work_mem大小的内存。因此,PG提供了独立的内存限制给autovacuum进程,那就是参数:autovacuum_work_mem。

    缺省情况下,autovacuum_work_mem参数值是在常规的maintenance_work_mem限制以内。因此如果autovacuum_work_mem参数值偏大,你可以适当的调整下这个值。

  • 对一张表的若干索引的并行处理可以通过手动vacuum来进行;使用autovacuum来达成此目的,可参导致产生大量的并行进程,因而是不允许的。

如果一个工作进程在autovacuum_naptime间隔内没能完成所有的计划任务,autovacuum守护进程会为目标数据库创建另一个工作进程并行执行。第2个工作进程会创建自己的对象列表,用于回收和分析,进而处理它们。在表级别上不存在并行;只有不同的表可以被并发的处理。

哪些表可以进行空间回收?

你可以在表一级禁用autovacuum,尽管这样做的必要性是难以想像的。有两个存储参数可以用于此目的, 一个用于常规表,另一个用于TOAST表:

  • autovacuum_enabled
  • toast.autovacuum_enabled

通常情况下,autovacuum会被死元组的累积或者新行的插入所触发。

死元组累积,死元组大多会被统计收集器固定周期性的进行计数;他们的当前数量会显示在系统catalog表:pg_stat_all_tables里头。

假定死元组在超过下边定义的两个参数值的阈值时,必须被回收:

  • autovacuum_vacuum_threshold, 指定死元组的数目 (绝对数值)  (50)
  • autovacuum_vacuum_scale_factor,设置死元组在一个表中的比例 (0.2)

在下述条件满足的时候就会执行回收:

pg_stat_all_tables.n_dead_tup > autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × pg_class.reltuples

显然这里主要的参数就是autovacuum_vacuum_scale_factor:它的值对于大表而言非常重要(也主要是大表才容易导致出现一些重大问题)。缺省值20%看起来太大了,可以把它调得非常小。

  • autovacuum_vacuum_threshold 和 toast.autovacuum_vacuum_threshold
  • autovacuum_vacuum_scale_factor 和 toast.autovacuum_vacuum_scale_factor

行插入.  如果记录行只是插入,从未被删除或更新,那么表里头将不会有死元组。但是这样的表仍然要进行回收处理,以对堆元组进行提前冻结并更新可见性映射(VM)(这样可以让仅索引扫描生效)。

当自上次回收处理以来插入的行的数量超过了另一对参数设置的阈值,表会被执行回收操作。

  • autovacuum_vacuum_insert_threshold
  • autovacuum_vacuum_insert_scale_factor

使用公式描述如下:

pg_stat_all_tables.n_ins_since_vacuum > autovacuum_vacuum_insert_threshold + autovacuum_vacuum_insert_scale_factor × pg_class.reltuples

就你前例一样,你可以使用存储参数来覆盖这些值:

  • autovacuum_vacuum_insert_threshold 和它TOAST上的对应值
  • autovacuum_vacuum_insert_scale_factor和它TOAST上的对应值

哪些表需要进行分析?

自动分析只需要处理那些改变了的行,因此相关计算也比回收也来得简单。

我们假定自上一次分析以来表中记录行修改的数量超过了下边两个配置参数设置的值,就会对它进行分析操作:

  • autovacuum_analyze_threshol  (50)
  • autovacuum_analyze_scale_factor (0.1)

自动分析会在满足下述条件时触发:

pg_stat_all_tables.n_mod_since_analyze > autovacuum_analyze_threshold + autovacuum_analyze_scale_factor × pg_class.reltuples

对于特定的表要覆盖上述参数,你可以使用相同名字的存储参数:

  • autovacuum_analyze_threshold
  • autovacuum_analyze_scale_factor

因为TOAST表是不需要进行分析的,它们没有对应的存储参数。

自动清理的处理(in action)

为了正式说明这一节的内容,我们创建两个视图来展示哪些表需要被回收和分析。在视图中使用的函数返回传入参数的当前值,这个值可以在表一级重新定义:

CREATE FUNCTION p(param text, c pg_class) RETURNS float
AS $$
SELECT coalesce(
-- use storage parameter if set
(SELECT option_value
FROM pg_options_to_table(c.reloptions)
WHERE option_name = CASE
-- for TOAST tables the parameter name is different
WHEN c.relkind = 't' THEN 'toast.' ELSE ''
END || param
),
-- else take the configuration parameter value
current_setting(param)
)::float;
$$ LANGUAGE sql;

下边是一个回收相关的视图的定义:

CREATE VIEW need_vacuum AS
WITH c AS (
SELECT c.oid,
greatest(c.reltuples, 0) reltuples,
p('autovacuum_vacuum_threshold', c) threshold,
p('autovacuum_vacuum_scale_factor', c) scale_factor,
p('autovacuum_vacuum_insert_threshold', c) ins_threshold,
p('autovacuum_vacuum_insert_scale_factor', c) ins_scale_factor
FROM pg_class c
WHERE c.relkind IN ('r','m','t')
)
SELECT st.schemaname || '.' || st.relname AS tablename,
st.n_dead_tup AS dead_tup,
c.threshold + c.scale_factor * c.reltuples AS max_dead_tup,
st.n_ins_since_vacuum AS ins_tup,
c.ins_threshold + c.ins_scale_factor * c.reltuples AS max_ins_tup,
st.last_autovacuum
FROM pg_stat_all_tables st
JOIN c ON c.oid = st.relid;

列max_dead_tup显示的是触发自动清理的死元组的数目,而max_ins_tup则显示的是与插入相关的阈值。

下边是与前边类似的分析相关的视图:

CREATE VIEW need_analyze AS
WITH c AS (
SELECT c.oid,
greatest(c.reltuples, 0) reltuples,
p('autovacuum_analyze_threshold', c) threshold,
p('autovacuum_analyze_scale_factor', c) scale_factor
FROM pg_class c
WHERE c.relkind IN ('r','m')
)
SELECT st.schemaname || '.' || st.relname AS tablename,
st.n_mod_since_analyze AS mod_tup,
c.threshold + c.scale_factor * c.reltuples AS max_mod_tup,
st.last_autoanalyze
FROM pg_stat_all_tables st
JOIN c ON c.oid = st.relid; 

列max_mod_dup 显示了自动分析要用到的阈值。

为了加速实验,我们设置每秒执行一次autovacuum:

ALTER SYSTEM SET autovacuum_naptime = '1s';
SELECT pg_reload_conf();

再对表vac进行截断,插入1000条数据。注意autovacuum在表一级已经关掉了。

TRUNCATE TABLE vac;
INSERT INTO vac(id,s) SELECT id'A' FROM generate_series(1,1000id;

我们来检查一下清理相关的视图结果:

SELECT * FROM need_vacuum WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]---+-----------
tablename       | public.vac
dead_tup        | 0
max_dead_tup    | 50
ins_tup         | 1000
max_ins_tup     | 1000
last_autovacuum | 

真实的阈值:max_dead_tup = 50。仅管公式里列出的是:50 + 0.2*1000 = 250。原因是这个表的统计信息还无法得到,因为INSERT命令还没有对它进行更新:

SELECT reltuples FROM pg_class WHERE relname = 'vac';
 reltuples
-----------
        -1 

pg_class.reltuples的值设成了-1; 这个没有设成0的特殊的常量,主要用于区别一张表没有任何统计信息和一张表是空表并且已经进行了分析的处理两种情况。为了计算的目的,负值被当作0来参与计算。从而50 + 0.2 * 0 = 50。

值max_ins_tup = 1000与那个投影值1200相区别,也是同样的原因。

我们再看一下分析视图:

SELECT * FROM need_analyze WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]----+-----------
tablename        | public.vac
mod_tup          | 1019
max_mod_tup      | 50
last_autoanalyze |

我们已经更新(这里是insert)了1000行;结果,阈值是超过了:由于这张表大小未知,它目前被设为50.这意味着自动分析会被触发,只要我们将开关打开的话:

ALTER TABLE vac SET (autovacuum_enabled = on);

一旦表分析完成,那个阈值就会被设到一个合适的值:150行。

SELECT reltuples FROM pg_class WHERE relname = 'vac';
 reltuples
-----------
      1000
SELECT * FROM need_analyze WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]----+-----------------------------
tablename | public.vac
mod_tup | 0
max_mod_tup | 150
last_autoanalyze | 2022-10-03 07:49:25.89077+08

我们重新回到自动清理:

SELECT * FROM need_vacuum WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]---+-----------
tablename       | public.vac
dead_tup        | 0
max_dead_tup    | 250
ins_tup         | 1000
max_ins_tup     | 1200
last_autovacuum |

max_dead_dup和max_ins_tup也会基于通过分析以后表的实际大小进行相应更新。

在至少满足下边两个条件之一的情况下,表会执行清理操作:

  • 累计有超过250个死元组
  • 超过200行被插入到表当中

当我们再次关闭autovacuum时,更新251行数据,这样会比阈值还大1:

ALTER TABLE vac SET (autovacuum_enabled = off);
UPDATE vac SET s = 'B' WHERE id <= 251;
SELECT * FROM need_vacuum WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]---+-----------
tablename       | public.vac
dead_tup        | 251
max_dead_tup    | 250
ins_tup         | 1000
max_ins_tup     | 1200
last_autovacuum |

现在触发条件满足了。我们再启用自动清理,过一会儿,我们会发现表会被处理,它的统计信息也会被重置:

ALTER TABLE vac SET (autovacuum_enabled = on);
SELECT * FROM need_vacuum WHERE tablename = 'public.vac' \gx
-[ RECORD 1 ]---+------------------------------
tablename       | public.vac
dead_tup        | 0
max_dead_tup    | 250
ins_tup         | 0
max_ins_tup     | 1200
last_autovacuum | 2022-10-03 07:57:58.321856+08

6.6 管理负载

在页一级,清理并不阻塞其它进程;但是,它会增加系统负载,对性能也有显著影响。

Vacuum Throttling (清理节流)

为了控制清理的强度,PG对表的处理采用了常规的暂停。在完成了大概vacuum_cost_limit个单位的工作以后,进程会执行sleep操作,休眠的时间大约是: vacuum_cost_delay个时间间隔。

vacuum_cost_delay默认值为0,意味着vacuum例程从来不暂停,这个时候vacuum_cost_limit的值也就不起作用了。假定管理员必须求助于手动清理,他们可能希望清理越快完成越好。

如果设置了休眠时间,进程在处理完vacuum_cost_limit个单位的工作(buffer cache中的页处理)以后就会暂停一段时间。每页的读操作成本通过buffer cache中页被发现,vacuum_cost_page_hit值来估算,  或者通过vacuum_cost_page_miss的值来估算(其它情况下)[3]。如果一个干净的页被vacuum写脏,它就会增加另一个vacuum_cost_page_dirty单位[4]

数据库杂记
数据库技术专家,PostgreSQL ACE,SAP HANA,Sybase ASE/ASA,Oracle,MySQL,SQLite各类数据库, SAP BTP云计算技术, 以及陈式太极拳教学倾情分享。出版过三本技术图书,武术6段。
 最新文章