版本可见性

共 22 题
📑 题目列表 22 题
#
★★★

1. MVCC 与锁的关系,MVCC 减少锁、但仍需锁处理写写冲突?

MVCC 与锁是什么关系?为什么 MVCC 主要用于减少读写之间的阻塞,但写写冲突仍需要锁来处理?

  • MVCC 解决读写阻塞、读不阻塞写的机制
  • 写写冲突仍需加锁的原因
  • 快照读与当前读的加锁差异

MVCC(Multi-Version Concurrency Control,多版本并发控制)通过为每个事务维护多个数据版本,让读操作(快照读)读取历史版本而不需要等待写事务提交,从而实现了"读不阻塞写、写不阻塞读"。但 MVCC 并没有消除写写冲突:两个事务同时修改同一行时,后写的一方必须等待先写的一方提交或回滚,这个等待就是通过加锁(行锁/当前读)实现的。因此 MVCC 与锁是互补关系,MVCC 负责降低读写竞争,锁负责保证写写操作之间的互斥与原子性。

MVCC 的核心价值在于把"读写冲突"从"必须互斥"变为"可并行",而写写冲突本质上无法通过版本化消除,因为一行只能有一个最终生效的新版本,必须用锁串行化。这也是为什么"MVCC 减少锁"而不等于"MVCC 不需要锁"。

#
★★★

2. MVCC 与隔离级别的关系,不同隔离级别对应不同快照策略?

MVCC 与隔离级别是什么关系?为什么不同隔离级别对应不同的快照策略?

  • Read View 的创建时机
  • RC 与 RR 的快照差异
  • 快照策略如何决定隔离级别行为

MVCC 通过 Read View(快照)来决定每个事务能读到哪些版本,而隔离级别决定了快照的创建策略。在 READ COMMITTED 下,每个语句执行时都新建一个 Read View,因此能看到语句开始前已提交的全部数据;在 REPEATABLE READ 下,事务首次读时创建 Read View 并在这整个事务内复用,因此后续所有语句都看到同一份快照,从而保证可重复读。PostgreSQL 的 REPEATABLE READ 与 SERIALIZABLE 在快照层面上类似,但 SERIALIZABLE 额外通过 SSI 检测写偏斜等串行化异常。因此,隔离级别本质上就是不同的快照(版本可见性)策略。

隔离级别是"语义",MVCC 快照是"实现"。理解 RC 每次语句刷新、RR 事务内复用同一个快照,就能解释为什么 RC 有不可重复读而 RR 没有,以及幻读的具体表现差异。

#
★★★

3. MVCC(Multi-Version Concurrency Control)的核心思想,读写不阻塞、写写冲突?

MVCC 的核心思想是什么?它如何实现读写不阻塞,同时保留写写冲突?

  • 多版本存储
  • 读写分离、读不阻塞写
  • 写写冲突的串行化

MVCC 的核心思想是:不把数据只在原地更新为单一版本,而是保留多个版本(旧版本通过 undo log 或堆元组保留),让每个事务通过 Read View 看到自己应见的版本。这样读操作读取的是历史版本,与正在进行的写操作互不干扰,实现了"读不阻塞写、写不阻塞读"。但两个写操作修改同一行时,最终只能保留一个版本,因此写写冲突仍需通过锁或提交时冲突检测来串行化,保证数据一致。

MVCC 的本质是用"空间换并发"——用冗余的历史版本换取更高的读写并发。它把"冲突"从读写之间转移到了写写之间,并尽可能推迟写写冲突的检测时机(如 OCC 在提交时检测)。

#
★★★

4. MySQL InnoDB MVCC 实现,每行存储 DB_TRX_ID、DB_ROLL_PTR 指向 undo log?

MySQL InnoDB 是如何实现 MVCC 的?为什么每行存储 DB_TRX_ID 和 DB_ROLL_PTR,且 DB_ROLL_PTR 指向 undo log?

  • InnoDB 行结构的隐藏列
  • DB_TRX_ID 记录最近修改事务
  • DB_ROLL_PTR 指向 undo log 构建版本链

