在PostgreSQL中,清理是一项维护任务,有助于优化数据库性能和回收空间。它涉及从表和索引中删除已删除或过时的行,以及更新查询计划器使用的统计信息。这个过程对于防止不必要的数据(称为“死行”)的积累是必要的,这些数据会占用大量空间并降低查询速度。

多版本并发控制(MVCC)

为了保持一致性和防止并发更新导致的数据丢失,PostgreSQL采用了多版本并发控制(MVCC)。PostgreSQL和其他数据库管理系统使用MVCC来确保读取的一致性,并防止并发更新造成的数据丢失。PostgreSQL是通过在数据库中存储每一行的多个版本来实现的,允许事务访问一致的数据快照。

在PostgreSQL中,表中的每一行都被分配了一个事务ID,称为“xmin”。这个ID表示插入该行的事务。更新或删除行时,不会立即从表中删除该行。相反,该行的新版本被插入一个新的事务ID,而旧版本被标记为“死亡”,事务ID称为“xmax”。

当事务读取一行时,它利用xmin和xmax值来确定该行对事务是否可见。如果xmin值大于事务的“快照”(事务开始时正在进行的事务id的记录),则事务对该行不可见。如果xmax值等于事务的ID,则表示该行已被事务删除,也不可见。在所有其他情况下,行对事务是可见的。

这允许事务访问一致的数据快照,因为它们只能看到事务开始时提交的行。它还可以防止并发更新导致的数据丢失,因为冲突的更新会导致插入新的行版本,而不是覆盖现有数据。

虽然MVCC由于需要维护每一行的多个版本而在存储和性能方面产生了一些开销,但它是PostgreSQL和其他支持并发更新的数据库系统的一个重要特性。

这允许存储每一行的多个版本,使事务能够访问一致的数据快照。但是,当更新或删除行时,这会导致死行的积累。

vacuum

PostgreSQL中的vacuum过程通过删除不再需要的行来帮助维护数据库的性能和空间效率。

这些行被称为“死行”,之所以会累积,是因为PostgreSQL使用MVCC允许多个事务同时访问相同的数据而不会发生冲突。在真空过程中,会扫描表和索引,并删除这些死行,这有助于回收空间并提高查询性能。必须定期运行真空以保持数据库平稳运行。MVCC存储每一行的多个版本,因此当更新或删除一行时,死行不会立即被删除。

vacuum过程删除死行并更新查询计划器使用的统计信息,以更准确地估计查询返回的行数,并选择最有效的执行计划。在PostgreSQL中有两种类型的vacuum: VACUUM和ANALYZE。VACUUM删除死行并更新统计信息,而ANALYZE只更新统计信息。通常建议同时运行VACUUM和ANALYZE。

VACUUM进程可以使用SQL命令手动启动,也可以使用autovacuum后台进程自动启动,后台进程根据可配置的阈值(如表中的死行数或活行数)运行。在PostgreSQL 15中,对VACUUM过程进行了优化,使其更高效、更快地VACUUM大型表。它包括了一些改进,比如能够并行地清空表的多个分区、并发地清空索引,以及跳过不受更新影响的清空索引。

从技术角度来看,PostgreSQL 15中的VACUUM过程包括几个组件。VACUUM守护进程(autovacuum)根据可配置的阈值启动VACUUM操作。VACUUM工作程序执行实际的VACUUM操作,扫描表或索引,并在更新统计信息时删除死行。

