PostgreSQL 更新性能对比分析

John Doe 五月 6, 2026

摘要:在本文中,我们将了解 PostgreSQL 更新性能的表现,及其背后的原理。

目录

表数据更新场景: 用例

为了能够更清楚地观察 UPDATE 操作的 WAL 日志开销,我们先将参数full_page_writes设置为 off。

SHOW full_page_writes;
 full_page_writes
------------------
 off
(1 row)

下面我们在 PostgreSQL 的数据库中,创建一个表,插入一些数据,并对表数据进行清理操作,查看更新操作的性能。

\set fillerlen 64
\set fillfactor 100
\set scale 100
\set updatebid 0

DROP TABLE IF EXISTS pgbench_accounts;
CREATE TABLE pgbench_accounts (
  aid      int PRIMARY KEY,
  bid      int,
  filler   char(:fillerlen),
  abalance int
) WITH (fillfactor = :fillfactor);

INSERT INTO pgbench_accounts(aid, bid, filler, abalance)
  SELECT aid, (aid - 1) / 100000 + 1, '', 0
    FROM generate_series(1, :scale * 100000) as aid;

VACUUM ANALYZE pgbench_accounts;
CHECKPOINT;

上面我们进行了检查点操作,刷写了缓冲区中的所有脏页面。这样,就可以通过 EXPLAIN ANALYZE 来查看修改的页面情况:

EXPLAIN (analyze, buffers, wal, costs false)
  UPDATE pgbench_accounts
    SET bid = bid + :updatebid, abalance = abalance + random(-5000, 5000)
    WHERE mod(aid, 1000) = 1;
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Update on pgbench_accounts (actual time=491.377..491.378 rows=0.00 loops=1)
   Buffers: shared hit=213611 dirtied=20142 written=133
   WAL: records=30003 bytes=2720132
   ->  Seq Scan on pgbench_accounts (actual time=0.026..449.655 rows=10000.00 loops=1)
         Filter: (mod(aid, 1000) = 1)
         Rows Removed by Filter: 9990000
         Buffers: shared hit=133334
 Planning:
   Buffers: shared hit=25 dirtied=1
 Planning Time: 0.156 ms
 Execution Time: 491.429 ms
(11 rows)

注意:在上面的 UPDATE 操作中,我们通过 updatebid 参数来控制是否更新 bid 列。当 updatebid 参数为 0 时,bid 列的值保持不变。

上面的 UPDATE 操作更新了 10000 条记录,不涉及修改索引相关的列。总共修改了 20224 个页面,产生了 30003 条 WAL 日志记录,WAL 日志总量为 2.6 MB。

依次指定 fillerlen 参数为 64、128,fillfactor 参数为 100、80,updatebid 参数为 0、1,执行上面的测试用例,收集 IO 相关的数据如下:

filler 列长度 (fillerlen) 填充因子 (fillfactor) 更新 bid 列 (updatebid) 修改页面数 WAL 记录数 WAL 日志量 (bytes)
64 100 0 20142 30003 2720132
64 80 0 10006 10000 719690
64 80 1 10006 10000 1439716
128 100 0 20224 30003 3360132
128 80 0 10009 10000 719710
128 80 1 10009 10000 2079758

PostgreSQL 更新性能数据分析

针对上面的更新性能测试的数据,我们可以看出:

  1. 填充因子 fillfactor 为 100 时,更新需要同时修改堆表页面和索引页面,并在 WAL 日志中记录完整的堆元组和索引记录,更新效率最差。
  2. 填充因子 fillfactor 为 80 时,可以在单个堆表页面内进行 HOT 更新,当更新只修改单个列时,WAL 日志中只需要记录单个列的新值,更新效率最高。
  3. 填充因子 fillfactor 为 80 时,可以在单个堆表页面内进行 HOT 更新,当更新要修改两个列时,WAL 日志中需要记录两个列及两列之间的数据。在上面的测试结果中,当 updatebid 参数为 1 时(更新 bid 列),filler 列长度由 64 调整成 128 后,WAL 日志量出现了明显的变化。

另外,关于索引对 PostgreSQL 更新操作的性能影响,可参见更新 PostgreSQL 表带来的索引写放大。简单来说,PostgreSQL 的更新操作耗时,和表的索引个数成正比关系。而 Redrock Postgres 的更新操作性能能够始终保持稳定。

Redrock Postgres 的表现

Redrock Postgres 引入了撤消日志,修改元组时可实现就地更新,无需添加索引记录。由于无需修改索引,可避免冗余的索引插入操作,从而帮助减少索引膨胀。

下面我们在 Redrock Postgres 的数据库中,执行和上面相同的测试用例,收集 IO 相关的数据如下:

filler 列长度 (fillerlen) 填充因子 (fillfactor) 更新 bid 列 (updatebid) 修改页面数 WAL 记录数 WAL 日志量 (bytes)
64 100 0 10079 10077 1457101
64 80 0 10078 10076 1457677
64 80 1 10087 10085 1558367
128 100 0 10078 10076 1457303
128 80 0 10079 10077 1456955
128 80 1 10088 10086 1558449

从上面的测试数据来看,无论是调整填充因子 fillfactor 的值,还是修改单个列或者两个列,IO 相关的开销没有明显的变化,更新操作的行为都能保持相对稳定。