InnoDB 在每行记录中维护 DB_TRX_ID(最近修改该行的事务 ID)和 DB_ROLL_PTR(回滚指针,指向该行在 undo log 中的旧版本记录)两个隐藏列。DB_TRX_ID 用于判断该版本对某个事务是否可见(与 Read View 的活跃事务列表比较);DB_ROLL_PTR 通过 undo log 把同一条记录的多个历史版本串成版本链。当某事务要读一行时,InnoDB 依据当前 Read View 沿版本链找到第一个可见版本。此外聚簇索引还有隐含的 DB_ROW_ID 和 rollback 段信息。

undo log 在这里扮演"历史版本仓库"的角色,它是逻辑日志,记录行被修改前的旧值。DB_TRX_ID 让数据库能快速判断版本可见性,DB_ROLL_PTR 让数据库能沿链回溯旧版本,二者共同支撑 MVCC 的版本读取。

#
★★★

5. PostgreSQL MVCC 实现,每行存储 xmin(创建事务 ID)与 xmax(删除事务 ID)?

PostgreSQL 是如何实现 MVCC 的?为什么每行存储 xmin(创建事务 ID)与 xmax(删除事务 ID)?

  • PostgreSQL 堆元组的事务 ID 字段
  • xmin 表示创建/插入该元组的事务
  • xmax 表示删除/更新该元组的事务

PostgreSQL 在每个堆元组(heap tuple)头部存储 xmin(创建该元组的事务 ID)和 xmax(删除/更新该元组的事务 ID)。当插入一行时,xmin 记为插入事务 ID;当更新一行时,旧元组的 xmax 记为更新事务 ID,同时插入一个新元组(xmin 为更新事务),旧元组成为死元组。判断可见性时,一个元组对某事务可见当且仅当 xmin 已提交且 xmax 未提交或 xmax 在该事务之后。由于 PostgreSQL 采用"新版本就地插入、旧版本标记"的方式,旧版本滞留需要 VACUUM 清理。

与 InnoDB 把旧版本放在 undo log 不同,PostgreSQL 把新旧版本都放在堆表中,通过 xmin/xmax 标记版本状态。这种实现让 PostgreSQL 读历史版本很直接(无需解析 undo log),但代价是旧版本会占用表空间,需要 VACUUM 回收。

#
★★★

6. 快照(Snapshot)的构造,当前活跃事务 ID 集合?

快照(Snapshot)是如何构造的?为什么它需要记录当前活跃事务 ID 集合?

  • Read View 的组成
  • 活跃事务列表(m_ids)
  • 版本可见性判断规则

快照(在 InnoDB 中称为 Read View,在 PostgreSQL 中称为 Snapshot)记录了创建时刻数据库的可见性状态,核心是当前活跃事务 ID 集合(m_ids),以及最小活跃事务 ID(up_limit_id)和已分配的最大事务 ID/事务快照(low_limit_id)。判断某版本是否可见时,若版本的事务 ID 在活跃集合中,说明该事务尚未提交,该版本不可见;若事务 ID 小于最小活跃事务 ID,说明已提交,可见;若事务 ID 大于等于快照边界,则该版本在快照之后创建,不可见。通过活跃集合就能确定什么是"快照时刻已提交"的数据。

快照的实质是"某一时刻已提交事务的集合边界"。活跃事务列表是快照的核心,因为它决定了哪些事务的修改在快照看来不可见,从而保证读一致性。快照构造的代价与活跃事务数量成正比。

#
★★★

7. MVCC 版本的膨胀问题,长事务导致 undo 堆积?

什么是 MVCC 版本的膨胀问题?为什么长事务会导致 undo 堆积?

  • 版本膨胀的成因
  • 长事务与活跃事务列表
  • undo/死元组无法回收