Autovacuum,在PostgreSQL中默认启用,可以使用PostgreSQL .conf文件中的几个参数进行配置。PostgreSQL有几个与VACUUM相关的设置,可以通过配置来控制VACUUM进程的运行方式。你可以在postgresql.conf文件中找到以下设置,包括:

  • autovacuum:该设置启用或禁用autovacuum后台进程。默认情况下,autovacuum是启用的。
  • autovacuum_vacuum_threshold:该设置确定在清空表之前必须在表中出现的死行数的最小值。缺省值是50。
  • autovacuum_analyze_threshold:该设置确定在分析表之前必须在表中出现的最小活动行数。缺省值是50。
  • autovacuum_vacuum_scale_factor:该设置是一个乘数,根据表大小决定触发VACUUM需要多少死行。默认值为0.2。
  • autovacuum_analyze_scale_factor:这个设置是一个乘数,它决定根据表的大小触发分析需要多少活动行。缺省值为0.1。
  • autovacuum_vacuum_cost_delay:该设置确定autovacuum在开始VACUUM操作之前等待的时间(以毫秒为单位)。缺省值为20。
  • autovacuum_vacuum_cost_limit:该设置确定在单个VACUUM操作中可以VACUUM的最大行数。缺省值为200。

下面是在postgresql.conf文件中配置一些vacuum设置的示例:

autovacuum = on 
autovacuum_vacuum_threshold = 100 
autovacuum_analyze_threshold = 100
autovacuum_vacuum_scale_factor = 0.5
autovacuum_analyze_scale_factor = 0.2
autovacuum_vacuum_cost_delay = 50
autovacuum_vacuum_cost_limit = 500

在本例中,启用了autovacuum,并将VACUUM和ANALYZE的阈值设置为100。VACUUM和ANALYZE的规模因素为0.5和0.2,分别,这意味着一个VACUUM将每1000个活行表中(0.5 x 100)被触发时50个死行,和一个ANALYZE表中每1000行(0.2×100)时有20个行将触发。VACUUM成本延迟设置为50毫秒,VACUUM成本限制设置为500行,这意味着autovacuum将在开始VACUUM操作之前等待50毫秒,并且一次最多VACUUM500行。

重要的是配置这些设置,以确保VACUUM和ANALYZE正常有效地运行,并且不会对数据库造成过多的负载。这也是一个好主意,监测活动的自动VACUUM和手动VACUUM表,没有充分维护的autovacuum。

也可以使用表的存储参数中的autovacuum_vacuum_cost_delay和autovacuum_vacuum_cost_limit参数对每个表进行配置。这些参数控制如何积极地autovacuum,与较低的成本延迟导致VACUUM运行更频繁和较高的成本限制,允许更多的行一次VACUUM。

并行vacuum

并行VACUUM是PostgreSQL中的一个功能,它允许VACUUM进程在多核处理器上并发运行,从而提高VACUUM操作的性能。这对于清理大型表特别有用,因为它允许VACUUM进程使用多个cpu并行扫描和处理表。

并行VACUUM是在PostgreSQL 13中作为实验特性引入的,并在PostgreSQL 14中普遍使用。要使用并行VACUUM,你需要将postgresql.conf配置文件中的“max_parallel_workers_per_gather”参数设置为大于1的值。要启用并行处理,请在运行VACUUM或分析命令时指定“parallel”选项。

例如,要在名为“foo_table”的表上运行并行真空,可以使用以下命令:

VACUUM (PARALLEL, ANALYZE) foo_table;

当在整个模式或数据库上运行vacuum或analyze命令时,你也可以指定“PARALLEL”选项:

VACUUM (PARALLEL, ANALYZE) schema_name.*; VACUUM (PARALLEL, ANALYZE);

注意:请记住,并行VACUUM可能会增加数据库服务器的负载,因为它需要同时使用多个CPU内核。在使用并行VACUUM时,您应该仔细监视数据库的性能,并根据需要调整“max_parallel_workers_per_gather”参数,以便为您的工作负载找到最佳设置。

事务回卷

事务回卷(Transaction wraparound)是PostgreSQL在达到最大事务ID (TXID)时发生的一种现象,系统将其封装以重用旧的事务ID。如果数据库中仍然有事务ID高于当前最大值的行,则会导致问题,因为它们将被视为“死亡”并被VACUUM进程删除。

要理解环绕事务的工作原理,就必须理解PostgreSQL如何管理事务id。PostgreSQL事务被分配一个唯一的事务ID,称为“xid”。xid为32位整数,最大值为2的32-1次方,即4294967295。当达到这个最大值时,系统会封装并重用旧的xids。

