PG中的VACUUM命令
VACUUM简介
VACUUM — 垃圾收集并根据需要分析一个数据库。
PostgreSQL数据库管理工作中,定期vacuum是一个重要的工作。
vacuum的效果:
- 释放,再利用 更新/删除的行所占据的磁盘空间.
- 更新POSTGRESQL查询计划中使用的统计数据
- 防止因事务ID的重置而使非常老的数据丢失。
第一点的原因是PostgreSQL数据的插入,更新,删除操作并不是真正放到数据库空间.如果不定期释放空间的话,由于数据太多,查询速度会巨降.
第二点的原因是PostgreSQL在做查询处理的时候,为了使查询速度提高,会根据统计数据来确定执行计划.如果不及时更新的话,查询的效果可能不如预期.
第三点的原因是PostgreSQL中每一个事务都会产生一个事务ID,但这个数字是有上限的. 当事务ID达到最大值后,会重新从最小值开始循环.这样如果不及时把以前的数据释放掉的话,原来的老数据会因为事务ID的丢失而丢失掉.
虽然在新版本的Postgresql中有自动的vacuum,但是如果是大批量的数据IO可能会导致自动执行很慢,需要配合手动执行以及自己的脚本来清理数据库。
注意:
vacuum可以与dml并行运行,但不能与ddl并行运行
vacuum full不能与dml、ddl并行运行
允许VACUUM并行处理表的索引,方法是通过PARALLEL参数控制是否开启并行vacuum索引。
vacuumdb命令可以并行,指定--parallel选项。
reindexdb命令可以并行,指定--jobs选项。
VACUUM命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | postgres=# \h vacuum Command: VACUUM Description: garbage-collect and optionally analyze a database Syntax: VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ] VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ] where option can be one of: FULL [ boolean ] FREEZE [ boolean ] VERBOSE [ boolean ] ANALYZE [ boolean ] DISABLE_PAGE_SKIPPING [ boolean ] SKIP_LOCKED [ boolean ] INDEX_CLEANUP [ boolean ] TRUNCATE [ boolean ] PARALLEL integer and table_and_columns is: table_name [ ( column_name [, ...] ) ] URL: https://www.postgresql.org/docs/13/sql-vacuum.html |
描述
VACUUM
收回由死亡元组占用的存储空间。在通常的PostgreSQL操作中,被删除或者被更新废弃的元组并没有在物理上从它们的表中移除,它们将一直存在直到一次VACUUM
被执行。因此有必要周期性地做VACUUM
,特别是在频繁被更新的表上。
在没有table_and_columns
列表的情况下,VACUUM
会处理当前用户具有清理权限的当前数据库中的每一个表和物化视图。如果给出一个列表,VACUUM
可以只处理列表中的那些表。
VACUUM ANALYZE
对每一个选定的表ANALYZE
。这是两种命令的一种方便的组合形式,可以用于例行的维护脚本。其处理细节可参考ANALYZE。
简单的 VACUUM
(不带FULL
)简单地收回空间并使其可以被重用。这种形式的命令可以和表的普通读写操作并行,因为它不会获得一个排他锁。但是,这种形式中额外的空间并没有被还给操作系统(在大多数情况下),它仅仅被保留在同一个表中以备重用。它还允许我们利用多个 CPU 来处理索引。 此功能称为parallel vacuum。要禁用此功能,可以使用PARALLEL
选项并将并行工作程序指定为零。
VACUUM FULL
将表的整个内容重写到一个新的磁盘文件中,并且不包含额外的空间,这使得没有被使用的空间被还给操作系统。这种形式的命令更慢并且在其被处理时要求在每个表上保持一个排他锁。
当选项列表被包围在圆括号中时,选项可以被写成任何顺序。如果没有圆括号,选项必须严格按照上面所展示的顺序指定。有圆括号的语法在PostgreSQL 9.0时被加入,无圆括号的语法则被废弃。
参数
FULL
选择“完全”清理,它可以收回更多空间,并且需要更长时间和表上的排他锁。这种方法还需要额外的磁盘空间,因为它会创建该表的一个新拷贝,并且在操作完成之前都不会释放旧的拷贝。通常这种方法只用于需要从表中收回数量庞大的空间时。
FREEZE
选择激进的元组“冻结”。指定
FREEZE
等价于参数vacuum_freeze_min_age和 vacuum_freeze_table_age设置为0的VACUUM
。当表被重写时总是会执行激进的冻结, 因此指定FULL
时这个选项是多余的。VERBOSE
为每个表打印一份详细的清理活动报告。
ANALYZE
更新优化器用以决定最有效执行一个查询的方法的统计信息。
DISABLE_PAGE_SKIPPING
通常,
VACUUM
将基于可见性映射跳过页面。已知所有元组都被冻结的页面总是会被跳过,而那些所有元组对所有事务都可见的页面则可能会被跳过(除非执行的是激进的清理)。此外,除非在执行激进的清理时,一些页面也可能会被跳过,这样可避免等待其他页面完成对其使用。这个选项禁用所有的跳过页面的行为,其意图是只在可见性映射内容被怀疑时使用,这种情况只有在硬件或者软件问题导致数据库损坏时才会发生。SKIP_LOCKED
规定
VACUUM
在开始处理关系时不等待任何冲突锁被释放:如果关系不能立即锁定而不等待,则跳过关系。 请注意即使采用此选项,VACUUM
在打开关系的索引时仍可能阻塞。 此外,VACUUM ANALYZE
在从分区、继承子表和某些类型的外表获取示例行时,仍然可能阻塞。 还有,虽然VACUUM
通常处理指定分区表的所有分区,但如果分区表上的锁冲突, 此选项将导致VACUUM
跳过所有分区。在pg12中,VACUUM命令新增了SKIP_LOCKED选项。SKIP_LOCKED选项的作用是,vacuum命令会跳过那些被lock住的table,并且此时vacuum命令会被看做是成功执行。在之前的版本中,vacuum命令遇到了lock住的table时,vacuum命令会一直在等待。
在第一个session中执行lock table操作:
123456789[pg124@iZm5ehqfjhnsbtxrzrnh2zZ ~]$ psql -d postgrespsql (12.4)Type 'help' for help.postgres=# begin;BEGINpostgres=# lock table t in exclusive mode;LOCK TABLEpostgres=#然后在第二个session中进行vacuum测试:
123456789101112131415161718[pg124@iZm5ehqfjhnsbtxrzrnh2zZ ~]$ psql -d postgrespsql (12.4)Type 'help' for help.postgres=# VACUUM t; --->>>首先不带skip_locked选项,结果vacuum t一直hang着,也就是说不带skip_locked选项时,vacuum命令是不跳过被锁定的table的。^CCancel request sentERROR: canceling statement due to user requestpostgres=# VACUUM skip_locked t;ERROR: syntax error at or near 't'LINE 1: VACUUM skip_locked t;^postgres=# VACUUM (skip_locked) t;WARNING: skipping vacuum of 't' --- lock not availableVACUUMpostgres=#postgres=# \echo :SQLSTATE00000postgres=#INDEX_CLEANUP
规定
VACUUM
尝试删除指向死元组的索引条目。 这通常是所需的行为,并且是默认行为,除非将vacuum_index_cleanup
选项设置为 false,对要被清空的表。 如果需要尽快运行清空操作的话,将此选项设置为 false 可能很有用,例如,为了避免即将发生的事务 ID 回绕[wraparound](请参阅 第 24.1.5 节)。 但是,如果不定期执行索引清理,性能可能会受到影响,因为随着表的修改,索引将累积死元组,并且表本身将累积死行指针,在索引清理完成之前都无法删除。 此选项对于没有索引的表无效,如果使用FULL
选项,则忽略此选项。TRUNCATE
指定
VACUUM
尝试截断表末尾的任何空页,并允许将截断页的磁盘空间返回到操作系统。 这通常是所需的行为,并且是默认行为,除非将vacuum_truncate
选项设置为 false,对要被清空的表。 将此选项设置为 false 可能有助于避免ACCESS EXCLUSIVE
锁定需要截断的表。如果使用FULL
选项,则忽略此选项。在pg12中,vacuum带有TRUNCATE选项。当vacuum(TRUNCATE off)时,vacuum不会对该table中尾部的free area区域进行回收,若是vacuum不带truncate选项,那么需要看该table的VACUUM_TRUNCATE属性设置。
123456789101112131415161718postgres=# VACUUM (VERBOSE ON, TRUNCATE OFF) t1 ;INFO: vacuuming 'public.t1'INFO: scanned index 'idx_t1' to remove 4000 row versionsDETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 sINFO: 't1': removed 4000 row versions in 18 pagesDETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 sINFO: index 'idx_t1' now contains 0 row versions in 23 pagesDETAIL: 4000 index row versions were removed.20 index pages have been deleted, 10 are currently reusable.CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.INFO: 't1': found 4000 removable, 0 nonremovable row versions in 18 out of 18 pagesDETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 607There were 0 unused item identifiers.Skipped 0 pages due to buffer pins, 0 frozen pages.0 pages are entirely empty.CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.VACUUMpostgres=#PARALLEL
使用
integer
后台处理器并行执行VACUUM
的索引真空和索引清理阶段(每个真空阶段的详细信息请参考表 27.25))。 用于执行操作的处理器数量等于关系上支持并行清理的索引数量,该数量受PARALLEL
选项指定的工人数量的限制,如果有的话,该数量还受到 max_parallel_maintenance_workers 限制。 当且仅当索引的大小大于min_parallel_index_scan_size时,索引才能参与并行清理。 请注意,不保证在执行期间会使用integer
中指定的并行工作线程数。 清理运行时可能需要比指定的更少的处理器,甚至根本没有处理器。每个索引只能使用一名处理器。 所以只有当表中至少有2
索引时才会启动并行工作程序。 在每个阶段开始之前启动清理工作进程,并在阶段结束时退出。这些行为可能会在未来的版本中发生变化。 此选项不能与FULL
选项一起使用。boolean
指定打开还是关闭所选选项。你可以写入
TRUE
、ON
或1
以启用该选项,以及FALSE
、OFF
或0
来禁用它。 在TRUE
被假定的情况下,boolean
值也可以被省略。integer
指定传递给所选选项的非负整数值。
table_name
要清理的表或物化视图的名称(可以有模式修饰)。如果指定的表示一个分区表,则它所有的叶子分区也会被清理。
column_name
要分析的指定列的名称。缺省是所有列。如果指定了一个列的列表,则
ANALYZE
也必须被指定。
输出
如果声明了VERBOSE
,VACUUM
会发出进度消息来表明当前正在处理哪个表。各种有关这些表的统计信息也会打印出来。
注意
要清理一个表,操作者通常必须是表的拥有者或者超级用户。但是,数据库拥有者被允许清理他们的数据库中除了共享目录之外的所有表(对于共享目录的限制意味着一个真正的数据库范围的VACUUM
只能被超级用户执行)。VACUUM
将会跳过执行者不具备清理权限的表。
VACUUM
不能在一个事务块内被执行。
对具有GIN索引的表,VACUUM
(任何形式)也会通过将待处理索引项移动到主要GIN索引结构中的合适位置来完成任何待处理的索引插入。详见第 66.4.1 节。
我们建议经常清理活动的生产数据库(至少每晚一次),以保证移除失效的行。在增加或删除了大量行之后, 对受影响的表执行VACUUM ANALYZE
命令是一个很好的做法。这样做将把最近的更改更新到系统目录,并且允许 PostgreSQL查询规划器在规划用户查询时做出更好的选择。
日常使用时,不推荐FULL
选项,但在特殊情况时它会有用。一个例子是当你删除或者更新了一个表中的绝大部分行时,如果你希望在物理上收缩表以减少磁盘空间占用并且允许更快的表扫描,则该选项是比较合适的。VACUUM FULL
通常会比简单VACUUM
更多地收缩表。
PARALLEL
选项仅用于清理目的。如果此选项与ANALYZE
选项一起指定,则不会影响ANALYZE
。
VACUUM
会导致I/O流量的大幅度增加,这可能导致其他活动会话性能变差。因此,有时建议使用基于代价的清理延迟特性。 对于并行清理,每个处理器的睡眠与该处理器完成的工作成比例。详情请参阅第 19.4.4 节。
PostgreSQL包括了一个“autovacuum”工具,它可以自动进行例行的清理维护。关于自动和手动清理的更多信息请见第 24.1 节。
例子
清理单一表onek
,为优化器分析它并且打印出详细的清理活动报告:
1 | VACUUM (VERBOSE, ANALYZE) onek; |
vacuumdb命令
vacuumdb 是 SQL 命令 VACUUM的封装,所以用vacuumdb和vacuum来清理数据库都可以,效果是一样的。
vacuumdb 中的几个重要参数:可以用
vacuumdb --help
查询。12345678910111213141516171819202122232425262728293031323334353637383940414243444546-a/--all vacuum所有的数据库-d dbname 只vacuum dbname这个数据库-f/--full 执行full的vacuum-t table 只vacuum table这个数据表-z/--analyze Calculate statistics for use by the optimizer[pg13@lhrpg ~]$ vacuumdb --helpvacuumdb cleans and analyzes a PostgreSQL database.Usage:vacuumdb [OPTION]... [DBNAME]Options:-a, --all vacuum all databases-d, --dbname=DBNAME database to vacuum--disable-page-skipping disable all page-skipping behavior-e, --echo show the commands being sent to the server-f, --full do full vacuuming-F, --freeze freeze row transaction information-j, --jobs=NUM use this many concurrent connections to vacuum--min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum--min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum-P, --parallel=PARALLEL_DEGREE use this many background workers for vacuum, if available-q, --quiet don't write any messages--skip-locked skip relations that cannot be immediately locked-t, --table='TABLE[(COLUMNS)]' vacuum specific table(s) only-v, --verbose write a lot of output-V, --version output version information, then exit-z, --analyze update optimizer statistics-Z, --analyze-only only update optimizer statistics; no vacuum--analyze-in-stages only update optimizer statistics, in multiplestages for faster results; no vacuum-?, --help show this help, then exitConnection options:-h, --host=HOSTNAME database server host or socket directory-p, --port=PORT database server port-U, --username=USERNAME user name to connect as-w, --no-password never prompt for password-W, --password force password prompt--maintenance-db=DBNAME alternate maintenance databaseRead the description of the SQL command VACUUM for details.Report bugs to <pgsql-bugs@lists.postgresql.org>.PostgreSQL home page: <https://www.postgresql.org/>切换到postgres用户下:
1vacuumdb -d yourdbname -f -z -v来清理你的数据库。或者加到crontab中
115 1 * * * postgres vacuumdb -d mydb -f -z -v >> /tmp/vacuumdb.log每天的一点一刻开始进行清理。
如何查询我的XID是否接近临界值的命令:
123select age(datfrozenxid) from pg_database;或者:select max(age(datfrozenxid)) from pg_database;然而我们关心的是哪一个大的表组要真正的vacuum:
123SELECT relname, age(relfrozenxid) as xid_age, pg_size_pretty(pg_table_size(oid)) as table_sizeFROM pg_class WHERE relkind = 'r' and pg_table_size(oid) > 1073741824ORDER BY age(relfrozenxid) DESC LIMIT 20;这个命令是查询按照最老的XID排序,查看大于1G而且是排名前20的表。
日常清理
PostgreSQL数据库要求周期性的清理维护。对于很多安装,让自动清理守护进程来执行清理已经足够,如第 24.1.6 节所述。你可能需要调整其中描述的自动清理参数来获得最佳结果。某些数据库管理员会希望使用手动管理的VACUUM
命令来对后台进程的活动进行补充或者替换,这通常使用cron或任务计划程序脚本来执行。要正确地设置手动管理的清理,最重要的是理解接下来几小节中讨论的问题。依赖自动清理的管理员最好也能略读该内容以帮助他们理解和调整自动清理。
清理的基础知识
PostgreSQL的VACUUM命令出于几个原因必须定期处理每一个表:
- 恢复或重用被已更新或已删除行所占用的磁盘空间。
- 更新被PostgreSQL查询规划器使用的数据统计信息。
- 更新可见性映射,它可以加速只用索引的扫描。
- 保护老旧数据不会由于事务ID回卷或多事务ID回卷而丢失。
正如后续小节中解释的,每一个原因都将指示以不同的频率和范围执行VACUUM
操作。
有两种VACUUM
的变体:标准VACUUM
和VACUUM FULL
。VACUUM FULL
可以收回更多磁盘空间但是运行起来更慢。另外,标准形式的VACUUM
可以和生产数据库操作并行运行(SELECT
、INSERT
、UPDATE
和DELETE
等命令将继续正常工作,但在清理期间你无法使用ALTER TABLE
等命令来更新表的定义)。VACUUM FULL
要求在其工作的表上得到一个排他锁,因此无法和对此表的其他使用并行。因此,通常管理员应该努力使用标准VACUUM
并且避免VACUUM FULL
。
VACUUM
会产生大量I/O流量,这将导致其他活动会话性能变差。可以调整一些配置参数来后台清理活动造成的性能冲击 — 参阅第 19.4.4 节。
恢复磁盘空间
在PostgreSQL中,一次行的UPDATE
或DELETE
不会立即移除该行的旧版本。这种方法对于从多版本并发控制(MVCC,见第 13 章)获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行VACUUM
完成。
VACUUM
的标准形式移除表和索引中的死亡行版本并将该空间标记为可在未来重用。不过,它将不会把该空间交还给操作系统,除非在特殊的情况中表尾部的一个或多个页面变成完全空闲并且能够很容易地得到一个排他表锁。相反,VACUUM FULL
通过把死亡空间之外的内容写成一个完整的新版本表文件来主动紧缩表。这将最小化表的尺寸,但是要花较长的时间。它也需要额外的磁盘空间用于表的新副本,直到操作完成。
例行清理的一般目标是多做标准的VACUUM
来避免需要VACUUM FULL
。自动清理守护进程尝试这样工作,并且实际上永远不会发出VACUUM FULL
。在这种方法中,其思想不是让表保持它们的最小尺寸,而是保持磁盘空间使用的稳定状态:每个表占用的空间等于其最小尺寸外加清理之间被用完的空间。尽管VACUUM FULL
可被用来把一个表收缩回它的最小尺寸并将该磁盘空间交还给操作系统,但是如果该表将在未来再次增长这样就没什么意义。因此,对于维护频繁被更新的表,适度运行标准VACUUM
运行比少量运行VACUUM FULL
要更好。
一些管理员更喜欢自己计划清理,例如在晚上负载低时做所有的工作。根据一个固定日程来做清理的难点在于,如果一个表有一次预期之外的更新活动尖峰,它可能膨胀得真正需要VACUUM FULL
来回收空间。使用自动清理守护进程可以减轻这个问题,因为守护进程会根据更新活动动态规划清理操作。除非你的负载是完全可以预估的,完全禁用守护进程是不理智的。一种可能的折中方案是设置守护进程的参数,这样它将只对异常的大量更新活动做出反应,因而保证事情不会失控,而在负载正常时采用有计划的VACUUM
来做批量工作。
对于那些不使用自动清理的用户,一种典型的方法是计划一个数据库范围的VACUUM
,该操作每天在低使用量时段执行一次,并根据需要辅以在重度更新表上的更频繁的清理(一些有着极高更新率的安装会每几分钟清理一次它们的最繁忙的表)。如果你在一个集簇中有多个数据库,别忘记VACUUM
每一个,你会用得上vacuumdb程序。
提示
当一个表因为大量更新或删除活动而包含大量死亡行版本时,纯粹的
VACUUM
可能不能令人满意。如果你有这样一个表并且你需要回收它占用的过量磁盘空间,你将需要使用VACUUM FULL
,或者CLUSTER,或者ALTER TABLE的表重写变体之一。这些命令重写该表的一整个新拷贝并且为它构建新索引。所有这些选项都要求排他锁。注意它们也临时使用大约等于该表尺寸的额外磁盘空间,因为直到新表和索引完成之前旧表和索引都不能被释放。如果你有一个表,它的整个内容会被周期性删除,考虑用TRUNCATE而不是先用
DELETE
再用VACUUM
。TRUNCATE
会立刻移除该表的整个内容,而不需要一次后续的VACUUM
或VACUUM FULL
来回收现在未被使用的磁盘空间。其缺点是会违背严格的 MVCC 语义。
更新规划器统计信息
PostgreSQL查询规划器依赖于有关表内容的统计信息来为查询产生好的计划。这些统计信息由ANALYZE命令收集,它除了直接被调用之外还可以作为VACUUM
的一个可选步骤被调用。拥有适度准确的统计信息很重要,否则差的计划可能降低数据库性能。
自动清理守护进程如果被启用,当一个表的内容被改变得足够多时,它将自动发出ANALYZE
命令。不过,管理员可能更喜欢依靠手动的ANALYZE
操作,特别是如果知道一个表上的更新活动将不会影响“感兴趣的”列的统计信息时。守护进程严格地按照一个被插入或更新行数的函数来计划ANALYZE
,它不知道那是否将导致有意义的统计信息改变。
正如用于空间恢复的清理一样,频繁更新统计信息对重度更新的表更加有用。但即使对于一个重度更新的表,如果该数据的统计分布没有很大改变,也没有必要更新统计信息。一个简单的经验法则是考虑表中列的最大和最小值改变了多少。例如,一个包含行被更新时间的timestamp
列将在行被增加和更新时有一直增加的最大值;这样一列将可能需要更频繁的统计更新,而一个包含一个网站上被访问页面 URL 的列则不需要。URL 列可以经常被更改,但是其值的统计分布的变化相对很慢。
可以在指定表上运行ANALYZE
甚至在表的指定列上运行,因此如果你的应用需要,可以更加频繁地更新某些统计。但实际上,通常只分析整个数据库是最好的,因为它是一种很快的操作。ANALYZE
对一个表的行使用一种统计的随机采样,而不是读取每一个单一行。
提示
- 尽管对每列的
ANALYZE
频度调整可能不是非常富有成效,你可能会发现值得为每列调整被ANALYZE
收集统计信息的详细程度。经常在WHERE
中被用到的列以及数据分布非常不规则的列可能需要比其他列更细粒度的数据直方图。见ALTER TABLE SET STATISTICS
,或者使用default_statistics_target配置参数改变数据库范围的默认值。
还有,默认情况下关于函数的选择度的可用信息是有限的。但是,如果你创建一个使用函数调用的表达式索引,关于该函数的有用的统计信息将被收集,这些信息能够大大提高使用该表达式索引的查询计划的质量。
- 自动清理守护进程不会为外部表发出
ANALYZE
命令,因为无法确定一个合适的频度。如果你的查询需要外部表的统计信息来正确地进行规划,比较好的方式是按照一个合适的时间表在那些表上手工运行ANALYZE
命令。
更新可见性映射
清理机制为每一个表维护着一个可见性映射,它被用来跟踪哪些页面只包含对所有活动事务(以及所有未来的事务,直到该页面被再次修改)可见的元组。这样做有两个目的。第一,清理本身可以在下一次运行时跳过这样的页面,因为其中没有什么需要被清除。
第二,这允许PostgreSQL回答一些只用索引的查询,而不需要引用底层表。因为PostgreSQL的索引不包含元组的可见性信息,一次普通的索引扫描会为每一个匹配的索引项获取堆元组,用来检查它是否能被当前事务所见。另一方面,一次只用索引的扫描会首先检查可见性映射。如果它了解到在该页面上的所有元组都是可见的,堆获取就可以被跳过。这对大数据集很有用,因为可见性映射可以防止磁盘访问。可见性映射比堆小很多,因此即使堆非常大,可见性映射也可以很容易地被缓存起来。
防止事务 ID 回卷失败
PostgreSQL的 MVCC 事务语义依赖于能够比较事务 ID(XID)数字:如果一个行版本的插入 XID 大于当前事务的 XID,它就是“属于未来的”并且不应该对当前事务可见。但是因为事务 ID 的尺寸有限(32位),一个长时间(超过 40 亿个事务)运行的集簇会遭受到事务 ID 回卷问题:XID 计数器回卷到 0,并且本来属于过去的事务突然间就变成了属于未来 — 这意味着它们的输出变成不可见。简而言之,灾难性的数据丢失(实际上数据仍然在那里,但是如果你不能得到它也无济于事)。为了避免发生这种情况,有必要至少每 20 亿个事务就清理每个数据库中的每个表。
周期性的清理能够解决该问题的原因是,VACUUM
会把行标记为 冻结,这表示它们是被一个在足够远的过去提交的事务所插入, 这样从 MVCC 的角度来看,效果就是该插入事务对所有当前和未来事务来说当然都 是可见的。PostgreSQL保留了一个特殊的 XID (FrozenTransactionId
),这个 XID 并不遵循普通 XID 的比较规则 并且总是被认为比任何普通 XID 要老。普通 XID 使用模-232算 法来比较。这意味着对于每一个普通 XID都有 20 亿个 XID “更老”并且 有 20 亿个“更新”,另一种解释的方法是普通 XID 空间是没有端点的环。 因此,一旦一个行版本创建时被分配了一个特定的普通 XID,该行版本将成为接下 来 20 亿个事务的“过去”(与我们谈论的具体哪个普通 XID 无关)。如 果在 20 亿个事务之后该行版本仍然存在,它将突然变得好像在未来。要阻止这一切 发生,被冻结行版本会被看成其插入 XID 为FrozenTransactionId
, 这样它们对所有普通事务来说都是“在过去”,而不管回卷问题。并且这样 的行版本将一直有效直到被删除,不管它有多旧。
注意
在9.4之前的PostgreSQL版本中,实际上会通过将一行的插入 XID 替换为FrozenTransactionId
来实现冻结,这种FrozenTransactionId在行的 xmin
系统列中是可见的。较新的版本只是设置一个标志位, 保留行的原始xmin
用于可能发生的鉴别用途。不过, 在9.4之前版本的数据库pg_upgrade中可能仍会找到 xmin
等于FrozenTransactionId
(2)的行。
此外,系统目录可能会包含xmin
等于BootstrapTransactionId
(1) 的行,这表示它们是在initdb的第一个阶段被插入的。 和FrozenTransactionId
相似,这个特殊的 XID 被认为比所有正常 XID 的年龄都要老。
vacuum_freeze_min_age控制在其行版本被冻结前一个 XID 值应该有多老。如果被冻结的行将很快会被再次修改,增加这个设置可以避免不必要 的工作。但是减少这个设置会增加在表必须再次被清理之前能够流逝的事务数。
VACUUM
通常会跳过不含有任何死亡行版本的页面,但是不会跳过那些含有带旧 XID 值的行版本的页面。要保证所有旧的行版本都已经被冻结,需要对整个表做一次扫描。vacuum_freeze_table_age控制VACUUM
什么时候这样做:如果该表经过vacuum_freeze_table_age
减去vacuum_freeze_min_age
个事务还没有被完全扫描过,则会强制一次全表清扫。将这个参数设置为 0 将强制VACUUM
总是扫描所有页面而实际上忽略可见性映射。
一个表能保持不被清理的最长时间是 20 亿个事务减去VACUUM
上次扫描全表时的vacuum_freeze_min_age
值。如果它超过该时间没有被清理,可能会导致数据丢失。要保证这不会发生,将在任何包含比autovacuum_freeze_max_age配置参数所指定的年龄更老的 XID 的未冻结行的表上调用自动清理(即使自动清理被禁用也会发生)。
这意味着如果一个表没有被清理,大约每autovacuum_freeze_max_age
减去vacuum_freeze_min_age
事务就会在该表上调用一次自动清理。对那些为了空间回收目的而被正常清理的表,这是无关紧要的。然而,对静态表(包括接收插入但没有更新或删除的表)就没有为空间回收而清理的需要,因此尝试在非常大的静态表上强制自动清理的间隔最大化会非常有用。显然我们可以通过增加autovacuum_freeze_max_age
或减少vacuum_freeze_min_age
来实现此目的。
vacuum_freeze_table_age
的实际最大值是 0.95 * autovacuum_freeze_max_age
,高于它的设置将被上限到最大值。一个高于autovacuum_freeze_max_age
的值没有意义,因为不管怎样在那个点上都会触发一次防回卷自动清理,并且 0.95 的乘数为在防回卷自动清理发生之前运行一次手动VACUUM
留出了一些空间。作为一种经验法则,vacuum_freeze_table_age
应当被设置成一个低于autovacuum_freeze_max_age
的值,留出一个足够的空间让一次被正常调度的VACUUM
或一次被正常删除和更新活动触发的自动清理可以在这个窗口中被运行。将它设置得太接近可能导致防回卷自动清理,即使该表最近因为回收空间的目的被清理过,而较低的值将导致更频繁的全表扫描。
增加autovacuum_freeze_max_age
(以及和它一起的vacuum_freeze_table_age
)的唯一不足是数据库集簇的pg_xact
和pg_commit_ts
子目录将占据更多空间,因为它必须存储所有向后autovacuum_freeze_max_age
范围内的所有事务的提交状态和(如果启用了track_commit_timestamp
)时间戳。提交状态为每个事务使用两个二进制位,因此如果autovacuum_freeze_max_age
被设置为它的最大允许值 20 亿,pg_xact
将会增长到大约 0.5 吉字节,pg_commit_ts
大约20GB。如果这对于你的总数据库尺寸是微小的,我们推荐设置autovacuum_freeze_max_age
为它的最大允许值。否则,基于你想要允许pg_xact
和pg_commit_ts
使用的存储空间大小来设置它(默认情况下 2 亿个事务大约等于pg_xact
的 50 MB存储空间,pg_commit_ts
的2GB的存储空间)。
减小vacuum_freeze_min_age
的一个不足之处是它可能导致VACUUM
做无用的工作:如果该行在被替换成FrozenXID
之后很快就被修改(导致该行获得一个新的 XID),那么冻结一个行版本就是浪费时间。因此该设置应该足够大,这样直到行不再可能被修改之前,它们都不会被冻结。
为了跟踪一个数据库中最老的未冻结 XID 的年龄,VACUUM
在系统表pg_class
和pg_database
中存储 XID 的统计信息。特别地,一个表的pg_class
行的relfrozenxid
列包含被该表的上一次全表VACUUM
所用的冻结截止 XID。该表中所有被有比这个截断 XID 老的普通 XID 的事务插入的行 都确保被冻结。相似地,一个数据库的pg_database
行的datfrozenxid
列是出现在该数据库中的未冻结 XID 的下界 — 它只是数据库中每一个表的relfrozenxid
值的最小值。一种检查这些信息的方便方法是执行这样的查询:
1 2 3 4 5 6 7 | SELECT c.oid::regclass as table_name, greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age FROM pg_class c LEFT JOIN pg_class t ON c.reltoastrelid = t.oid WHERE c.relkind IN ('r', 'm'); SELECT datname, age(datfrozenxid) FROM pg_database; |
age
列度量从该截断 XID 到当前事务 XID 的事务数。
VACUUM
通常只扫描从上次清理后备修改过的页面,但是只有当全表被扫描时relfrozenxid
才能被推进。当relfrozenxid
比vacuum_freeze_table_age
个事务还老时、当VACUUM
的FREEZE
选项被使用时或当所有页面正好要求清理来移除死亡行版本时,全表将被扫描。当VACUUM
扫描全表时,在它被完成后,age(relfrozenxid)
应该比被使用的vacuum_freeze_min_age
设置略大(比在VACUUM
开始后开始的事务数多)。如果在autovacuum_freeze_max_age
被达到之前没有全表扫描VACUUM
在该表上被发出,将很快为该表强制一次自动清理。
如果出于某种原因自动清理无法从一个表中清除旧的 XID,当数据库的最旧 XID 和回卷点之间达到 1 千一百万个事务时,系统将开始发出这样的警告消息:
1 2 | WARNING: database "mydb" must be vacuumed within 10985967 transactions HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database. |
(如该示意所建议的,一次手动的VACUUM
应该会修复该问题;但是注意该次VACUUM
必须由一个超级用户来执行,否则它将无法处理系统目录并且因而不能推进数据库的datfrozenxid
)。如果这些警告被忽略,一旦距离回卷点只剩下 1 百万个事务时,该系统将会关闭并且拒绝开始任何新的事务:
1 2 | ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb" HINT: Stop the postmaster and vacuum that database in single-user mode. |
这一百万个事务的富余是为了让管理员能通过手动执行所要求的VACUUM
命令进行恢复而不丢失数据。但是,由于一旦系统进入到安全关闭模式,它将不会执行命令。做这个操作的唯一方法是停止服务器并且以单一用户启动服务器来执行VACUUM
。单一用户模式中不会强制该关闭模式。关于使用单一用户模式的细节请见postgres参考页。
多事务和回卷
Multixact ID被用来支持被多个事务锁定的行。由于在一个元组头部 只有有限的空间可以用来存储锁信息,所以只要有多于一个事务并发地锁住一个行, 锁信息将使用一个“多个事务 ID”(或简称多事务 ID)来编码。任何特定 多事务 ID 中包括的事务 ID 的信息被独立地存储在pg_multixact
子目 录中,并且只有多事务 ID 出现在元组头部的xmax
域中。和事务 ID 类似,多事务 ID 也是用一个 32 位计数器实现,并且也采用了相似的存储,这些都要 求仔细的年龄管理、存储清除和回卷处理。在每个多事务中都有一个独立的存储区域 保存成员列表,它也使用一个 32 位计数器并且也应被管理。
在一次VACUUM
表扫描(部分或者全部)期间,任何比 vacuum_multixact_freeze_min_age 要老的多事务 ID 会被替换为一个不同的值,该值可以是零值、 一个单一事务 ID 或者一个更新的多事务 ID。 对于每一个表,pg_class
.relminmxid
存储了在该表任意元组中仍然存在的最老可能多事务 ID。如果这个值比 vacuum_multixact_freeze_table_age老, 将强制一次全表扫描。可以在 pg_class
.relminmxid
上使用mxid_age()
来找到它的年龄。
全表VACUUM
扫描(不管是什么导致它们)将为表推进该值。 最后,当所有数据库中的所有表被扫描并且它们的最老多事务值被推进, 较老的多事务的磁盘存储可以被移除。
作为一种安全设备,对任何多事务年龄超过 autovacuum_multixact_freeze_max_age的表, 都将发生一次全表清理扫描。如果已用的成员存储空间超过总量的 50%,全表清理扫描 也将逐步在所有表上进行,这会从那些具有最老多事务年龄的表开始。即使自动清理被 在名义上被禁用,这两中类型的全表扫描都将会发生。
自动清理后台进程
PostgreSQL有一个可选的但是被高度推荐的特性autovacuum,它的目的是自动执行VACUUM
和ANALYZE
命令。当它被启用时,自动清理会检查被大量插入、更新或删除元组的表。这些检查会利用统计信息收集功能,因此除非track_counts被设置为true
,自动清理不能被使用。在默认配置下,自动清理是被启用的并且相关配置参数已被正确配置。
“自动清理后台进程”实际上由多个进程组成。有一个称为 自动清理启动器的常驻后台进程(autovacuum launcher), 它负责为所有数据库启动自动清理工作者进程。 启动器将把工作散布在一段时间上,它每隔 autovacuum_naptime秒尝试在每个数据库中启动一个工作者 (因此,如果安装中有N
个数据库,则每 autovacuum_naptime
/N
秒将启动一个新的工作者)。 在同一时间只允许最多autovacuum_max_workers 个工作者进程运行。如果有超过autovacuum_max_workers
个数据库需要被处理,下一个数据库将在第一个工作者结束后马上被处理。 每一个工作者进程将检查其数据库中的每一个表并且在需要时执行 VACUUM
和/或ANALYZE
。 可以设置log_autovacuum_min_duration 来监控自动清理工作者的活动。
如果在一小段时间内多个大型表都变得可以被清理,所有的自动清理工作者可能都会被占用来在一段长的时间内清理这些表。这将会造成其他的表和数据库无法被清理,直到一个工作者变得可用。对于一个数据库中的工作者数量并没有限制,但是工作者确实会试图避免重复已经被其他工作者完成的工作。注意运行着的工作者的数量不会被计入max_connections或superuser_reserved_connections限制。
relfrozenxid
值比autovacuum_freeze_max_age事务年龄更大的表总是会被清理(这页表示这些表的冻结最大年龄被通过表的存储参数修改过,参见后文)。否则,如果从上次VACUUM
以来失效的元组数超过“清理阈值”,表也会被清理。清理阈值定义为:
1 | 清理阈值 = 清理基本阈值 + 清理缩放系数 * 元组数 |
其中清理基本阈值为autovacuum_vacuum_threshold, 清理缩放系数为autovacuum_vacuum_scale_factor, 元组数为pg_class
.reltuples
。
如果自上次清理以来插入的元组数量超过了定义的插入阈值,表也会被清理,该阈值定义为:
1 | 清理插入阈值 = 清理基础插入阈值 + 清理插入缩放系数 * 元组数 |
清理插入基础阈值为autovacuum_vacuum_insert_threshold,清理插入缩放系数为autovacuum_vacuum_insert_scale_factor。 这样的清理可以允许部分的表被标识为all visible,并且也可以允许元组被冻结,可以减小后续清理的工作需要。 对于可以接收INSERT
操作但是不能或几乎不能UPDATE
/DELETE
操作的表, 可能会从降低表的autovacuum_freeze_min_age中受益,因为这可能允许元组在早期清理中被冻结。 废弃元组的数量和插入元组的数量可从统计收集器中获得;它是一个半精确的计数,由每个UPDATE
、DELETE
和 INSERT
操作进行更新。 (它只是半精确的,因为一些信息可能会在重负载情况下丢失。) 如果表的relfrozenxid
值大于vacuum_freeze_table_age
事务老的, 执行一个主动的清理来冻结旧的元组,并推进relfrozenxid
;否则,只有上次清理以后修改过的页面被扫描。
对于分析,也使用了一个相似的阈值:
1 | 分析阈值 = 分析基本阈值 + 分析缩放系数 * 元组数 |
该阈值将与自从上次ANALYZE
以来被插入、更新或删除的元组数进行比较。
临时表不能被自动清理访问。因此,临时表的清理和分析操作必须通过会话期间的SQL命令来执行。
默认的阈值和缩放系数都取自于postgresql.conf
,但是可以为每一个表重写它们(和许多其他自动清理控制参数), 详情参见Storage Parameters。 如果一个设置已经通过一个表的存储参数修改,那么在处理该表时使用该值,否则使用全局设置。 全局设置请参阅第 19.10 节。
当多个工作者运行时,在所有运行着的工作者之间自动清理代价延迟参数 (参阅第 19.4.4 节)是 “平衡的”,这样不管实际运行的工作者数量是多少, 对于系统的总体 I/O 影响总是相同的。不过,任何正在处理已经设置了每表autovacuum_vacuum_cost_delay
或 autovacuum_vacuum_cost_limit
存储参数的表的工作者不会被考虑在均衡算法中。
autovacuum工作进程通常不会阻止其他命令。如果某个进程尝试获取与autovacuum持有的SHARE UPDATE EXCLUSIVE
锁冲突的锁,则锁获取将中断该autovacuum。有关冲突的锁定模式,请参见表 13.2。 但是,如果autovacuum正在运行以防止事务ID回卷(即在pg_stat_activity
视图中的autovacuum查询名以(to prevent wraparound)
结尾),则autovacuum不会被自动中断。
警告
定期运行需要获取与SHARE UPDATE EXCLUSIVE
锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成。
index_cleanup
有两个地方体现INDEX_CLEANUP
第一:vacuum的INDEX_CLEANUP选项
第二:create table的存储参数
建表时,当VACUUM
在此表上运行时表示启用或禁用索引清理。默认值为true
。 禁用索引清理可以显著加快VACUUM
,但如果表修改很频繁,也可能导致索引严重膨胀。 VACUUM的INDEX_CLEANUP
参数,如果指定,将覆盖此选项的值。
- 在pg12中,vacuum 可以通过index_cleanup选项跳过对index的清理
- 在pg12中可以在create table语句中定义存储参数vacuum_index_cleanup是否对本表的index进行清理
- 启用index_cleanup的副作用是会引起index的膨胀(index bloat),但是,若是主要目标是freeze old tuples,vacuum跳过INDEX_CLEANUP还是有用的。
- 如果vacuum不带INDEX_CLEANUP选项,vacuum会参考该表的VACUUM_INDEX_CLEANUP属性
1 2 3 4 5 6 7 8 9 | lhrdb=# VACUUM (VERBOSE ON, INDEX_CLEANUP OFF) test; INFO: vacuuming "public.test" INFO: "test": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 524 There were 0 unused item identifiers. Skipped 0 pages due to buffer pins, 0 frozen pages. 0 pages are entirely empty. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. VACUUM |
1 2 3 4 5 6 7 8 9 10 11 | lhrdb=# CREATE TABLE vacuum1(c1 INT, c2 VARCHAR(10)) WITH (VACUUM_INDEX_CLEANUP = OFF) ; CREATE TABLE lhrdb=# \d+ vacuum1; Table "public.vacuum1" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+-----------------------+-----------+----------+---------+----------+--------------+------------- c1 | integer | | | | plain | | c2 | character varying(10) | | | | extended | | Access method: heap Options: vacuum_index_cleanup=off |
是不是应该描述的时候顺便解释一下和maintenance_work_mem的关系
在维护操作比如:VACUUM(收集表和索引的统计信息,整理表和索引)、CREATE INDEX、ALTER TABLE ADD FOREIGN Key等中都会使用maintenance_work_mem内存缓冲区。