MVCC 版本膨胀是指由于历史版本无法及时回收,导致存储空间被旧版本大量占用。在 InnoDB 中,undo log 中保存的旧版本只有在没有事务需要它们时才能被 purge 回收;而判断"是否需要"取决于最老的活跃事务(oldest Read View)。如果存在一个长事务,它一直持有快照,数据库就必须保留自该事务开始以来的所有旧版本,导致 undo log 不断增长、空间膨胀。PostgreSQL 中同理,长事务会把 oldest xmin 拖住,使死元组无法被 VACUUM 回收。

版本膨胀的本质是"回收边界被活跃事务拖住"。长事务或未提交事务是版本膨胀最主要的原因,它会阻止旧版本回收,导致表/undo 无限增长,甚至影响性能与回卷安全。治理手段是避免长事务、及时提交,并依靠 VACUUM/purge 线程回收。

#
★★★

8. MySQL InnoDB undo log 的结构与 purge 线程?

MySQL InnoDB 的 undo log 结构是怎样的?purge 线程的作用是什么?

  • undo log 的类型(insert/update undo)
  • 版本链的组织
  • purge 线程清理机制

InnoDB 的 undo log 分为两类:insert undo(用于 INSERT 产生的回滚段,事务提交后可直接清理)和 update undo(用于 UPDATE/DELETE 产生的回滚段,需要保留用于 MVCC 回滚和版本读取)。undo log 按回滚段组织,每个回滚段包含多个 undo 页,undo 页中通过指针把同一行的历史版本串成链。purge 线程(后台线程)负责清理不再被任何活跃事务需要的历史版本:它根据 history list 逐个处理已提交事务的 undo,删除其产生的旧版本,释放空间。purge 线程可以配置多个(innodb_purge_threads)以提升清理速度。

undo log 身兼两职:一是事务回滚的依据,二是 MVCC 版本链的来源。purge 线程是 InnoDB 的"垃圾回收",它必须在没有活跃事务引用旧版本时才能安全清理,其清理进度受最老活跃事务制约。

#
★★★

9. PostgreSQL VACUUM 与 autovacuum 的工作机制,标记死元组、回收空间?

PostgreSQL 的 VACUUM 与 autovacuum 是如何工作的?它如何标记死元组并回收空间?

  • 死元组的判定
  • VACUUM 回收空间与更新可见性映射
  • autovacuum 自动触发机制

PostgreSQL 中,更新/删除产生的旧元组被称为死元组(dead tuple),在没有任何事务需要它们时即可被 VACUUM 回收。VACUUM 扫描表,标出死元组并释放其占用的空间(可复用于新元组),同时更新可见性映射(visibility map)、清理事务状态、冻结过旧的事务 ID。autovacuum 是默认开启的后台进程,当表中死元组数量超过阈值(由 autovacuum_vacuum_threshold 与 autovacuum_vacuum_scale_factor 决定)时自动触发 VACUUM,无需人工干预。普通 VACUUM 不获取排他锁,可与其他操作并发,但空间不一定立即返还操作系统。

VACUUM 是 PostgreSQL 处理 MVCC 版本膨胀的核心手段。autovacuum 让清理自动化,避免频繁手动的运维负担。需要注意的是 VACUUM 回收的是"可复用空间",不一定立刻归还给操作系统,除非用 VACUUM FULL 重写表。

#
★★★

10. Undo Log(PostgreSQL 中 heap tuple + xmax 标记)的实现?

与 InnoDB 的 undo log 相比,PostgreSQL 的"Undo Log"是如何实现的?为什么说 PostgreSQL 用 heap tuple + xmax 标记代替 undo log?

  • PostgreSQL 无独立 undo log
  • 更新就地插入新元组、旧元组标 xmax
  • 回滚与 MVCC 的替代实现

PostgreSQL 没有独立的 undo log 文件,而是采用"就地多版本"策略:更新一行时,直接在表上插入一个新元组,并把旧元组的 xmax 标记为更新事务 ID,旧元组即为死元组。回滚时,只需把新元组标记为死元组、旧元组恢复可见即可(通过事务日志 CL、事务状态判断),无需像 InnoDB 那样回放 undo log 恢复旧值。MVCC 读取历史版本也直接通过元组头部的 xmin/xmax 判断,无需解析 undo log。因此 PostgreSQL 用"堆元组 + xmax 标记"替代了 InnoDB 的 undo log 机制。

两种实现殊途同归:InnoDB 把旧版本放到独立的 undo log,PostgreSQL 把旧版本留在堆表里用 xmax 标记。PostgreSQL 的优点是读历史版本开销低、无需解析 undo;缺点是旧版本占用表空间、需要 VACUUM 清理,且更新开销较大(要写新元组)。

#
★★★

11. VACUUM FULL 与 VACUUM 的差异,原地 vs 重写表?

VACUUM FULL 与普通 VACUUM 有何差异?为什么说 VACUUM 是原地回收、VACUUM FULL 是重写表?

  • 普通 VACUUM 的原地回收
  • VACUUM FULL 重写表、压缩空间
  • 锁与阻塞差异

普通 VACUUM 是"原地回收":它扫描表,把死元组占用的空间标记为可复用,但不会重写表,也不把空间归还给操作系统,只减小表内部可复用的空间。VACUUM FULL 则重写整个表,重新紧凑排列存活元组,把不含数据的空洞整体移除,从而真正把空间归还给文件系统,也更新统计信息。但 VACUUM FULL 需要获取 ACCESS EXCLUSIVE 锁,会阻塞对该表的所有读写,且耗时较长,因此只在空间严重膨胀时才使用。普通 VACUUM 不阻塞并发读写,可频繁执行。

普通 VACUUM 适合日常维护,低开销、可在线;VACUUM FULL 适合一次性压缩空间,但要停读写。两者在"回收程度"和"阻塞窗口"上形成取舍,日常推荐 autovacuum + 普通 VACUUM,出现严重膨胀时才考虑 VACUUM FULL 或 pg_repack。

#
★★★

12. VACUUM 阻塞的常见场景,长事务、临时表?

VACUUM 被阻塞的常见场景有哪些?长事务和临时表是如何影响 VACUUM 的?

  • 长事务拖住 oldest xmin
  • VACUUM 因快照分叉无法清理
  • 临时表与 VACUUM 的锁定

VACUUM 被阻塞的常见场景是存在长事务或未提交事务:它们会把 oldest xmin(最老活跃事务)拖住,使得 VACUUM 无法清理任何比该事务更晚产生的死元组,导致清理不彻底、表持续膨胀。此外,如果存在运行极长的查询(持有一个非常老的快照),VACUUM 同样无法推进其清理边界。临时表方面,VACUUM 在处理临时表时通常不阻塞,但 autovacuum 默认不处理临时表,需手动 VACUUM。高并发下 VACUUM 也可能因 I/O 竞争或锁等待而变慢。

VACUUM 的清理边界被"最老活跃事务"锁死,这是版本膨胀的根源。排查 VACUUM 阻塞时,应优先定位并结束长事务/长查询,这是解除阻塞的根本。

#
★★

13. InnoDB Read View 的创建时机在 RC(每条语句创建新 Read View)与 RR(事务首次读创建并复用)上有何差异?这如何决定不可重复读与幻读的可见性行为?

InnoDB 的 Read View 创建时机在 RC 与 RR 下有何差异?这如何决定不可重复读与幻读的可见性行为?

  • RC 每条语句新建 Read View
  • RR 事务首次读创建并复用
  • 对不可重复读与幻读的影响

在 READ COMMITTED 下,InnoDB 每条 SELECT 语句都新建一个 Read View,因此同一事务内两次 SELECT 可能看到不同数据,导致不可重复读;同时由于每次语句都刷新快照,幻读在 RC 下是可能发生的(对快照读而言)。在 REPEATABLE READ 下,InnoDB 在事务第一次 SELECT 时创建 Read View,并在整个事务内复用,后续所有快照读都基于同一快照,因此不可重复读和幻读(对快照读而言)都不会发生。注意:RR 下普通 SELECT 是快照读不会幻读,但 UPDATE/DELETE 这类当前读会加临键锁,从加锁读角度仍能防护幻读。

Read View 的创建时机直接决定了快照读的一致性边界。RC 用"每次刷新"换取更快看到最新数据,RR 用"事务内复用"换取一致性。理解这一差异就能解释两种隔离级别下不可重复读与幻读现象的有无。

#
★★

14. MVCC 与 GC,PostgreSQL VACUUM、MySQL purge 线程?

MVCC 与垃圾回收(GC)是什么关系?PostgreSQL 的 VACUUM 与 MySQL 的 purge 线程分别扮演什么角色?

  • 历史版本回收的必要性
  • PostgreSQL VACUUM 回收死元组
  • MySQL purge 线程清理 undo 旧版本

MVCC 产生大量历史版本,这些版本在不再被任何事务引用时需要被回收,这一过程类似于垃圾回收(GC)。PostgreSQL 通过 VACUUM/autovacuum 回收死元组并复用空间;MySQL InnoDB 通过 purge 线程扫描并清理不再被引用的 undo log 历史版本。两者都受"最老活跃事务"约束:只要有长事务存在,回收就会被推迟。合理配置 autovacuum 参数和 purge 线程数量,能有效控制版本膨胀。

MVCC 的 GC 设计是"延迟回收":只有当旧版本确定不再可见时才删除。GC 的及时性直接决定存储膨胀程度与性能,这也是运维中监控 autovacuum/purge 进度的重要原因。

#
★★

15. VACUUM 的调优参数,autovacuum_vacuum_threshold、autovacuum_vacuum_scale_factor?

VACUUM 的主要调优参数有哪些?autovacuum_vacuum_threshold 与 autovacuum_vacuum_scale_factor 如何决定触发时机?

  • autovacuum 触发阈值
  • threshold 与 scale_factor 的公式
  • 调优考量

autovacuum 触发 VACUUM 的判断公式是:当死元组数量 > autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × 当前元组数 时触发。其中 threshold 是固定的最小触发量,scale_factor 是比例系数(默认 0.2,即表有 20% 的死元组才触发)。对于小表,threshold 保证不会频繁触发;对于大表,scale_factor 避免频繁全表扫描。对大表或写入频繁的表,常需调小 scale_factor 或调大 threshold 来平衡清理频率与开销。相关参数还有 autovacuum_vacuum_cost_limit 等用于控制清理的 I/O 代价。

触发公式本质是"死元组到达一定比例才清理",兼顾小表与频繁写入场景。调优核心是控制清理频率与 I/O 开销之间的平衡,避免表膨胀失控或清理过度消耗资源。

#
★★

16. PostgreSQL 的 oldest xmin(最老活跃事务下界)如何同时决定版本可见性边界与 VACUUM 冻结/清理时机?长事务为何会拖住 xmin 导致膨胀与回卷风险?

PostgreSQL 的 oldest xmin 如何同时决定版本可见性边界与 VACUUM 冻结/清理时机?为什么长事务会拖住 xmin 导致膨胀与回卷风险?

  • oldest xmin 的定义
  • 可见性边界与清理边界
  • 事务 ID 回卷风险

oldest xmin 是所有活跃事务中最小的 xmin,它同时是两条边界:一是版本可见性边界,任何事务的快照都只能看到 xmin 之前已提交的数据,因此 xmin 之后产生的旧版本对当前事务仍可能可见;二是 VACUUM 清理边界,VACUUM 只能清理 xmin 之前已提交且不再需要的死元组,不能清理 xmin 之后的新版本。长事务会一直持有 xmin,阻止 VACUUM 推进,导致死元组堆积、表膨胀。更严重的是,事务 ID 是 32 位循环复用的,若 VACUUM 因 xmin 被拖住而无法"冻结"过旧事务 ID,旧事务 ID 可能被复用,导致损坏或回卷(wraparound)风险,数据库会强制冻结抢占 I/O 资源。

