← 返回题目列表

PostgreSQL 的 HOT Update 是什么?fillfactor 为什么会影响更新性能?

高频 困难 第 19 / 31 题 更新于 2026/07/29
PostgreSQLHOT UpdatefillfactorMVCC

简化版

HOT Update 是 PostgreSQL 在更新行时,如果索引列没有变化且同一数据页还有空间,就可以只在页内生成新行版本,避免更新索引。fillfactor 会影响页面预留空间,适当降低 fillfactor 能给后续更新留空间,提高 HOT 机会,但会增加表体积。

详细版

PostgreSQL 的 MVCC 更新不是原地覆盖,而是生成新行版本。普通更新如果涉及索引维护,成本会更高。HOT Update 的目标是:当索引列不变,新版本能放在同一页时,不必为新版本更新索引。

触发条件大致包括:

  • 被更新的字段不包含索引列。
  • 同一 heap page 有足够空间放新版本。
  • 页面内能通过 HOT chain 找到最新版本。

fillfactor 表示插入时页面填充比例。比如 80 表示页面保留约 20% 空间给更新。更新频繁的表可以适当降低,但读多写少表不一定需要。

完整版教学

一、PostgreSQL 更新会产生新行版本

PostgreSQL 使用 MVCC。更新一行时,并不是直接覆盖原行,而是写入一个新版本,旧版本留给还在读取旧快照的事务。

旧版本 row(v1) -> update -> 新版本 row(v2)
旧版本等待 vacuum 后回收

这让读写并发更友好,但也带来版本链、表膨胀和索引维护成本。HOT Update 就是为了降低某些更新场景的索引维护成本。

记忆钩子:HOT 的核心不是“更新更热”,而是“Heap Only Tuple”,尽量只动 heap,不动索引。

二、普通更新为什么会牵动索引

索引项通常指向 heap 中的行位置。如果更新产生新行版本,数据库需要确保通过索引能找到可见的新版本。

如果索引列变化,索引当然要更新。即使索引列没变,普通情况下也可能需要新的索引项指向新版本。索引更新会增加写放大,还会造成索引膨胀。

HOT Update 的优化点是:索引仍指向旧版本所在位置,再通过同页 HOT chain 找到新版本,避免新增索引项。

index -> heap tuple v1 -> HOT chain -> heap tuple v2

三、HOT Update 的两个关键条件

HOT 不是所有更新都能触发。最重要条件有两个:索引列不变,新版本能放在同一个 heap page。

如果更新了被索引的 status 字段,索引必须维护;如果没更新索引列,但当前页没有剩余空间,新版本只能放到别的页,HOT 也无法成立。

条件是否利于 HOT
更新非索引列有利
更新索引列不利
同页有空间有利
行变大很多不利

这也是为什么“给所有字段都建索引”会损害更新性能:索引越多,更新越容易触碰索引列或维护成本越高。

四、fillfactor 是给更新留空间

fillfactor 控制插入时页面填充程度。默认接近 100,表示尽量填满页面。对于频繁更新的表,可以设置为 80 或 90,让每个页面留一点空间给后续行版本。

alter table user_profile set (fillfactor = 80);
vacuum full user_profile;

如果页面大小 8KB,fillfactor 80 大致意味着插入时只填到约 6.4KB,留下约 1.6KB 给更新。这样后续更新更容易在同页生成新版本。

代价也明显:表会变大,顺序扫描和缓存效率可能下降。所以它适合更新频繁的表,不是所有表都要调。

五、HOT 和表膨胀仍然需要 Vacuum

HOT 避免了部分索引更新,但旧行版本仍然存在。旧版本何时回收,仍依赖 Vacuum。长事务会阻止回收,让 HOT chain 变长,查询也会受影响。

可以通过统计信息观察 HOT 情况:

select relname, n_tup_upd, n_tup_hot_upd
from pg_stat_user_tables
where relname = 'user_profile';

如果 n_tup_hot_upd / n_tup_upd 比例很低,说明很多更新没有走 HOT,可以检查索引设计、行大小和 fillfactor。

六、索引设计会影响 HOT 机会

每多一个索引,就多一份更新维护成本。尤其是把经常变化的字段建索引,会让对应更新无法 HOT。

例如用户表里 last_login_at 高频更新,如果给它建索引,每次登录都要维护索引,HOT 机会下降。除非业务确实频繁按最近登录排序或筛选,否则这个索引可能得不偿失。

优化时不能只看查询,还要看写入路径。PostgreSQL 的索引不是免费午餐。

七、常见误区与追问

  • 误区:HOT Update 会原地覆盖旧数据。 它仍然生成新行版本,只是尽量避免更新索引。
  • 误区:fillfactor 越低越好。 预留空间会增大表体积,读多写少表可能得不偿失。
  • 误区:只要不改索引列就一定 HOT。 还要同页有空间,新版本放不下也不能 HOT。
  • 追问:怎么查看 HOT 比例?pg_stat_user_tablesn_tup_hot_updn_tup_upd
  • 追问:索引越多为什么影响更新? 更新需要维护索引,且更新索引列会破坏 HOT 条件。
  • 追问:HOT 还需要 vacuum 吗? 需要,旧版本最终仍要由 Vacuum 回收。

八、面试中可以这样落地

如果一张资料表频繁更新非索引字段,可以减少不必要索引,并把 fillfactor 调到 80 或 90,然后观察 HOT 比例和表膨胀。

alter table user_profile set (fillfactor = 85);

select n_tup_upd, n_tup_hot_upd
from pg_stat_user_tables
where relname = 'user_profile';

如果 HOT 比例提升且查询性能没有明显下降,说明调整有效。这个回答体现了观测和验证,而不是盲目调参。

九、加强记忆

HOT Update 记住三个条件:更新不碰索引列,同页有空间,旧版本能通过链找到新版本。fillfactor 是给更新留空间,能提升 HOT 机会,但会让表更大。面试时把 MVCC、新版本、索引维护和 Vacuum 串起来讲,就是完整答案。