“PostgreSQL 更新很频繁的表怎么调 fillfactor、HOT 更新到底是什么条件” 是调优文章里经常一笔带过的地方。这篇在本机自己起一个 PostgreSQL 16.15,关掉 autovacuum,用 pg_stat_user_tables 的 n_tup_hot_upd 看每种设置下 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 初始大小 更新后大小 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 越小表越大、全表扫描越慢;只对更新频繁且改非索引列的表值得。