oldest xmin 是把"可见性"与"清理"绑在一起的枢纽:它既决定哪些版本应被看到,也决定哪些版本能否被回收。长事务让 xmin 不前移,既造成膨胀又带来回卷风险,因此 PostgreSQL 运维必须监控长事务与 age 值。

#
★★

17. RR 快照读下 UPDATE 发现目标行已被其他事务修改提交时,PostgreSQL 的 EvalPlanQual 重检查与 InnoDB 的当前读分别如何处理?为何能防止丢失更新?

RR 快照读下 UPDATE 发现目标行已被其他事务修改提交时,PostgreSQL 的 EvalPlanQual 与 InnoDB 的当前读分别如何处理?为什么能防止丢失更新?

  • EvalPlanQual 机制
  • InnoDB 当前读加锁
  • 防止丢失更新的原理

在 PostgreSQL REPEATABLE READ 下,若 UPDATE 依据的 WHERE 条件基于快照读发现的旧行,而该行已被其他事务修改并提交,重新执行扫描时 PostgreSQL 通过 EvalPlanQual(EPQ)机制重新求值:它获取该行的最新版本,重新对该行执行 UPDATE 的 WHERE 条件判断,若满足则基于最新版本更新,否则不更新。在 MySQL InnoDB RR 下,UPDATE 是当前读,会加临键锁并读取最新已提交版本,若行已被改,则基于最新版本继续更新。两种机制都保证基于"最新可见数据"更新,而不是覆盖他人已提交的修改,从而防止丢失更新。

丢失更新指两个事务都读旧值、后写覆盖先写。EPQ 与当前读都让 UPDATE 基于最新版本执行,保证更新不会覆盖他人已提交的修改,这是 MVCC 下防止丢失更新的关键设计。

#
★★

18. MySQL DB_TRX_ID 与 DB_ROLL_PTR?

MySQL 的 DB_TRX_ID 与 DB_ROLL_PTR 分别是什么?它们如何协作实现 MVCC?

  • DB_TRX_ID 记录修改事务 ID
  • DB_ROLL_PTR 指向 undo log 旧版本
  • 版本链构建

DB_TRX_ID 是 InnoDB 每行隐藏的事务 ID 列,记录最近一次修改该行的事务 ID,用于判断版本可见性(与 Read View 活跃列表比较)。DB_ROLL_PTR 是回滚指针,指向该行在 undo log 中的旧版本;通过 DB_ROLL_PTR 可以把同一行的多个版本通过 undo log 串成一条版本链。读取时,InnoDB 先看 DB_TRX_ID 判断当前版本是否可见,若不可见则沿 DB_ROLL_PTR 找到更早版本继续判断,直到找到可见版本。

DB_TRX_ID 负责"可见性判断",DB_ROLL_PTR 负责"版本回溯",两者配合构成 InnoDB MVCC 的读取路径。这也是理解 InnoDB 版本链与 undo 结构的基础。

#
★★

19. PostgreSQL VACUUM 的作用?

PostgreSQL VACUUM 的作用是什么?它主要完成哪些工作?

  • 回收死元组空间
  • 更新可见性映射与统计信息
  • 冻结事务 ID 防止回卷

PostgreSQL VACUUM 的作用主要包括:回收更新/删除产生的死元组占用的空间使其可复用;更新可见性映射(visibility map),从而让索引-only 扫描更高效;更新统计信息(配合 ANALYZE);清理事务状态表;以及"冻结"过旧的事务 ID(把事务 ID 标记为 frozen),防止事务 ID 回卷(wraparound)风险。autovacuum 默认自动执行 VACUUM。普通 VACUUM 不获取排他锁,可与并发操作并行。

VACUUM 是 PostgreSQL 维护 MVCC 版本存储与事务 ID 安全的核心命令,兼具"空间回收"与"安全防护"双重职责。理解它的作用有助于进行 PostgreSQL 的日常运维与性能调优。

#
★★

