PostgreSQL-VACUUM介绍
1. VACUUM介绍
PostgreSQL VACUUM 流程
PostgreSQL 实际上不会在您发出 statement 时物理地删除数据。相反,该数据在逻辑上被标记为内部已删除,然后不会显示在针对表的查询中。没有VACUUM,你不仅会填满您的驱动器,而且你将无法获得最新的统计数据。
- VACUUM
删除
更新TABLESPACE VACUUM
。 - 该过程检查表和索引,生成统计信息,然后将该信息存储在名为 的系统目录(系统表)中。
VACUUM
VACUUM;
虽然已删除行中的空间被回收以供重复使用,但数据库的实际大小不会缩小。例外情况是,当表的尾端有完全空白的页面时。在这种情况下,可以看到空间被完全回收。
VACUUM (FULL);
此命令会将数据库中的所有表重新构建为新表。这会带来巨大的开销,并且肯定会在移动数据时导致阻塞。这也将导致系统上出现大量 IO。但是,它将删除每个空白页面,为操作系统回收空间。
# 在手动运行 VACUUM 时,您还可以将特定表作为目标:
VACUUM radio.antenna;
# 您甚至可以指定表列表:
VACUUM radio.antenna, radio.bands, radio.digitalmodes;
# 在这两种情况下,只有列出的一个或多个表将完成 VACUUM 清理过程,而不是访问数据库中我有权访问的每个表
为了真正了解发生了什么,我们可以利用一个附加参数VERBOSE
。我将加载一个包含一些数据的表,然后删除该数据。然后,我们将运行VACUUM
:
INSERT INTO radio.countries
(country_name)
SELECT generate_series(1,15000,1);
DELETE FROM radio.countries
WHERE country_id BETWEEN 3 AND 12000;
VACUUM (VERBOSE) radio.countries;
VACUUM分析
# 更新统计信息的功能
VACUUM (ANALYZE);
# ANALYZE先开启再使用VACUUM
ANALYZE;
# 还可以利用VERBOSE参数来查看ANALYZE运行时发生的情况
ANALYZE (VERBOSE) radio.countries;
2. Autovacuum介绍
什么是Autovacuum?
负责清理和优化 Postgres 表存储的自动进程
重要性
Vacuum不仅仅是清理存储空间。在数据不断变化的环境中,Postgres 表通常会遇到过多的插入、更新和删除操作。此活动可能会导致表膨胀。当表的物理占用空间远超过其实际保存的数据大小时,就会发生表膨胀。
实现最佳系统性能
Vacuum是必不可少的。但是,它也是需要花销的的,如果不检查,可能会给系统带来负担。平衡在于autovacuum_vacuum_cost_delay
和autovacuum_vacuum_cost_limit
。autovacuum_vacuum_cost_delay
是 autovacuum 进程在达到autovacuum_vacuum_cost_limit
时停止处理的时间。想象一下这一系列事件 - 一个表达到 10% 的 bloate,这意味着 10% 的元组是死的。当达到 10% 阈值时,autovacuum worker 开始工作并开始产生成本。当该成本达到 autovacuum_vacuum_cost_limit
时,它将在 autovacuum_vacuum_cost_delay
指定的持续时间内暂停,然后继续工作,直到完成。
参数 autovacuum_vacuum_scale_factor
表示 table 大小的部分,当被 “dead” 行超过时,会提示 vacuum 操作。对于数据更改频繁的表,减小此值可能会有所帮助。
Postgres 中的 autovacuum_naptime
参数指定 autovacuum 在任何给定数据库上运行之间的最小延迟。默认值(我们之前设置的)为 1min
。
Autovacuum
默认情况下启用,PostgreSQL 中有一个守护进程,它将完全自动地在系统上的数据库上运行VACUUM
和ANALYZE
。该过程非常复杂且高度可定制,因此可以对基本行为进行大量更改。
autovacuum_max_workers
:默认可以同时运行的线程数为 3。autovacuum_naptime
:每 60 秒启动一次。autovacuum_vacuum_threshold
:VACUUM
必须超过autovacuum_vacuum_threshold
值,默认值为 50 个元组或行。autovacuum_vacuum_insert_threshold
:默认有 1,000 个元组。autovacuum_vacuum_insert_scale_factor
:默认情况下为给定表的 20% 的行。autovacuum_analyze_threshold
:默认情况下为 50 个元组
有一个阈值来确定给定的表是否将经历VACUUM
和ANALYZE
过程。 VACUUM
必须超过autovacuum_vacuum_threshold
值,默认值为 50 个元组或行。比这稍微复杂一点,因为有一个计算涉及 autovacuum_vacuum_insert_threshold
,默认有 1,000 个元组,然后将其添加到 autovacuum_vacuum_insert_scale_factor
,默认情况下为给定表的 20% 的行。然后将该值乘以表中的元组数。
ANALYZE
是类似的。 autovacuum_analyze_threshold
默认情况下为 50 个元组,根据 autovacuum_analyze_scale_factor
、表的 10% 以及达到分析阈值的元组数量。
所有这些设置都可以在服务器级别或表级别进行控制,从而可以对自动VACUUM
和自动ANALYZE
运行方式进行高度控制。你可能会发现,与 SQL Server 中的统计信息更新类似,自动化流程需要进行调整,或者通过偶尔的手动更新进行增强。
3. 问题研究
3.1
1: vacuum和freeze的参数非常多,如果要实现自动动态调整某些参数,从而降低vacuum对pg正常SQL执行的影响,有哪些参数可以调整?如何做到怎么动态配置相关参数而不让vaccum影响应用系统的稳定性? 比如在某些时刻,用户更新和删除非常频繁,此时应该如何调整?而在晚上或空闲时又如何调整?
1. 关键 VACUUM
参数
autovacuum_vacuum_cost_delay
:该参数控制autovacuum
操作的延迟时间,默认是 20 毫秒。增加此值可以降低VACUUM
的资源占用,但会使VACUUM
操作变慢。- 高负载时:可以将其设置为较高的值(如 50-100 毫秒)以减少资源竞争。
- 低负载时:可以将其设置为较低的值(如 10-20 毫秒)以加快
VACUUM
速度。
autovacuum_vacuum_cost_limit
:该参数决定了autovacuum
一次能够消耗的 I/O 成本上限。- 高负载时:降低此值(如 50-100),减少 I/O 消耗。
- 低负载时:增加此值(如 200-1000),使
VACUUM
更快完成。
autovacuum_naptime
:表示autovacuum
的启动间隔时间,默认为 1 分钟。减小此值可以更频繁地触发autovacuum
,对及时清理有帮助。- 高负载时:可以适当增大(如 5 分钟),避免过频繁的
autovacuum
影响系统性能。 - 低负载时:可以减小(如 30 秒),使得
VACUUM
更及时地清理碎片。
- 高负载时:可以适当增大(如 5 分钟),避免过频繁的
maintenance_work_mem
:VACUUM
过程中用于维护索引的内存分配。- 高负载时:减小该值(如 64MB),降低内存使用。
- 低负载时:适当增大(如 512MB),提高
VACUUM
操作的效率。
2. 动态调整方案
为了在不同时间段动态调整这些参数,可以结合以下几种方式:
- 手动调优:通过管理计划或手动命令调整
autovacuum
相关参数。例如,可以使用定时任务在白天和晚上分别运行ALTER SYSTEM
命令来设置不同的参数,并使用pg_reload_conf()
重新加载配置。 - 基于脚本的自动化调优:编写脚本监控系统负载和
VACUUM
的运行状态,动态地在不同负载情况下自动调整参数。可以使用psql
命令行工具结合crontab
定时任务执行自动化调整。 - 动态检测更新频率:可以在数据库中检测高频更新的表,针对这些表设置自定义的
autovacuum
配置。例如,使用ALTER TABLE ... SET (autovacuum_vacuum_cost_delay = ...)
为频繁更新的表独立配置autovacuum
参数。
3.2
2: xid耗尽的原因?哪些场景会导致无法freeze相关事务号?发生xid耗尽了该如何抢救?如何预防?如何避免因为freeze导致IO风暴?也就是如何做到自适应,以自动调整freeze相关参数或者强制进行某些操作,避免xid耗尽
因为PG的版本号是uint32的,是重复使用的,所以每隔大约20亿个事务后,必须要冻结,否则记录会变成未来的,对当前事务"不可见",现在,还可以通过行的t_infomask
来区分行是否为冻结行。
1. xid耗尽的原因?
- 长时间未运行
VACUUM
操作:未及时运行VACUUM
操作会导致旧事务的XID
未被回收。 - 活跃长事务:长时间运行的事务会阻止
VACUUM
标记旧事务为可回收,导致不能冻结(FREEZE
)这些事务号。 - 高频事务更新:某些表存在频繁的插入、更新、删除操作,会更快消耗
XID
。 autovacuum
配置不当:autovacuum
是 PostgreSQL 中自动清理旧事务的机制,若配置不合理,可能无法按需触发,导致旧事务版本未被及时回收。autovacuum_vacuum_cost_limit
过低,限制了VACUUM
的资源使用;autovacuum_naptime
间隔太长,导致清理不够频繁;以及autovacuum_freeze_max_age
设置过大,导致冻结延迟。- 表级别禁用
autovacuum
:某些表可能被手动禁用了autovacuum
(例如对大表或特定表设置了ALTER TABLE ... SET (autovacuum_enabled = false);
),这意味着这些表的事务ID不会被自动清理,从而导致其XID
累积并耗尽。 - 阻塞
VACUUM
的锁:当表或索引被长时间锁定时,VACUUM
无法获得所需的锁而无法执行。这种情况在数据库中发生较多锁争用或高并发锁定的情况下更为常见。 - 未对 TOAST 表执行
VACUUM
:在 PostgreSQL 中,TOAST
表用于存储较大的字段数据。TOAST
表需要单独清理和冻结。如果主表执行了VACUUM
而TOAST
表未及时清理,TOAST
表中旧的事务ID会积累,导致XID
耗尽风险。- 未及时冻结老旧数据:PostgreSQL 通过
VACUUM FREEZE
将非常旧的事务号标记为“冻结”状态,使其在XID
循环后依然有效。如果冻结未及时执行,接近XID
极限的老旧数据会占用大量XID
,加速耗尽。
2. 导致无法freeze相关事务号?
- 长时间未提交的长事务:如果某些事务长时间未提交(例如开启了事务但忘记提交或回滚),这些事务会保持活跃状态,阻止
VACUUM FREEZE
将旧事务号冻结。因为 PostgreSQL 需要保留旧版本的数据,以便这些长事务能够访问一致的历史快照数据。 - 长时间运行的游标:PostgreSQL 支持使用游标处理大数据量查询,以便分步获取结果集。长时间未关闭的游标会保留旧数据快照,阻止
VACUUM
和FREEZE
操作的正常进行。 - 并发高频更新:在一些高并发系统中,表中可能存在大量的更新和删除操作,这会导致生成大量的旧事务版本(即“垃圾”版本)。在这种情况下,
autovacuum
的默认配置可能无法及时回收和冻结这些旧版本。 - 表锁阻塞:某些场景下,大量的锁争用会影响
VACUUM
操作。如果表处于繁忙的锁争用状态(例如被长时间锁定或高并发锁竞争),VACUUM FREEZE
操作无法获得必要的锁从而完成冻结。 - 临时表未自动冻结:临时表会在
autovacuum
进程中被自动忽略,因为它们是会话级别的表,通常在会话结束后自动清理。然而,如果会话保持较长时间活跃,临时表中的事务号不会被VACUUM
自动冻结,可能导致这些表的XID
耗尽问题。 - 禁用
autovacuum
或不合理的配置:如果某些表被禁用了autovacuum
(例如通过ALTER TABLE
禁用),这些表不会自动触发VACUUM FREEZE
,从而导致其事务号得不到及时冻结。
3. 发生xid耗尽了该如何抢救?
当 PostgreSQL 发生 XID
耗尽时,数据库将无法正常启动新事务并可能会进入只读状态,甚至完全停止响应。
- 立即手动执行
VACUUM FREEZE
:
-
确保数据库当前可操作时,手动对所有表执行
VACUUM FREEZE
操作,以最大化冻结旧的XID
并回收事务ID。 -
可以优先处理
XID
age 接近耗尽的表(可以通过pg_stat_all_tables
查看),确保回收最老的事务ID。 -
执行以下 SQL 查询以找到即将耗尽 XID 的表:
SELECT relname, age(relfrozenxid) AS xid_age FROM pg_class WHERE relkind = 'r' -- 'r' 表示普通表 ORDER BY age(relfrozenxid) DESC LIMIT 10;
-
然后对这些表执行手动 VACUUM FREEZE:
VACUUM FREEZE table_name;
-
在只读模式下运行(如果必要):
-
在抢救期间,将数据库设置为只读模式,阻止新的写事务生成新的
XID
,从而减缓耗尽速度。可以在数据库层面或表级别设置为只读模式。 -
通过以下命令将数据库设置为只读模式:
ALTER DATABASE database_name SET default_transaction_read_only = on;
-
-
临时增大
autovacuum_freeze_max_age
:
-
如果还没有完全耗尽,调整
autovacuum_freeze_max_age
为一个较小的值,强制触发更频繁的自动冻结。 -
修改系统配置并重载配置(若数据库仍在运行):
ALTER SYSTEM SET autovacuum_freeze_max_age = '100000000'; -- 根据实际情况设定较小的值 SELECT pg_reload_conf();
- 进入单用户模式执行
VACUUM FREEZE
-
如果数据库已无法正常启动或持续报错,进入 PostgreSQL 单用户模式执行
VACUUM FREEZE
,以确保数据库能够强制冻结并回收XID
。 -
步骤:
-
停止 PostgreSQL 服务。
-
以单用户模式启动 PostgreSQL:
postgres --single -D /path/to/data_directory database_name
-
在单用户模式下执行 VACUUM FREEZE:
VACUUM FREEZE;
-
退出单用户模式并重启 PostgreSQL 服务。
-
4. 如何预防xid耗尽?
预防 XID
耗尽的关键在于确保 PostgreSQL 能够及时回收和冻结旧事务ID,并在高并发和频繁写入的环境中适当调整配置。
- 启用并合理配置
autovacuum
:autovacuum
是 PostgreSQL 清理和冻结旧事务ID的核心机制,合理配置它可以预防XID
耗尽。- autovacuum_freeze_max_age:建议设置为接近默认值(例如 200,000,000),不要设置得过大,防止过多的旧事务ID累积。如果数据库更新频繁,可以适当调低,以便更早触发冻结。
- autovacuum_naptime:这个参数决定了
autovacuum
周期性运行的间隔时间。可以缩短这个间隔(如 10 秒),让autovacuum
更频繁地检查需要清理的表。 - autovacuum_vacuum_cost_limit 和 autovacuum_vacuum_cost_delay:如果硬件性能支持,可以增加
autovacuum_vacuum_cost_limit
,减小autovacuum_vacuum_cost_delay
,以加快VACUUM
清理速度。
- 监控
XID
使用情况:定期监控数据库中XID
的使用情况,提前发现并避免XID
接近耗尽。 - 避免长时间运行的事务。
- 设置合理的事务管理策略。
- 确保
VACUUM
覆盖所有表,包括TOAST
表。 - 避免禁用
autovacuum
。 - 用户自己调度 freeze:在业务低谷的时间窗口,对年龄较大,数据量较大的表进行
vacuum freeze
。 - 考虑分区:把大表分成小表。
- 对大表设置不同的
vacuum
年龄。
5. 如何避免因为freeze导致IO风暴?
为避免由于 VACUUM FREEZE
操作导致的 I/O 风暴,可以通过自适应调整 autovacuum
配置和动态监控来优化 VACUUM
进程的执行。
-
调整
autovacuum
参数,限制 I/O 负载:PostgreSQL 提供了一些autovacuum
参数,可以通过限制 I/O 负载来降低VACUUM FREEZE
带来的影响:-
autovacuum_vacuum_cost_delay:设置
VACUUM
操作的延迟时间(毫秒)。增加此值可以降低VACUUM
产生的 I/O 负载,避免 I/O 高峰。通常可以设置为 10ms 到 20ms,甚至更高。 -
autovacuum_vacuum_cost_limit:设置
VACUUM
操作的 I/O 开销限制。将此值设高,可以让VACUUM
在低负载时更快地完成;但在高负载时,适当降低此值,以减少 I/O 争用。可以根据硬件性能来逐步调优此参数。
-
-
合理配置
autovacuum_freeze_max_age
:动态调整autovacuum_freeze_max_age
可以有效预防XID
耗尽且不影响性能:- 高负载时:将
autovacuum_freeze_max_age
设高,以避免频繁冻结带来的性能影响。 - 低负载时:将
autovacuum_freeze_max_age
设低,以便提前执行FREEZE
,减少冻结积压的事务。
可以使用
pg_cron
或数据库触发器来动态检测系统负载,并相应地调整autovacuum_freeze_max_age
。 - 高负载时:将
-
监控并优先处理 XID age 高的表:通过定期监控表的
XID age
(例如pg_stat_all_tables
中的age(relfrozenxid)
),优先对XID age
高的表手动执行VACUUM FREEZE
,在空闲时集中处理高风险表,避免系统繁忙时集中触发大量冻结。 -
设置
autovacuum_analyze_scale_factor
和autovacuum_vacuum_scale_factor
:PostgreSQL 中,autovacuum
会在达到一定数据修改比例后自动触发。通过合理设置autovacuum_analyze_scale_factor
和autovacuum_vacuum_scale_factor
,可以调整触发的频率和 I/O 影响:- autovacuum_vacuum_scale_factor:设置为较小值(如 0.05),可以使频繁更新的表更早触发
autovacuum
,减少单次VACUUM
的负担。 - autovacuum_analyze_scale_factor:设置为较低值,可以更频繁触发统计信息更新,优化查询性能,间接减少 I/O 负载。
- autovacuum_vacuum_scale_factor:设置为较小值(如 0.05),可以使频繁更新的表更早触发
-
分布式 VACUUM 调度:避免在同一时间内对所有表同时执行
VACUUM
。可以在低负载期间逐步清理数据库中的表,采用分布式的 VACUUM 调度策略。使用pg_cron
或外部任务调度工具分批次执行VACUUM FREEZE
,保证每次清理的表数量较少、不会对系统产生较大的 I/O 负载。 -
空闲时间强制触发
VACUUM FREEZE
:- 利用数据库空闲时段,手动或自动触发
VACUUM FREEZE
,确保在业务低谷期完成事务冻结,避免高峰期自动触发导致 I/O 风暴。 - 可以使用
pg_cron
定时在夜间或其他空闲时段运行VACUUM FREEZE
,对写入频繁的表尤其有效。
- 利用数据库空闲时段,手动或自动触发
-
考虑数据库分区:
- 对于大表,可以考虑使用表分区,将数据分散到不同的分区表中。这样可以降低每个分区的
XID
age 增速,使得VACUUM
可以更高效地回收并冻结事务。 - 通过分区,可以有效减少每个分区表上的
XID
消耗速度,降低VACUUM
的 I/O 压力。
- 对于大表,可以考虑使用表分区,将数据分散到不同的分区表中。这样可以降低每个分区的
3.3
3: vacuum有加速执行模式,用于在xid不够的紧急情况,该模式和正常vacuum有啥不同?为啥会速度快?
数据库系统中,VACUUM
是一种清理机制,用于回收已删除或过期数据所占用的存储空间,同时维护数据库的统计信息,防止数据膨胀。
当提到 VACUUM
的加速执行模式(Fast Vacuum
或 Aggressive Vacuum
),一般指数据库在特定紧急场景下,通过调整参数或改变策略来加速执行 VACUUM
操作。
1. vacuum有加速执行模式和正常不同?
-
扫描范围:
-
正常模式:通常以全表扫描的方式逐页处理,按需回收页面。
-
加速模式:更积极地处理表的所有页面,尤其是对存在问题的页面优先回收。
-
-
事务与锁:
-
正常模式:可能使用较轻量的共享锁,避免对查询和写入操作造成过多干扰。
-
加速模式:可能会使用更强的锁或频繁抢占资源以优先完成清理任务。
-
-
优先级:
-
正常模式:
VACUUM
被认为是后台维护任务,其优先级较低,不会主动抢占太多资源。 -
加速模式:以更高优先级执行,可能占用更多 I/O 和 CPU 资源。
-
-
处理冻结事务:
-
正常模式:处理冷数据或老事务的冻结时,可能优先级较低。
-
加速模式:在事务 ID(XID)接近耗尽时,会更积极地处理数据以防止数据库停止服务。
-
-
检查点与 WAL:
-
正常模式:可能以增量方式生成较少的 WAL 数据。
-
加速模式:处理速度快,但可能会生成更多的 WAL,导致 I/O 开销增加。
-
2.为什么vacuum加速执行模式速度快?
-
参数调优:
-
数据库可能会动态调整参数(例如提高
vacuum_cost_limit
,降低vacuum_cost_delay
),减少 VACUUM 的休眠时间,提升操作频率。 -
减少或禁用对磁盘 I/O 的速率限制,使得操作更加密集。
-
-
高优先级资源使用:
- 加速模式中,
VACUUM
被赋予更高的优先级,可以更频繁地抢占资源(例如 CPU 和 I/O),导致整体处理速度提升。
- 加速模式中,
-
忽略代价模型:
-
在正常模式下,
VACUUM
会评估清理任务的代价(如 I/O 和内存开销),并进行分阶段操作。 -
在加速模式下,这种代价评估可能被部分忽略,更加激进地执行。
-
-
冻结处理优化:
- 当 XID 接近溢出时,加速模式会集中处理冻结事务,直接跳过部分非关键任务。
3.4
4: 为什么vacuum时会产生大量wal flushing?为什么vacuum自己不刷数据而是要借助wal来?
VACUUM 依赖 WAL flushing 是为了保证数据一致性、支持事务隔离和优化写入操作。虽然这种设计会导致一定的 WAL 开销,但可以通过调参和优化来缓解其影响。
1. vacuum时会产生大量wal flushing?
- 页修改需要记录到 WAL:
- 在 PostgreSQL 等数据库中,
VACUUM
操作会清理数据页(如标记元组为可回收、更新页面的事务状态等)。这些修改都会被记录到 WAL,以便在崩溃后通过 WAL 重放恢复数据一致性。 - 每次对页面的修改(如清理死元组)都需要生成相应的 WAL 记录,这些记录需要写入磁盘,因此会产生大量
WAL flushing
。
- 在 PostgreSQL 等数据库中,
- 页面冻结引发 WAL 写入:
- 当
VACUUM
将页面中的事务 ID(XID)冻结以避免事务溢出时,这种变化也是一种元数据修改,会生成 WAL 日志。
- 当
- 高并发与脏数据:
- 如果
VACUUM
操作需要处理大量脏页(未刷到磁盘的页面),则这些页面的修改需要通过 WAL 来保证一致性,从而产生大量WAL flushing
。
- 如果
- 清理索引:
VACUUM
还会清理表的相关索引。这些索引的变更也需要记录到 WAL 中,进一步增加了WAL flushing
的量。
2. 为什么vacuum自己不刷数据而是要借助wal来?
-
数据一致性保障:
-
数据库的崩溃恢复依赖 WAL 日志。即使
VACUUM
中途失败或系统宕机,通过 WAL 重放可以保证清理操作的中断点恢复和数据一致性。 -
如果
VACUUM
不使用 WAL 而直接修改数据,一旦宕机,可能无法回滚不完整的清理操作,从而导致数据损坏或不一致。
-
-
事务隔离和并发控制:
-
数据库需要支持高并发操作。WAL 日志是对物理页面修改的序列化记录,允许其他事务并发访问,而不会直接影响当前内存中的数据页面。
-
如果
VACUUM
不使用 WAL,而直接修改磁盘,可能破坏其他事务的视图,导致隔离性问题。
-
-
增量写优化:
-
使用 WAL,可以延迟实际数据的磁盘写入(通过后台刷脏页完成),减少频繁的磁盘随机写操作。
-
如果直接刷数据,每次操作都会触发磁盘写入,增加 I/O 开销。
-
-
日志优先设计:
- PostgreSQL 的设计采用 WAL 日志优先的方式,所有对数据的持久化修改都需要先记录 WAL,再更新数据页。这样可以优化崩溃恢复的性能。
3.5
5: vacuum支持PARALLEL选项,其并行的场景是啥?用于解决啥问题?
VACUUM
的 PARALLEL
选项在 PostgreSQL 15 中引入,旨在解决传统 VACUUM
在清理大型表或分区表时效率较低的问题。以下是并行 VACUUM
的主要场景和解决的问题分析:
1. 并行 VACUUM
的适用场景
- 大型表的清理:
- 对包含大量死元组的大型表,传统的单线程
VACUUM
清理速度较慢,尤其在表数据和索引规模较大时。 - 并行
VACUUM
通过多进程分工加快清理速度,减少长时间锁定和资源占用。
- 对包含大量死元组的大型表,传统的单线程
- 多索引表的维护:
- 表上的多个索引在清理过程中需要更新统计信息和回收空间。
- 并行
VACUUM
可以为不同的索引分配独立的工作线程,提高索引清理效率。
- 分区表的处理:
- 分区表由多个子表组成,传统
VACUUM
需要逐个清理子表。 - 并行模式下,多个子表可以同时被清理,缩短分区表整体清理时间。
- 分区表由多个子表组成,传统
- 磁盘 I/O 密集型场景:
- 在高 I/O 带宽的存储系统中,单线程无法充分利用磁盘性能。
- 并行处理可以更好地利用磁盘资源,加快数据读取和清理的效率。
2. 并行 VACUUM
解决的问题
- 性能瓶颈:
- 单线程模式下,清理大型表需要很长时间,会拖慢数据库的整体性能。
- 并行模式显著缩短了清理时间,降低了
VACUUM
操作对系统的影响。
- 索引清理效率:
- 在多索引场景中,单线程依次清理索引效率低下,可能导致长时间锁住资源。
- 并行
VACUUM
可以同时处理多个索引,减少锁竞争。
- 系统延迟问题:
- 长时间运行的
VACUUM
可能会增加事务等待时间,影响正常查询和更新操作。 - 并行模式可以快速释放被占用的元组和索引,降低系统延迟。
- 长时间运行的
- 资源利用率不足:
- 在多核和高 I/O 带宽的硬件环境中,单线程无法充分利用系统资源。
- 并行模式更好地发挥多核性能,优化硬件使用。
3. 注意事项
- 资源消耗:
- 并行
VACUUM
使用多个工作线程,会增加 CPU 和内存的消耗。 - 在资源有限的系统中,过多的并行任务可能影响其他事务。
- 并行
- 参数配置:
- 控制并行度的关键参数包括
vacuum_parallel_workers
(每次VACUUM
最大的并行工作线程数)。 - 并行任务的效率取决于硬件资源和表的数据分布。
- 控制并行度的关键参数包括
- 适用性:
- 并行
VACUUM
并非对所有表都有效,小表和简单结构的表不需要并行清理。
- 并行
3.6
6: 对于更新,索引有hot方式,那为啥还会出现索引膨胀,vacuum为啥会有时候会对表加AccessExclusiveLock锁,会对表执行截断操作,从而影响其他正常SQL的执行,此情况怎么避免?
1. 更新中的索引膨胀原因
- PostgreSQL 的 HOT(Heap-Only Tuple)优化主要用于减少更新操作时对索引的影响,但它并不适用于所有场景。当 HOT 不起作用时,索引膨胀就可能发生。
- 索引膨胀的原因包括:
- 更新导致的索引项复制:如果更新的列是索引列,或者没有可用的页存储新元组(无法进行 HOT),则 PostgreSQL 需要在索引中插入新的条目并标记旧的为无效,但无效条目不会立即删除。
- 索引清理延迟:VACUUM 在清理死元组时,会同步清理索引,但如果频率不足,索引中的死元组会堆积,导致膨胀。
2. VACUUM 的锁类型与截断操作
- VACUUM 通常只获取弱锁(如
AccessShareLock
),不会阻塞正常的查询或更新操作。但在以下情况下,VACUUM 可能会获取AccessExclusiveLock
:- 表截断操作:VACUUM 在清理表末尾未使用的页时,需要获取
AccessExclusiveLock
,以确保没有其他事务访问这些页。 - 防止数据竞争:在极少数情况下,为了保证数据一致性,VACUUM 可能需要升级锁。
- 表截断操作:VACUUM 在清理表末尾未使用的页时,需要获取
- 表截断对系统的影响: 截断会阻止其他事务访问表,尤其在高并发环境下可能导致严重的性能问题。
3. 如何避免索引膨胀和 VACUUM 截断对系统的影响
1. 减少索引膨胀
- 优化更新模式:
- 避免频繁更新索引列。如果索引列需要频繁变更,可以重新设计索引,减少对这些列的依赖。
- 尽量让更新符合 HOT 条件,即更新的列非索引列,且目标页有足够的可用空间。
- 调整填充因子(Fillfactor):
- 为表或索引设置较低的填充因子(如 70%-80%),为每页预留更多空间,增加 HOT 更新的可能性。
- 示例:
ALTER TABLE my_table SET (FILLFACTOR = 80);
- 定期维护索引:
- 使用
REINDEX
重建膨胀的索引。 - 在非高峰期运行
VACUUM
或ANALYZE
,结合autovacuum
参数进行调整。
- 使用
2. 避免 VACUUM 截断的影响
- 调整 AutoVacuum 参数:
- 增加
autovacuum_vacuum_threshold
和autovacuum_vacuum_scale_factor
,减少频繁触发。 - 通过降低
autovacuum_freeze_max_age
提前运行 VACUUM,避免因事务 ID 老化触发长时间的强锁操作。
- 增加
- 避免表截断:
- 使用
autovacuum_truncate = OFF
禁用自动截断。如果需要回收未使用的页,可手动运行VACUUM FULL
或CLUSTER
,在维护窗口中执行。
- 使用
- 分区表优化:
- 将大表分区,减少每个分区的大小。VACUUM 仅需作用于活跃分区,降低整体的锁争用风险。
3. 监控和计划维护
- 监控死元组积累:
- 定期检查
pg_stat_user_tables
中的n_dead_tuples
,判断是否需要手动运行 VACUUM。 - 使用
pg_stat_all_indexes
分析索引膨胀情况。
- 定期检查
- 手动维护策略:
- 在低流量时段运行
VACUUM FULL
或REINDEX
,尽量避免在高峰时段触发自动清理。
- 在低流量时段运行
- 预防性维护:
- 如果预计更新会引发大量死元组,可以提前运行
ANALYZE
以更新表统计信息,优化查询计划并减少死元组堆积。
- 如果预计更新会引发大量死元组,可以提前运行
3.7
7: 数据库维度frozenxid,和表纬度的relfrozenxid何时会被更新,以及会影响哪些操作?频繁更新这些字段会影响vacuum性能不?
1. 何时更新这些字段
relfrozenxid
更新条件:- 当表中的所有事务 ID 都被冻结或确认已经不需要回滚时,
VACUUM
操作会更新该表的relfrozenxid
。 - 触发场景:
- 手动执行
VACUUM
或VACUUM FREEZE
。 - 自动
autovacuum
运行时(尤其是当表接近事务 ID 包裹around时,频率会增加)。
- 手动执行
- 当表中的所有事务 ID 都被冻结或确认已经不需要回滚时,
frozenxid
更新条件:- 当所有表的
relfrozenxid
都比当前的数据库级别frozenxid
更新时,数据库的frozenxid
随之更新。
- 当所有表的
2. 更新这些字段会影响哪些操作?
- 影响
VACUUM
性能:- 如果
relfrozenxid
需要更新,VACUUM
操作需要扫描整个表以冻结事务 ID。这种扫描对于大表来说可能会占用大量的 I/O 和 CPU 资源。 - 如果表中有许多未提交的长事务,冻结操作可能会变得更加耗时。
- 如果
- 影响事务性能:
- 旧事务的
relfrozenxid
不更新可能导致事务 ID wraparound 风险,从而阻塞新的写操作。
- 旧事务的
- 与
autovacuum
的交互:- 当数据库或表的事务 ID 接近
autovacuum_freeze_max_age
(默认 2 亿)时,系统会触发更频繁的autovacuum
,以防止事务 ID wraparound。
- 当数据库或表的事务 ID 接近
3. 频繁更新 frozenxid
和 relfrozenxid
的影响
- 对
VACUUM
的性能影响:- 如果频繁触发更新这些字段,
VACUUM
会花费更多时间在表扫描和事务冻结上,从而增加数据库的 I/O 开销。 - 对于大型表或高事务速率的系统,这可能成为性能瓶颈。
- 如果频繁触发更新这些字段,
- 对系统整体性能的影响:
autovacuum
更频繁地运行,可能导致资源争用,尤其是 I/O 和 CPU 资源。- 长期未维护的表(未执行
VACUUM
或ANALYZE
)可能会延迟frozenxid
的更新,从而触发系统级autovacuum
,影响数据库整体响应速度。
3.8
8: 在pg17引入了一个新的数据结构TidStore,解决了什么问题?有什么效果?
在 PostgreSQL 17 中引入的 TidStore 数据结构,是为了解决 VACUUM
和 INDEX VACUUM
阶段中的性能问题以及优化这些操作的资源利用。它是一种内存优化的数据结构,能够高效地管理大量的元组标识符 (TIDs, Tuple IDs)。下面是具体的解读。
1. TidStore 解决了什么问题?
VACUUM 的性能瓶颈
- 在 PostgreSQL 中,
VACUUM
的主要任务之一是清理已删除但尚未回收的元组(死元组)。 VACUUM
的操作流程:- 扫描表(heap):标记死元组。
- 清理索引(INDEX VACUUM):删除与这些死元组相关的索引项。
- 问题在于:
- 死元组多时,索引清理效率低:
VACUUM
需要将所有死元组的 TIDs 存储在内存或临时磁盘中,然后遍历每个索引去删除对应的索引项。- 如果死元组较多,TIDs 的存储和管理会占用大量内存或需要频繁的磁盘 I/O,导致性能下降。
- 多索引扫描重复开销高:
- 每个索引扫描可能需要重新加载或计算对应的 TIDs,增加了重复开销。
- 死元组多时,索引清理效率低:
内存管理问题
- 在处理大表时,VACUUM 需要为死元组的 TIDs 分配足够的内存。
- 如果内存不足,VACUUM 会退化为使用临时文件,这会显著增加 I/O 开销。
2. 引入 TidStore 的效果
性能提升
- 减少内存占用:
- TidStore 通过紧凑的存储机制降低了管理 TIDs 的内存消耗,使得
VACUUM
更适合处理大表或高并发环境。
- TidStore 通过紧凑的存储机制降低了管理 TIDs 的内存消耗,使得
- 加速索引清理:
- TidStore 支持高效的批量操作,减少了索引清理阶段的时间。
- 减少临时文件 I/O:
- 在内存不足的情况下,TidStore 优化了内存与磁盘之间的切换,减少了传统方法中频繁的磁盘 I/O。
提高可靠性和稳定性
- TidStore 的动态扩展能力确保了在极端情况下(例如死元组特别多)也能稳定运行
VACUUM
,避免因为内存不足而导致性能急剧下降。
扩展性
- TidStore 的设计不仅适用于
VACUUM
,还为未来在其他需要管理大量 TIDs 的场景(如并发查询优化)提供了潜力。
3.9
9: 在pg16引入的vacuum_buffer_usage_limit参数的作用,以及怎么调整 ?
在 PostgreSQL 16 中引入的 vacuum_buffer_usage_limit
参数,用于控制 VACUUM
和 ANALYZE
操作在访问表时可以使用的共享缓冲区(shared buffers)的上限。这一参数的引入旨在优化 VACUUM
的内存管理,特别是防止其对正常工作负载产生过多干扰。
1. vacuum_buffer_usage_limit
的作用
限制 VACUUM 使用的共享缓冲区
VACUUM
操作会读取表的数据块以扫描元组(行)并清理死元组。这些数据块会被加载到共享缓冲区。- 在高负载场景下,如果
VACUUM
不受限制地使用共享缓冲区,可能会驱逐正常查询或事务需要的数据块,导致性能下降。 vacuum_buffer_usage_limit
:- 通过设置一个上限,限制
VACUUM
可以占用的共享缓冲区页数。 - 一旦达到限制,
VACUUM
会将超出部分的脏页写回磁盘,释放缓冲区用于后续操作。
- 通过设置一个上限,限制
减少对正常工作负载的干扰
VACUUM
是一个后台维护任务,但如果其使用共享缓冲区过多,可能影响其他前台查询或事务的性能。- 设置合理的
vacuum_buffer_usage_limit
可以确保VACUUM
的内存占用在可控范围内,降低对用户查询的冲击。
增强对大表的支持
- 在处理大表时,
VACUUM
的内存需求更高。通过限制缓冲区使用,防止因内存不足导致性能不稳定或回退到低效的磁盘操作。
2. 如何调整 vacuum_buffer_usage_limit
默认值
- 默认值为
0
,表示没有限制(VACUUM
可以使用所有可用的共享缓冲区)。
设置参数
-
可以在
postgresql.conf
文件中配置,也可以通过会话级设置动态调整。SET vacuum_buffer_usage_limit = 256MB; -- 会话级设置
或修改
postgresql.conf
文件并重启服务器:vacuum_buffer_usage_limit = '128MB'
推荐调整方法
- 小表: 可以保留默认值(
0
),因为小表对共享缓冲区的占用通常有限。 - 大表:为了避免
VACUUM
操作占用过多内存资源,可根据共享缓冲区大小和系统负载设置一个适中的值。例如:- 如果共享缓冲区总大小为 8GB,可以设置为
256MB
或512MB
。 - 在高并发场景下,设置一个较低值(如
128MB
),以减小对其他事务的影响。
- 如果共享缓冲区总大小为 8GB,可以设置为
- 压力测试: 在调整参数后,对负载敏感的生产环境进行压力测试,确保不会因限制过严导致
VACUUM
性能显著下降。
3.10
10: 在删除一个page中的数据时,在进行vacuum后,其shared_buffer中可能多个page被标记为dirty,其原因是什么?
1. 数据页删除的过程
当数据被删除时,PostgreSQL 并不会立即从磁盘上移除数据,而是将数据标记为“已删除”(dead tuple)。删除操作可能会触发以下几种对页的修改:
a. 修改元组状态
- 被删除的元组会被标记为不可见(例如,设置元组的
LP_DEAD
标志)。 - 这种修改会直接影响包含这些元组的数据页,将其标记为 dirty,以便稍后同步到磁盘。
b. 更新 Free Space Map (FSM)
VACUUM
会更新表的 Free Space Map (FSM),记录每个页中可用的空闲空间。- 如果数据的删除导致某些页有更多的空闲空间,这些更新会标记 FSM 页为 dirty。
c. 更新 Visibility Map (VM)
VACUUM
使用 Visibility Map (VM) 跟踪哪些页中没有死元组,便于加速未来的扫描操作。- 如果某一页被完全清理(没有死元组且冻结了所有事务 ID),
VACUUM
会更新对应的 VM 页,标记其为 dirty。
2. VACUUM 操作导致多个页变脏的原因
a. 直接修改的数据页
VACUUM
会清理死元组并回收空间。- 如果一个数据页包含需要清理的元组,
VACUUM
将修改该页并标记为 dirty。
b. Visibility Map 页
-
对应数据页的
Visibility Map 页
可能会被修改:
- 如果数据页中的所有元组都被冻结或没有死元组,VM 页会更新为“所有元组可见”状态。
- 这会导致 VM 页被标记为 dirty。
c. Free Space Map 页
- 数据页空间发生变化时,Free Space Map 中的对应记录会更新。
- 如果删除操作导致某个数据页的空闲空间达到一定阈值(例如,可以容纳新元组),FSM 页会被修改并标记为 dirty。
d. 索引页
- 如果删除操作涉及索引项,
VACUUM
还需要清理对应的索引页。 - 索引清理可能会导致这些页被标记为 dirty。
3.11
11: autovacuum在运行过程中,其产生的io大小怎么计算,怎么去根据服务器的性能进行动态调整?
1. Autovacuum 产生的 I/O 大小计算
主要 I/O 开销来源
- 表和索引的扫描:
autovacuum
需要扫描表的所有页,以标记死元组和清理空间。- 索引清理也会导致 I/O。
- Visibility Map 和 Free Space Map 更新:
- 清理后更新 Visibility Map 和 Free Space Map,会涉及额外的 I/O。
- 脏页的写回:
- 修改的数据页、VM 页和 FSM 页被标记为 dirty 后需要写回磁盘。
- WAL 日志的写入:
autovacuum
生成的修改操作会记录到 WAL(Write-Ahead Logging),这也是一部分 I/O 开销。
I/O 大小的计算公式
近似公式如下:
Autovacuum I/O = (表大小 + 索引大小) × 扫描比例 + 脏页写回大小 + WAL 日志大小
- 表大小和索引大小:对应表和索引的实际物理存储大小。
- 扫描比例:由
autovacuum_vacuum_cost_limit
和autovacuum_vacuum_cost_delay
控制。 - 脏页写回大小:与清理过程中修改的页数和缓冲区写回策略有关。
- WAL 日志大小:与清理操作(如删除元组、冻结事务等)的记录数量有关。
监控工具
使用 PostgreSQL 提供的统计视图和工具可以直接监控 autovacuum
产生的 I/O:
pg_stat_activity
:查看当前autovacuum
活动。pg_stat_progress_vacuum
:- 提供
autovacuum
的进度信息,包括处理的页数。
- 提供
pg_stat_bgwriter
:- 监控共享缓冲区的写回。
- 操作系统级监控工具:
iotop
或iostat
可以监控数据库实例的 I/O 开销。
2. 根据服务器性能动态调整 Autovacuum 参数
关键参数简介
- I/O 限制相关参数:
autovacuum_vacuum_cost_limit
:- 每个
autovacuum
工作进程的 I/O "成本"上限。 - 默认值是
200
,可以根据磁盘性能调整。
- 每个
autovacuum_vacuum_cost_delay
:- 达到成本上限后的休眠时间(单位:毫秒)。
- 默认值是
20ms
,可以通过增加该值来减少 I/O 压力。
- 运行频率相关参数:
autovacuum_naptime
:autovacuum
检查表的时间间隔(默认 1 分钟)。
autovacuum_vacuum_threshold
和autovacuum_vacuum_scale_factor
:- 控制表何时触发
autovacuum
,与表中死元组数量相关。
- 控制表何时触发
- 并发进程数:
autovacuum_max_workers
:- 控制同时运行的
autovacuum
进程数。
- 控制同时运行的
动态调整策略
- 根据磁盘 I/O 性能
- 高性能存储(如 NVMe SSD):
- 增加
autovacuum_vacuum_cost_limit
,如500-1000
。 - 减少
autovacuum_vacuum_cost_delay
,如5ms
。
- 增加
- 低性能存储(如 HDD):
- 降低
autovacuum_vacuum_cost_limit
,如50-100
。 - 增加
autovacuum_vacuum_cost_delay
,如50-100ms
。
- 降低
- 高性能存储(如 NVMe SSD):
- 根据负载类型
- OLTP 系统(高并发写入):
- 降低
autovacuum_vacuum_cost_limit
,减少对前台负载的干扰。 - 增加
autovacuum_vacuum_cost_delay
。
- 降低
- OLAP 系统(批量查询):
- 提高
autovacuum_vacuum_cost_limit
,加快清理速度。 - 减少
autovacuum_vacuum_cost_delay
。
- 提高
- OLTP 系统(高并发写入):
- 根据表大小和更新频率
- 频繁更新的大表:
- 降低
autovacuum_vacuum_threshold
和autovacuum_vacuum_scale_factor
,触发更频繁的清理。
- 降低
- 更新较少的小表:
- 增加上述参数,减少不必要的清理操作。
- 频繁更新的大表: