bigywater.tech
PostgreSQL · 4

PostgreSQL HOT 更新与 fillfactor:fillfactor 100、90、70、50 的 HOT 占比是 0%、12.2%、44.7%、100.0%,代价是表变大

“PostgreSQL 更新很频繁的表怎么调 fillfactor、HOT 更新到底是什么条件” 是调优文章里经常一笔带过的地方。这篇在本机自己起一个 PostgreSQL 16.15,关掉 autovacuum,用 pg_stat_user_tables 的 n_tup_hot_upd 看每种设置下 HOT 的占比、表大小和更新耗时。

0% → 100%
fillfactor 从 100 降到 50,全表更新的 HOT 占比从 0.0% 升到 100.0%
85.2 → 28.9 MB
连续更新 5 轮不清理,fillfactor 100 的表涨到 85.2 MB,fillfactor 50 的表一直是 28.9 MB
1 页 → 5 页
同一行更新 1000 次,有老事务时剪枝清不掉旧版本,表从 1 页变成 5 页

HOT 的条件

实测 · 本机执行输出
  update … set v = v + 1(v 没有索引):n_tup_upd = 1000,其中 HOT = 454;索引 h1_k 大小 2285568 字节(更新前 2260992)
  再 update … set k = k + 1000000(k 有索引):累计 n_tup_upd = 2000,其中 HOT = 454;索引 h1_k 大小 2301952 字节

改了有索引的列就不可能是 HOT;没改索引列也要同一页里放得下新版本。

fillfactor 的效果

实测 · 本机执行输出
  fillfactor       初始大小       更新后大小  n_tup_upd    HOT 更新    HOT 占比      更新耗时
         100    14.2 MB     28.4 MB     100000         0      0.0%     0.31s
          90    15.9 MB     29.9 MB     100000     12246     12.2%     0.28s
          70    20.6 MB     31.9 MB     100000     44744     44.7%     0.30s
          50    28.9 MB     28.9 MB     100000    100000    100.0%     0.06s

每页留出空位,新版本放得进同一页,HOT 占比随 fillfactor 降低而升高。

不清理也不长

实测 · 本机执行输出
  fillfactor = 100:初始 + 5 轮之后的表大小(MB):14.2 → 28.4 → 42.6 → 56.8 → 71.0 → 85.2
  fillfactor = 50:初始 + 5 轮之后的表大小(MB):28.9 → 28.9 → 28.9 → 28.9 → 28.9 → 28.9

HOT 更新的旧版本被页内剪枝回收,没有 VACUUM 表也不增长。

长事务

实测 · 本机执行输出
  没有老事务:更新 1000 次之后 1 页(8192 字节)
  有一个没结束的 repeatable read 事务:更新 1000 次之后 5 页(40960 字节)

老事务挡住剪枝,同一行更新 1000 次表从 1 页长到 5 页。

代价

实测 · 本机执行输出
  fillfactor = 100:表 14.2 MB(1819 页),全表扫描 4.5 ms
  fillfactor = 90:表 15.9 MB(2041 页),全表扫描 4.7 ms
  fillfactor = 70:表 20.6 MB(2632 页),全表扫描 4.9 ms
  fillfactor = 50:表 28.9 MB(3704 页),全表扫描 6.8 ms

fillfactor 越小表越大、全表扫描越慢;只对更新频繁且改非索引列的表值得。

分步演示

HOT 更新实测
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / PostgreSQL 16.15(Homebrew);每个实验自己 initdb 一个数据目录,端口 9261 起,只监听 127.0.0.1,不碰本机已有的 5432 上的实例;命令行工具统一用 LC_ALL=C 取英文原文,路径换成 ;计时用 time.perf_counter 或 EXPLAIN ANALYZE 的 Execution Time,数字每次运行会有小幅抖动;实验结束后整个数据目录删除。autovacuum 关掉,只有页内剪枝能回收空间;表 10 万行,主键 + 一个有索引的列 k + 没有索引的列 v + 100 字节文本;DML 放在一次性连接里执行,保证 pg_stat_user_tables 及时;全表扫描耗时是各跑 7 次的最小值,表在内存里
    • 没有验证:真实磁盘上的 IO 耗时(这里的表都在 shared_buffers 和系统缓存里);fillfactor 对索引(B-tree 的 fillfactor)的影响;更新带 TOAST 大字段的行;并发更新下的 HOT 比例;PostgreSQL 16 以外版本(例如后续版本对 HOT 条件的放宽);fillfactor 对 VACUUM 耗时的影响
    京ICP备2026067502号-1京公网安备11010502063487号