由 John Doe 四月 2, 2026
摘要:在本文中,我们将了解 PostgreSQL 存储过程中的异常逻辑对数据库整体性能的影响。
目录
准备测试
我们知道,在 PostgreSQL 的存储过程中使用异常逻辑,需要用到子事务。我们先创建一个简单的函数,该函数可以根据指定的 depth 参数,增加异常逻辑的嵌套深度:
CREATE OR REPLACE FUNCTION generate_subtrans_load(p_id int, depth int)
RETURNS void AS $$
BEGIN
IF depth > 0 THEN
BEGIN
PERFORM generate_subtrans_load(p_id, depth - 1);
EXCEPTION WHEN OTHERS THEN NULL;
END;
ELSE
INSERT INTO penalty_test VALUES (p_id, 'Deep Stack');
END IF;
END;
$$ LANGUAGE plpgsql;
下面我们来通过一个简单的存储过程,循环调用上面带异常逻辑的函数进行测试:
CREATE OR REPLACE PROCEDURE subtrans_test(depth integer)
AS $$
DECLARE
-- Timing Variables
v_start_ts timestamp;
v_time interval;
-- Row Verification
v_rows int;
-- WAL Variables
v_start_lsn pg_lsn;
v_end_lsn pg_lsn;
v_wal numeric;
-- XID Variables
v_start_xid xid8;
v_end_xid xid8;
v_xid bigint;
i int;
v_target_rows int := 5000;
BEGIN
DROP TABLE IF EXISTS penalty_test;
-- Explicitly preserve rows so COMMIT doesn't empty the table
CREATE TEMP TABLE penalty_test (id int, val text) ON COMMIT PRESERVE ROWS;
v_start_xid := pg_current_xact_id();
COMMIT;
v_start_ts := clock_timestamp();
v_start_lsn := pg_current_wal_insert_lsn();
FOR i IN 1..v_target_rows LOOP
PERFORM generate_subtrans_load(i, depth);
END LOOP;
COMMIT;
v_end_lsn := pg_current_wal_insert_lsn();
v_end_xid := pg_current_xact_id();
v_time := clock_timestamp() - v_start_ts;
SELECT count(*) INTO v_rows FROM penalty_test;
v_wal := v_end_lsn - v_start_lsn;
v_xid := (v_end_xid::text::bigint - v_start_xid::text::bigint - 1);
RAISE NOTICE 'Target Rows: %, Depth: %', v_target_rows, depth;
RAISE NOTICE 'Execution Time & Verification: % (Rows: %)', v_time, v_rows;
RAISE NOTICE 'Disk Usage (WAL Written): % bytes', v_wal;
RAISE NOTICE 'Transaction ID Consumption: % XIDs', v_xid;
END $$ LANGUAGE plpgsql;
存储过程异常逻辑的性能测试
我们将测试以下 4 种不同的场景:
- 无异常(深度 0):简单的插入操作,循环插入若干行数据,不涉及异常逻辑。
- 单异常(深度 1):相同的插入循环,但每次插入后都会触发一个异常处理块。
- 48 层异常(深度 48):一次函数调用,任意创建 48 个子事务(低于 64 个的限制),然后完成相同的插入操作。
- 128 层异常(深度 128):一次函数调用,任意创建 128 个子事务(超出 64 个的限制),然后完成相同的插入操作。
指定 depth 参数为 0,在 PostgreSQL 中调用存储过程 subtrans_test,结果如下:
CALL subtrans_test(0);
NOTICE: Target Rows: 5000, Depth: 0
NOTICE: Execution Time & Verification: 00:00:00.02833 (Rows: 5000)
NOTICE: Disk Usage (WAL Written): 40 bytes
NOTICE: Transaction ID Consumption: 1 XIDs
依次指定 depth 参数为 0、1、48、128,在 PostgreSQL 中调用存储过程 subtrans_test。并在 Redrock Postgres 中进行同样的测试,收集结果如下:
| 无异常逻辑(深度 0) | 执行时间 (s) | WAL 日志量 | 事务 ID 数 |
|---|---|---|---|
| PostgreSQL | 0.02833 | 40 bytes | 1 |
| Redrock Postgres | 0.03216 | 3192 bytes | 1 |
| 单异常逻辑(深度 1) | 执行时间 (s) | WAL 日志量 | 事务 ID 数 |
|---|---|---|---|
| PostgreSQL | 0.05122 | 43256 bytes | 5001 |
| Redrock Postgres | 0.04747 | 3192 bytes | 1 |
| 48 层异常(深度 48) | 执行时间 (s) | WAL 日志量 | 事务 ID 数 |
|---|---|---|---|
| PostgreSQL | 1.453 | 2076440 bytes | 240001 |
| Redrock Postgres | 0.807 | 3264 bytes | 1 |
| 128 层异常(深度 128) | 执行时间 (s) | WAL 日志量 | 事务 ID 数 |
|---|---|---|---|
| PostgreSQL | 2.5282 | 5537048 bytes | 640001 |
| Redrock Postgres | 2.1339 | 3320 bytes | 1 |
由测试结果可以看出,PostgreSQL 执行存储过程时,消耗的事务 ID 数 = 循环次数 * 异常逻辑嵌套深度,产生的 WAL 日志量也会相应放大。
结论
通过以上测试可知,在 PostgreSQL 中执行存储过程时,使用越多的异常处理逻辑,会消耗更多的事务 ID,并产生更多 WAL 数据。
而 Redrock Postgres 是基于回滚日志记录位置实现的子事务,这些子事务不过是事务执行过程中的一个临时标记。子事务使用和所属事务块同样的事务 ID,它们并不需要分配独立的事务 ID。同时,这样产生的 WAL 日志量也会保持稳定。