PostgreSQL 删除对查询的性能影响

John Doe 五月 19, 2026

摘要:在本文中,我们将了解 PostgreSQL 删除对查询的影响,及其背后的原理。

目录

标记删除元组的事务状态

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

-- 会话一
CREATE TABLE t_large (id integer, data text);
INSERT INTO t_large (id, data)
  SELECT i, repeat('Pg', 64)
    FROM generate_series(1, 1000000) AS s(i);

CREATE INDEX large_idx ON t_large (id);
VACUUM ANALYZE t_large;

我们先创建一个简单的函数,该函数可以根据指定的 off 参数,遍历每个表页面中的一个元组,并计算出访问过的元组中的数据总大小:

CREATE OR REPLACE FUNCTION calc_data_size(off int)
 RETURNS bigint
AS $$
DECLARE
  i int := off;
  siz bigint := 0;
  val text;
BEGIN
  PERFORM pg_current_xact_id();
  WHILE i < 1000000 LOOP
    SELECT data INTO val FROM t_large WHERE id = i;
    siz := siz + char_length(COALESCE(val, ''));
    i := i + 50;
  END LOOP;
  RETURN siz;
END $$ LANGUAGE plpgsql;

注意:在上面的函数体中,我们调用了系统函数pg_current_xact_id,让函数运行在一个事务中,从而可以观察到产生的 WAL 日志量对于整个查询的性能影响。

在准备好基础表和初始数据后,让我们启动一个会话,在该会话中开始一个事务,并保持在空闲状态:

-- 会话二
BEGIN;
SELECT pg_current_xact_id();

让我们删除表中的一部分数据。再触发一次检查点操作,刷写缓冲区中的所有脏页面。

-- 会话一
DELETE FROM t_large WHERE mod(id, 50) = 1;
CHECKPOINT;

下面我们来查询表中已删除的记录,观察查询执行的情况。

-- 会话一
EXPLAIN (analyze, buffers, wal, costs false)
  SELECT calc_data_size(1);
                            QUERY PLAN
-------------------------------------------------------------------
 Result (actual time=323.068..323.069 rows=1.00 loops=1)
   Buffers: shared hit=77306 read=2753 dirtied=20001
   WAL: records=20001 fpi=20001 bytes=162183713 buffers full=17818
 Planning Time: 0.020 ms
 Execution Time: 323.118 ms
(5 rows)

可以看到,在页面不脏的情况下,查询删除的记录都会生成记录页面镜像的 WAL 日志。修改了 20001 个页面,产生了 20001 条 WAL 日志记录,WAL 日志总量为 155 MB。同时,这也给查询带来了一定的性能损耗。

查询过程中的页面清理

让我们结束会话二中的空闲事务。再触发一次检查点操作,刷写缓冲区中的所有脏页面。

-- 会话二
COMMIT;
CHECKPOINT;

下面我们来查询表中的活跃记录,观察查询执行的情况。

-- 会话一
EXPLAIN (analyze, buffers, wal, costs false)
  SELECT calc_data_size(2);
                            QUERY PLAN
-------------------------------------------------------------------
 Result (actual time=332.218..332.219 rows=1.00 loops=1)
   Buffers: shared hit=80000 dirtied=19592
   WAL: records=19592 fpi=19592 bytes=155893544 buffers full=17046
 Planning Time: 0.020 ms
 Execution Time: 332.233 ms
(5 rows)

可以看到,查询访问包含已删除记录的页面,会对访问的页面进行清理,并记录相应的 WAL 日志。修改了 19592 个页面,产生了 19592 条 WAL 日志记录,WAL 日志总量为 149 MB。同时,这也给查询带来了一定的性能损耗。

Redrock Postgres 的表现

下面我们在 Redrock Postgres 的数据库中,执行和上面相同的测试用例。在查询表中已删除的记录时,观察到的查询执行情况如下:

EXPLAIN (analyze, buffers, wal, costs false)
  SELECT calc_data_size(1);
                      QUERY PLAN
-------------------------------------------------------
 Result (actual time=68.575..68.576 rows=1.00 loops=1)
   Buffers: shared hit=1 read=12 dirtied=3
   WAL: records=1 fpi=3 bytes=11376
 Planning Time: 0.023 ms
 Execution Time: 68.630 ms
(5 rows)

输出结果显示,前面的查询操作只修改了回滚段上的 3 个页面,用于记录事务的状态,没有修改任何表数据页面。接下来,在空闲事务结束后,查询表中的活跃记录:

EXPLAIN (analyze, buffers, wal, costs false)
  SELECT calc_data_size(2);
                      QUERY PLAN
-------------------------------------------------------
 Result (actual time=89.976..89.977 rows=1.00 loops=1)
   Buffers: shared hit=1 read=2 dirtied=3
   WAL: records=1 fpi=3 bytes=14496
 Planning Time: 0.023 ms
 Execution Time: 89.993 ms
(5 rows)

输出结果显示,前面的查询操作也只修改了回滚段上的 3 个页面,没有修改任何表数据页面。

测试结论

PostgreSQL 的 DELETE 性能优势是有代价的,后面的元组事务状态标记和页面清理,都需要再次进行页面修改。PostgreSQL 采用标记删除 + 延迟清理,换取 DELETE 速度但需承担后续维护成本;Redrock Postgres 删除时会记录回滚日志和事务状态,牺牲 DELETE 速度以保证查询稳定性。在实际生产环境中:

  • 对于 DELETE 操作频繁但查询压力可控的场景,PostgreSQL 的表现会更好;
  • 对于 DELETE 与查询交替频繁的核心业务场景,如银行交易系统、支付平台,Redrock Postgres 的表现会更加稳定。