20. PostgreSQL xmin 与 xmax?

PostgreSQL 的 xmin 与 xmax 分别是什么?它们如何表示元组的创建与删除?

  • xmin 表示创建元组的事务 ID
  • xmax 表示删除/更新元组的事务 ID
  • 版本可见性判断

xmin 是创建该元组(插入该行)的事务 ID,xmax 是删除/更新该元组的事务 ID。插入时 xmin 记为插入事务 ID、xmax 为空;更新时旧元组 xmax 记为更新事务 ID,新元组 xmin 记为更新事务 ID;删除时删除事务 ID 写入元组 xmax。判断可见性时,若 xmin 已提交且 xmax 为未提交或为未来事务,则元组可见;若 xmax 是已提交事务则元组不可见。通过 xmin/xmax 即可判断一个元组在某事务视图下是否可见。

xmin/xmax 是 PostgreSQL 实现 MVCC 的最小信息单元,配合 CL 事务状态日志可实现版本可见性判断。理解它们就能理解 PostgreSQL 就地多版本与死元组的产生机制。

#
★★

21. MVCC 版本膨胀的成因与治理,长事务与未提交事务如何阻止旧版本回收,膨胀率如何量化、VACUUM 与在线整理如何选?

MVCC 版本膨胀的成因与治理是什么?长事务与未提交事务如何阻止旧版本回收?膨胀率如何量化?VACUUM 与在线整理如何选?

  • 版本膨胀的成因
  • 膨胀率的量化(膨胀关系)
  • VACUUM 与在线整理(pg_repack)的选择

MVCC 版本膨胀的根源是长事务或未提交事务把最老活跃事务(oldest xmin)拖住,使旧版本无法被回收。膨胀率可通过膨胀关系(bloat ratio)量化,即"实际占用空间与有效数据空间之比",常用 pgstattuple、pg_bloat_check 等工具或基于统计信息估算。治理手段分两步:一是从源头消除长事务,及时提交短事务;二是依靠自动清理(autovacuum/普通 VACUUM)回收可复用空间。当普通 VACUUM 无法回收空间(如存在持续长事务)或需要把空间真正归还给操作系统时,才考虑在线整理工具(如 pg_repack)或 VACUUM FULL,前者在线、后者需排他锁。

膨胀治理的核心是"控制回收边界"与"选择回收手段"。日常以 autovacuum 为主,配合监控膨胀率;当膨胀严重影响空间或性能时,在线整理(pg_repack)是比 VACUUM FULL 更友好的选择,因为它不阻塞写入。

#

22. VACUUM FULL 的代价,获取 ACCESS EXCLUSIVE 锁并重写表带来的阻塞窗口,与普通 VACUUM 的回收程度差异,何时值得用 pg_repack 替代?

VACUUM FULL 的代价是什么?它与普通 VACUUM 在回收程度上有何差异?何时值得用 pg_repack 替代?

  • VACUUM FULL 的排他锁与阻塞
  • 与普通 VACUUM 的回收程度差异
  • pg_repack 的在线优势

VACUUM FULL 需要获取 ACCESS EXCLUSIVE 锁并重写整张表,期间所有对该表的读写都会被阻塞,且重写耗时与表大小成正比,这是它最大的代价。它比普通 VACUUM 回收更彻底:普通 VACUUM 只标记空间可复用,VACUUM FULL 重写表后把空间真正归还给操作系统并压缩表。普通 VACUUM 不阻塞并发、可频繁执行,但回收不彻底;VACUUM FULL 回收彻底但有长阻塞窗口。当表膨胀严重、需要真正压缩空间,但又不能接受长时间阻塞业务时,用 pg_repack 替代:它通过临时表在线重建表,不获取排他锁(仅短暂持锁),能在不阻塞读写的情况下回收空间。

选择取决于"回收彻底性"与"阻塞窗口"的权衡。大表膨胀且要求在线时,pg_repack 优于 VACUUM FULL;小表或可接受停机时,VACUUM FULL 更简单。