为了防止事务环绕导致问题,运行VACUUM进程并定期删除死行是很重要的。您可以使用以下查询来检查存在事务返转风险的表。

vacuum统计

查看当前模式下所有表的VACUUM历史:

此查询检索当前数据库中“public”模式下所有表的VACUUM统计信息。此查询可以帮助监视VACUUM进程的状态,并识别可能需要进行VACUUM处理或ANALYZE的表。例如,如果一张表有很多死行,或者最近没有进行过清理或分析。在这种情况下,可能值得运行手动ANALYZE或ANALYZE操作来提高数据库的性能。

SELECT
    n.nspname as schema_name,
    c.relname as table_name,
    c.reltuples as row_count,
    c.relpages as page_count,
    s.n_dead_tup as dead_row_count,
    s.last_vacuum,
    s.last_autovacuum,
    s.last_analyze,
    s.last_autoanalyze
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
WHERE c.relkind = 'r' AND n.nspname = 'public';

查看自上次VACUUM以来被修改的表的列表:

SELECT
    n.nspname as schema_name,
    c.relname as table_or_index_name,
    c.relkind as table_or_index,
    c.reltuples as row_count,
    s.last_vacuum,
    s.last_autovacuum,
    s.last_analyze,
    s.last_autoanalyze
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
WHERE (c.relkind = 'r' or c.relkind = 'i')
AND (s.last_vacuum < s.last_autovacuum OR s.last_vacuum < s.last_analyze);

查看有大量死行的表和索引:

SELECT
    n.nspname as schema_name,
    c.relname as table_name,
    c.reltuples as row_count,
    s.n_dead_tup as dead_row_count
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
WHERE c.relkind = 'r' AND s.n_dead_tup > 0;

SELECT
    n.nspname as schema_name,
    c.relname as index_name,
    s.n_dead_tup as dead_row_count
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
    WHERE (c.relkind = ''r'' or c.relkind = ''i'')
    AND (s.last_vacuum < s.last_autovacuum OR s.last_vacuum < s.last_analyze)') AS t(
        schema_name text,
        table_or_index_name text,
        table_or_index char(1),
        row_count bigint,
        last_vacuum timestamp,
        last_autovacuum timestamp,
        last_analyze timestamp,
        last_autoanalyze timestamp
    );

您可以使用dblink为所有数据库编写上述查询。这将给出所有数据库的信息。

SELECT *
FROM dblink('host=<host> port=<port> dbname=<database> user=<username> password=<password>',
    'SELECT
        n.nspname as schema_name,
        c.relname as table_or_index_name,
        c.relkind as table_or_index,
        c.reltuples as row_count,
        s.last_vacuum,
        s.last_autovacuum,
        s.last_analyze,
        s.last_autoanalyze
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
    WHERE (c.relkind = ''r'' or c.relkind = ''i'')
    AND (s.last_vacuum < s.last_autovacuum OR s.last_vacuum < s.last_analyze)') AS t(
        schema_name text,
        table_or_index_name text,
        table_or_index char(1),
        row_count bigint,
        last_vacuum timestamp,
        last_autovacuum timestamp,
        last_analyze timestamp,
        last_autoanalyze timestamp
    );

注意:您需要将占位符<host>、<port>、<database>、<username>和<password>替换为服务器的实际值。

总结

重要的是正确配置autovacuum,以避免使用过多的VACUUM使数据库过载。这也是一个好主意,监测活动的autovacuum和手动VACUUM表,没有充分维护的autovacuum。

总而言之,在PostgreSQL中,VACUUM是一项基本的维护任务,有助于回收空间和提高性能。一般建议定期运行VACUUM,可以手动使用vacuum SQL命令,也可以自动使用autovacuum后台进程。这有助于确保数据库保持高效和无死行,并降低事务环绕的风险。autovacuum是自动化这一过程的一种方便方式,但重要的是要配置和监控它,以确保它有效和充分地运行。

posted on 2024-01-15 22:11  jl1771  阅读(38)  评论(0编辑  收藏  举报