为了保持数据库的清洁,数据库需要日常清理,也就是Routine Vacuuming。
日常清理包括两部分,即VACCUM(垃圾回收)和ANALYZE(搜集统计信息)。不过VACCUM命令也可以同时执行ANALYZE。
日常清理可以自动做,即autovacuum 守护进程;也可以手工执行VACCUM和ANALYZE命令。自动做是常态,手工做是补充。
还可参考Introduction to VACUUM, ANALYZE, EXPLAIN, and COUNT
控制数据库自动维护
autovacuum默认开启,:
sql
-- Controls whether the server should run the autovacuum launcher daemon. on by default;
-- however, track_counts must also be enabled for autovacuum to work.
postgres=# show autovacuum;
autovacuum
------------
on
(1 row)
postgres=# \! ps -ef|grep vacuum
postgres 1060 1001 0 Jul20 ? 00:00:00 postgres: autovacuum launcher
-- Enables collection of statistics on database activity. on by default
postgres=# show track_counts;
track_counts
--------------
on
(1 row)
和VACUUM命令相关的参数:
sql
postgres=# show vacuum_
vacuum_buffer_usage_limit vacuum_cost_page_dirty vacuum_failsafe_age vacuum_multixact_failsafe_age
vacuum_cost_delay vacuum_cost_page_hit vacuum_freeze_min_age vacuum_multixact_freeze_min_age
vacuum_cost_limit vacuum_cost_page_miss vacuum_freeze_table_age vacuum_multixact_freeze_table_age
和autovacuum守护进程相关的参数:
sql
postgres=# show autovacuum
autovacuum autovacuum_max_workers autovacuum_vacuum_cost_limit autovacuum_vacuum_threshold
autovacuum_analyze_scale_factor autovacuum_multixact_freeze_max_age autovacuum_vacuum_insert_scale_factor autovacuum_work_mem
autovacuum_analyze_threshold autovacuum_naptime autovacuum_vacuum_insert_threshold
autovacuum_freeze_max_age autovacuum_vacuum_cost_delay autovacuum_vacuum_scale_factor
简单来说,如果autovacuum启用(),则每隔autovacuum_naptime(默认1分钟)唤醒一次,并决定是否运行 VACUUM、ANALYZE 或两者兼而有之。任何时候运行的维护进程数量都不会超过 autovacuum_max_workers(默认为3) 个。这些自动清理工作进程在执行 I/O 操作时,会累积成本点,直到达到 autovacuum_vacuum_cost_limit 值(默认为-1,即使用vacuum_cost_limit的值,默认为200),之后会休眠一段 autovacuum_vacuum_cost_delay (默认2毫秒)时间。
当至少有 autovacuum_analyze_threshold 行(默认为50)更改,并且由 autovacuum_analyze_scale_factor (默认为10%)定义的表的一部分已被插入、更新或删除时,autovacuum 将运行 ANALYZE。
当至少有 autovacuum_vacuum_threshold行(默认为50)更改,并且由 autovacuum_vacuum_scale_factor (默认为20%)定义的表的一部分已被更新或删除时,autovacuum 将运行 VACUUM。
如果自动清理操作至少运行了log_autovacuum_min_duration指定的时间(默认值为 10 分钟),则操作被记录。设置为零将记录所有自动清理操作。设置为 -1 将禁用记录自动清理操作。
以上参数都是针对所有表的,也可针对单独的表设置。使用ALTER TABLE SET ( storage_parameter [= value] [, ... ] )。storage parameter的帮助见这里。
避免自动冻结
vacuum的一个重要目的是避免防止事务 ID 回绕失败,详见Preventing Transaction ID Wraparound Failures。
因为事务ID(XID)是4个字节表示的,大概40亿多一点。而XID是递增的。正常情况下,较大的XID表示其发生的事件靠后。但当XID耗尽重新回到0,此时时间先后的顺序就需要别的机制来判断了。
在PG的表中,每一行都有2个隐含列:xmin和xmax。
- xmin为此行版本的插入事务的标识(事务 ID)。
- xmax为删除事务的标识(事务 ID),对于未删除的行版本则为零。
详见博客PostgreSQL 事务ID环绕问题。
删除导致数据库膨胀的问题
磁盘空间膨胀可能是由长时间运行的查询或长时间运行的写入事务以及写入密集型工作负载共同导致的。
VACUUM 无法移除dead row,除非它们对所有用户不可见。因此要避免长时间不提交的事务。可通过监控pg_stat_activity的列state的值为idle in transaction的会话。必要时可设置idle_in_transaction_session_timeout。
针对临时表重度用户的操作
创建临时表时,我们会将条目插入到 pg_class、pg_type 和 pg_attribute 系统表中。这些系统表及其索引会开始增长并膨胀。为了控制这种增长,您可以手动或自动vacuum。
查看系统表及索引:
sql
SELECT relname, pg_relation_size(oid) FROM pg_class
WHERE relkind in ('i','r') AND relnamespace = 'pg_catalog'::regnamespace
ORDER BY 2 DESC;
另外,也可考虑增加temp_buffers,以及将临时表的IO和数据表的分开。
识别并修复膨胀的表和索引
MVCC 是 PostgreSQL 的核心部分,无法关闭,因为它能够实现高并发性和高性能数据库。MVCC 的内部机制中:每一行代表一个行版本,因此它有两个系统列: xmin 和 xmax - 分别表示创建和删除该版本时两个事务的标识符。如果该版本尚未删除,则 xmax 的值为 NULL。
一般来说,我们不是删除行版本,而是通过更改它们的 xmin 和/或 xmax 值来改变它们的可见性。当插入一行时,其 xmin 值设置为创建事务的XID,而 xmax 为空;当删除一行时,xmax 设置为删除事务的"XID",而不会真正删除该行。UPDATE 操作的处理方式类似于 DELETE 操作加 INSERT;删除的行代表旧版本,而插入的行代表新版本。最后,当回滚事务时,通过将事务 ID 标记为"已中止",使其所有更改都变得不可见。
这样,我们可以获得更快的 DELETE、UPDATE 和 ROLLBACK ,但代价导致表和索引的大小增长,因为它们会留下死行版本。DELETE 和中止的 INSERT 语句会占用空间,这些空间必须通过VACUUM来回收。
除VACUUM外,PG还有一个特性Heap-Only Tuples (HOT)可缓解此问题。
建议安装pgstattuple扩展,监控表的空间情况:
sql
sampledb=# CREATE EXTENSION pgstattuple;
CREATE EXTENSION
sampledb=# SELECT * FROM pgstattuple('pg_catalog.pg_proc');
table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
811008 | 3318 | 748610 | 92.31 | 10 | 3928 | 0.48 | 30452 | 3.75
(1 row)
sampledb=# SELECT * FROM pgstattuple('hr.employees');
table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
16384 | 107 | 10848 | 66.21 | 1 | 92 | 0.56 | 4628 | 28.25
(1 row)
sampledb=# select * from pgstattuple_approx('hr.employees');
table_len | scanned_percent | approx_tuple_count | approx_tuple_len | approx_tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | approx_free_space | a
pprox_free_percent
-----------+-----------------+--------------------+------------------+----------------------+------------------+----------------+--------------------+-------------------+--
-------------------
16384 | 100 | 107 | 10848 | 66.2109375 | 1 | 92 | 0.5615234375 | 4628 |
28.2470703125
(1 row)
监控和调优 Vacuum 操作
通过ps或 pg_stat_activity,可获取autovacuum的PID。每当 VACUUM 运行时,pg_stat_progress_vacuum 视图都会为当前正在清理的每个后端(包括autovacuum 工作进程)包含一行。VACUUM FULL 命令的进度通过 pg_stat_progress_cluster 报告,因为 VACUUM FULL 和 CLUSTER 都会重写表,而常规 VACUUM 只会就地修改表。
以下是通过watch捕捉到的vacuum进度信息:
sql
postgres=# SELECT * FROM pg_stat_progress_vacuum;
Wed 23 Jul 2025 03:36:52 AM UTC (every 2s)
pid | datid | datname | relid | phase | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuples | num_dead_tuples
-------+-------+----------+-------+---------------------+-----------------+-------------------+--------------------+--------------------+-----------------+-----------------
14007 | 5 | postgres | 37801 | cleaning up indexes | 549 | 549 | 549 | 0 | 159759 | 0
(1 row)
max_parallel_maintenance_workers可设置vacuum的并行度,但对于同一个表不会启用并行。
维护索引
autovacuum 不会检测膨胀的索引,也不会执行任何重建索引的操作。必要时可通过reindexdb实用程序或reindex SQL 命令来重建索引。重建索引支持并行和建立在新的表空间。
查找未使用的索引
如果未使用,则idx_scan为0:
sql
schemaname | relname | indexrelname | idx_scan
------------+-------------+-------------------------+----------
hr | employees | emp_name_ix | 0
hr | departments | dept_id_pk | 0
hr | departments | dept_location_ix | 0
hr | jobs | job_id_pk | 0
hr | locations | loc_id_pk | 0
hr | locations | loc_city_ix | 0
hr | locations | loc_country_ix | 0
hr | locations | loc_state_province_ix | 0
hr | regions | reg_id_pk | 0
hr | job_history | jhist_emp_id_st_date_pk | 0
hr | job_history | jhist_department_ix | 0
hr | job_history | jhist_employee_ix | 0
hr | job_history | jhist_job_ix | 0
hr | employees | emp_email_uk | 0
hr | countries | country_c_id_pk | 0
hr | employees | emp_department_ix | 0
hr | employees | emp_job_ix | 0
hr | employees | emp_manager_ix | 0
hr | employees | emp_emp_id_pk | 1
(19 rows)
这些表都太小了,所以索引暂时用不上,可以SET enable_seqscan = off;强制使用索引。
小心删除不需要的索引
在确认删除前,可以先将索引置为无效:
sql
BEGIN;
-- Mark index as invalid
UPDATE pg_index
SET indisvalid = false
WHERE indexrelid = 'your_index_name'::regclass;
COMMIT;
维护计划
监控系统并不能替代良好周密的规划。它们的作用在于提醒您注意需要应对的突发情况。您需要应对的突发事件越多,同时处理多起紧急情况的可能性就越大。而一旦发生这种情况,设备就可能出现故障。归根结底,这都是您的责任。如果您希望认真履行职责,就应该为此做好准备。
以下为重点维护任务:
- 日常备份
- 监控空间增长
- 数据恢复验证和演练
- 日常清理:vacuum
- 日志分析
- 安全监控和分析
- 了解数据库使用模式
- 持续性能分析
您或许还可以抽出时间考虑以下问题:
- 数据质量:数据库内容是否准确且有意义?数据是否可以改进?
- 商业智能:数据是否被用于所有能为组织带来价值的方面?