【GaussDB】 存储过程中使用自治事务记录日志的性能优化方案测试及评估
背景
在ORACLE数据库里进行业务逻辑开发时,使用自治事务来记录日志是一种非常常见的用法,但这种用法原样迁移到GaussDB时,性能会下降得非常严重,尤其是在循环次数非常多的情况下使用,这个时间差会变得非常大,需要考虑有没有什么办法在不减少日志记录的情况下,提升存储过程的性能。
概述
本文记录了在 GaussDB 中多种记录日志的方式,包括写入文件/批量插表等多种方案,并对各方案的性能表现进行了对比测试。
测试环境:GaussDB 506.0.0.SPC0100(build e324981f),每方案 3 轮取平均值,梯度 10 / 100 / 1000 条。
结论速览(1000 条数据):
| 方案 | 平均耗时 | 吞吐量 | 相对基线 |
|---|---|---|---|
| C fopen/fprintf(非 fenced) | 7.32 ms | 136,618 rows/s | 4,344x |
| C fopen/fprintf(fenced) | 38.21 ms | 26,169 rows/s | 832x |
| INSERT TABLE(unnest 批量) | 141.50 ms | 7,067 rows/s | 225x |
| DBE_FILE 内置包 | 136.03 ms | 7,351 rows/s | 234x |
| INSERT TABLE(逐条自治事务) | 31,801 ms(基线) | 31 rows/s | 1.0x |
C 标准文件 I/O 非 fenced 模式性能最优,比逐条 INSERT TABLE 快 4,000 倍以上。
一、方案对比总览
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| A. 原版INSERT TABLE 逐条 | 逐条调用自治事务存储过程写表 | 数据落库可查可审计 | 极慢,自治事务开销大;频繁commit也慢 |
| B. INSERT TABLE unnest | 嵌套表变量 + unnest_table() 批量 INSERT | 数据落库,比逐条快 225x | 业务代码框架需要改造 |
| C. C fopen/fprintf | 自定义 C 函数直接调用 C 标准库写文件 | 性能最高,4,344x | 需编译部署,非 fenced 有安全风险,查询日志需使用外部表,并发时单次写入超过4K有断页风险 |
| D. DBE_FILE 内置包 | GaussDB 内置 dbe_file 包,PL/SQL 封装 | 零部署,234x | 性能不如 C 函数,查询日志需使用外部表,并发时单次写入超过4K有断页风险 |
二、方案一:INSERT TABLE 逐条(基线)
存储过程
CREATE TABLE perf_log (
seq_id INT,
user_id TEXT,
log_content TEXT,
log_time DATE
);
CREATE PROCEDURE sp_insert_perf_log(
p_seq_id INT,
p_user_id TEXT,
p_log_content TEXT,
p_log_time DATE
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO perf_log VALUES (p_seq_id, USER, 'aaaaaaaaaa', SYSDATE);
COMMIT;
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
三、方案二:INSERT TABLE unnest 批量
GaussDB 支持 unnest_table() 函数展开嵌套表变量,类似 Oracle 的 TABLE() 函数。将数据先构造到嵌套表变量中,再通过 INSERT INTO ... SELECT * FROM unnest_table(变量) 一次性批量写入。
DECLARE
TYPE t_log_rec IS RECORD (
seq_id INT,
log_content VARCHAR2(200)
);
TYPE t_log_tab IS TABLE OF t_log_rec;
v_tab t_log_tab;
BEGIN
-- 构造嵌套表
v_tab := t_log_tab();
FOR i IN 1..1000 LOOP
v_tab.EXTEND;
v_tab(i).seq_id := i;
v_tab(i).log_content := i || ',' || USER || ',aaaaaaaaaa,' ||
TO_CHAR(SYSDATE, 'yyyymmddhh24miss');
END LOOP;
-- 一次性批量插入
INSERT INTO perf_log(seq_id, log_content)
SELECT t.seq_id, t.log_content FROM unnest_table(v_tab) t;
COMMIT;
END;
/
1000 条数据:逐条 31.8 秒 vs unnest 批量 141ms,提升 225 倍。
四、方案三:C fopen/fprintf
4.1 源码
文件:udf_write_file.cpp
GaussDB 与 openGauss 差异对照:
项 openGauss GaussDB 头文件 postgres.hgaussdb.h错误报告 ereport(...)ERR_PROC(错误编号, 级别, (...))错误码 errcode(...)err_ecode(...)错误消息 errmsg(...)err_msg(...)
#include "gaussdb.h"
#include "fmgr.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include <cstdio>
#include <cstring>
#include <cerrno>
PG_MODULE_MAGIC;
extern "C" Datum udf_write_file(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(udf_write_file);
Datum
udf_write_file(PG_FUNCTION_ARGS)
{
char *filename = text_to_cstring(PG_GETARG_TEXT_P(0));
char *content = text_to_cstring(PG_GETARG_TEXT_P(1));
FILE *fp = fopen(filename, "a");
if (fp == NULL) {
ERR_PROC(GAUSS_44273, ERR_LEVEL_ERROR,
(err_ecode(ERRCODE_IO_ERROR),
err_msg("could not open file \"%s\": %m", filename)));
PG_RETURN_INT32(-1);
}
int bytes_written = fprintf(fp, "%s\n", content);
if (bytes_written < 0) {
fclose(fp);
ERR_PROC(GAUSS_44186, ERR_LEVEL_ERROR,
(err_ecode(ERRCODE_IO_ERROR),
err_msg("failed to write to file \"%s\": %m", filename)));
PG_RETURN_INT32(-1);
}
fclose(fp);
PG_RETURN_INT32(bytes_written);
}
4.2 编译
g++ -std=c++0x -Wall -fPIC \
-I${GAUSSHOME}/include/gaussdb/server \
-D_GNU_SOURCE \
-c -o udf_write_file.o udf_write_file.cpp
g++ -std=c++0x -Wall -fPIC -shared \
-L${GAUSSHOME}/lib -Wl,-rpath,${GAUSSHOME}/lib \
udf_write_file.o -o udf_write_file.so
4.3 部署
cp udf_write_file.so ${GAUSSHOME}/lib/gaussdb/proc_srclib/
# 拷贝到其他节点
pscp -H 192.168.*.* udf_write_file.so $GAUSSHOME/lib/gaussdb/proc_srclib/
4.4 创建函数
-- 非 fenced(性能最优,但有安全风险)
CREATE FUNCTION udf_write_file(filename text, content text)
RETURNS integer
AS 'udf_write_file.so', 'udf_write_file'
LANGUAGE C STRICT;
-- fenced(安全隔离)
-- 需先开启: gs_guc reload -I all -N all -c "enable_cfunction=on"
CREATE FUNCTION udf_write_file_fenced(filename text, content text)
RETURNS integer
AS 'udf_write_file.so', 'udf_write_file'
LANGUAGE C STRICT fenced;
五、方案四:DBE_FILE 内置包(零部署)
CREATE DIRECTORY LOG_DIR AS '/tmp';
CREATE OR REPLACE PROCEDURE utl_write_log(
p_filename IN VARCHAR2,
p_content IN VARCHAR2
) AS
v_file_handle dbe_file.FILE_TYPE;
BEGIN
v_file_handle := dbe_file.FOPEN('LOG_DIR', p_filename, 'A', 32767);
dbe_file.WRITE_LINE(v_file_handle, p_content);
dbe_file.CLOSE(v_file_handle);
EXCEPTION
WHEN OTHERS THEN
IF dbe_file.IS_OPEN(v_file_handle) THEN
dbe_file.CLOSE(v_file_handle);
END IF;
RAISE;
END utl_write_log;
/
六、性能测试
6.1 测试方法
使用预编译存储过程 sp_perf_bench(循环次数, 轮数) 一键执行全部方案,SYSTIMESTAMP 精确计时,结果持久化到 perf_result 表。
gsql -r -d postgres -p 8000 -f perf_test.sql
6.2 测试结果汇总
10 条/轮
| 方案 | 平均耗时(ms) | 最小(ms) | 最大(ms) | 吞吐量(rows/s) | 相对基线 |
|---|---|---|---|---|---|
| C fopen/fprintf(非fenced) | 0.257 | 0.128 | 0.507 | 38,860 | 958x |
| DBE_FILE 内置包 | 1.937 | 1.600 | 2.462 | 5,163 | 127x |
| C fopen/fprintf(fenced) | 3.729 | 3.287 | 4.001 | 2,682 | 66x |
| INSERT TABLE(unnest批量) | 19.123 | 15.871 | 24.428 | 523 | 13x |
| INSERT TABLE(逐条自治事务) | 246.161 | 161.149 | 401.142 | 41 | 1.0x |
100 条/轮
| 方案 | 平均耗时(ms) | 最小(ms) | 最大(ms) | 吞吐量(rows/s) | 相对基线 |
|---|---|---|---|---|---|
| C fopen/fprintf(非fenced) | 0.824 | 0.813 | 0.831 | 121,310 | 2,879x |
| C fopen/fprintf(fenced) | 6.875 | 6.534 | 7.137 | 14,545 | 345x |
| DBE_FILE 内置包 | 12.614 | 11.745 | 13.760 | 7,928 | 188x |
| INSERT TABLE(unnest批量) | 53.926 | 33.687 | 76.534 | 1,854 | 44x |
| INSERT TABLE(逐条自治事务) | 2,372.331 | 2,072.346 | 2,539.938 | 42 | 1.0x |
1000 条/轮
| 方案 | 平均耗时(ms) | 最小(ms) | 最大(ms) | 吞吐量(rows/s) | 相对基线 |
|---|---|---|---|---|---|
| C fopen/fprintf(非fenced) | 7.320 | 6.923 | 7.579 | 136,618 | 4,344x |
| C fopen/fprintf(fenced) | 38.214 | 36.259 | 40.278 | 26,169 | 832x |
| DBE_FILE 内置包 | 136.029 | 125.180 | 152.269 | 7,351 | 234x |
| INSERT TABLE(unnest批量) | 141.496 | 43.443 | 257.844 | 7,067 | 225x |
| INSERT TABLE(逐条自治事务) | 31,801.031 | 29,000.139 | 34,637.350 | 31 | 1.0x |
6.3 趋势分析
随数据量增长的耗时趋势(平均耗时 ms):
| 方案 | 10 条 | 100 条 | 1000 条 | 1000/10 倍率 |
|---|---|---|---|---|
| C fopen/fprintf(非fenced) | 0.257 | 0.824 | 7.320 | 28.5x(近线性) |
| C fopen/fprintf(fenced) | 3.729 | 6.875 | 38.214 | 10.2x |
| DBE_FILE 内置包 | 1.937 | 12.614 | 136.029 | 70.2x |
| INSERT TABLE(unnest批量) | 19.123 | 53.926 | 141.496 | 7.4x |
| INSERT TABLE(逐条自治事务) | 246.161 | 2,372.331 | 31,801.031 | 129.2x |
关键发现:
-
C 非 fenced 几乎线性扩展:10 条到 1000 条耗时增长约 28.5 倍(接近理想线性 100 倍),说明单次调用的固定开销极小。1000 条仅 7.3ms,吞吐量达 13.7 万 rows/s。
-
逐条自治事务扩展性最差:耗时增长 129 倍(超过线性),说明自治事务的上下文切换开销随并发量累积放大。
-
unnest 批量 vs DBE_FILE:在 1000 条时两者性能接近(141ms vs 136ms),但 unnest 方案波动大(最小 43ms 最大 258ms),DBE_FILE 更稳定。
-
C fenced 的固定开销:即使只写 10 条也需要 3.7ms(vs 非 fenced 的 0.26ms),说明 fenced 模式每次调用有约 3ms 的 IPC 通信固定开销。
七、注意事项
自定义C函数
| 项目 | 说明 |
|---|---|
| 非 fenced 模式 | C 函数直接在 GaussDB 主进程中运行,代码缺陷可导致数据库崩溃,仅建议测试环境使用 |
| fenced 模式 | C 函数在独立的 fenced 子进程中运行,崩溃不影响主进程,但由于需要依赖GaussDB头文件编译,因此在GaussDB打内核补丁或者升级时需要重新编译,增加了维护量 |
| 文件路径 | 确保数据库操作系统用户对目标目录有写权限 |
enable_cfunction | fenced 模式需开启:gs_guc reload -I all -N all -c "enable_cfunction=on" ,不开会报错 Unsupported feature |
| 多节点 | so文件需要复制到每个节点上,且日志文件只会记录在主节点,切换后无法通过外部表查询,如果设计共享存储或者同步机制,会增加架构复杂度 |
| 日志归档 | 需要设计日志轮转及归档机制,比如单个日志文件持续增大 |
| 操作系统注入风险 | 本文用例未作安全防护,实际使用需做路径保护以及权限控制 |
unnest嵌套表批量插入
核心改造思路:
执行业务逻辑前,需要先初始化嵌套表全局变量,然后把原本调用的写自治事务的存储过程,改成往全局变量里填充数据,并且设计逻辑当全局变量填满时,调用一次自治事务进行插表,重新初始化全局变量,最后在业务逻辑执行结束时,把没填满的全局变量trim掉空的记录再插表。
此方案虽然不如自定义C函数直接写文件快,但是相较于原本的每行日志就调用一个自治事务进行插入的性能也是提升巨大,已经接近使用内置的dbe_file写文件的速度了。只是需要进行的业务代码框架上的改造,开发上稍微麻烦点。另外如果有kill session,那么就存在丢失日志记录的可能性。
其他
| 问题 | 原因 | 解决方案 |
|---|---|---|
匿名块 CALL proc() 报 query has no destination for result data | GaussDB DO $$ 匿名块中不支持直接 CALL | 改为 PERFORM proc() 或去掉CALL |
DBMS_OUTPUT.PUT_LINE 不存在 | GaussDB 对应 DBE_OUTPUT.PUT_LINE | 使用 RAISE INFO/NOTICE 或 DBE_OUTPUT |
编译时 gaussdb.h 找不到 | su 切用户后环境变量未加载 | 先 source gauss_env_file |
| openGauss/postgresql能编译的C函数在GaussDB上编译会报错 | GaussDB修改了源码文件名,且修改了函数名 | 以openGauss作为参考,在GaussDB对应的头文件相同行数附近找可能的函数名 |
| 使用长匿名块测试性能不佳 | 匿名块没有预编译,需要在执行时再去编译,有时间消耗;另外,由于开启了记录PLSQL每一层SQL的能力,长匿名块的文本会在SQL的每一层堆栈进行拷贝,当长度超过预设的SQL记录长度时,强制使用纯文本方式去进行脱敏处理,导致性能低下 | 将测试代码创建成存储过程再进行测试 |
| GaussDB相同代码反复执行多次,性能可能存在巨大变化 | 除了有cplan/gplan的因素,还有aplan、plsql cache等多种因素 | 先预热反复执行N次等性能稳定后再进行正式测试 |
八、测试脚本
-- ============================================================
-- GaussDB 文件写入性能对比测试 - 一键执行脚本
-- 适用于 GaussDB 506.0.0.SPC0100
-- ============================================================
-- 用法: gsql -r -d postgres -p 8000 -f perf_test.sql
-- ============================================================
\set ON_ERROR_STOP off
-- 清理旧对象
DROP FUNCTION IF EXISTS udf_write_file(text, text);
DROP FUNCTION IF EXISTS udf_write_file_fenced(text, text);
DROP VIEW IF EXISTS v_perf_summary CASCADE;
DROP VIEW IF EXISTS v_perf_ratio CASCADE;
DROP TABLE IF EXISTS perf_log;
DROP TABLE IF EXISTS perf_result;
DROP PROCEDURE IF EXISTS sp_insert_perf_log;
DROP PROCEDURE IF EXISTS sp_perf_bench;
DROP PROCEDURE IF EXISTS sp_clear_perf_data;
DROP VIEW IF EXISTS v_perf_summary;
DROP VIEW IF EXISTS v_perf_ratio;
DROP DIRECTORY IF EXISTS LOG_DIR;
DROP PROCEDURE IF EXISTS utl_write_log;
-- ============================================================
-- 1. 创建测试对象
-- ============================================================
CREATE TABLE perf_log (
seq_id INT,
user_id TEXT,
log_content TEXT,
log_time DATE
);
CREATE PROCEDURE sp_insert_perf_log(
p_seq_id INT,
p_user_id TEXT,
p_log_content TEXT,
p_log_time DATE
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO perf_log VALUES (p_seq_id, USER, 'aaaaaaaaaa', SYSDATE);
COMMIT;
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
CREATE FUNCTION udf_write_file(filename text, content text)
RETURNS integer
AS 'udf_write_file.so', 'udf_write_file'
LANGUAGE C STRICT;
CREATE FUNCTION udf_write_file_fenced(filename text, content text)
RETURNS integer
AS 'udf_write_file.so', 'udf_write_file'
LANGUAGE C STRICT fenced;
CREATE DIRECTORY LOG_DIR AS '/tmp';
CREATE OR REPLACE PROCEDURE utl_write_log(
p_filename IN VARCHAR2,
p_content IN VARCHAR2
) AS
v_file_handle dbe_file.FILE_TYPE;
BEGIN
v_file_handle := dbe_file.FOPEN('LOG_DIR', p_filename, 'A', 32767);
dbe_file.WRITE_LINE(v_file_handle, p_content);
dbe_file.CLOSE(v_file_handle);
EXCEPTION
WHEN OTHERS THEN
IF dbe_file.IS_OPEN(v_file_handle) THEN
dbe_file.CLOSE(v_file_handle);
END IF;
RAISE;
END utl_write_log;
/
CREATE TABLE perf_result (
test_no INT,
method TEXT,
loop_count INT,
round_no INT,
elapsed_ms NUMERIC(10,3),
test_time TIMESTAMP DEFAULT SYSDATE
);
-- ============================================================
-- 2. 创建性能测试存储过程 (预编译,追加模式)
-- ============================================================
CREATE OR REPLACE PROCEDURE sp_perf_bench(
p_loop_count IN INT,
p_rounds IN INT
) IS
TYPE t_log_rec IS RECORD (
seq_id INT,
log_content VARCHAR2(200)
);
TYPE t_log_tab IS TABLE OF t_log_rec;
v_tab t_log_tab;
v_start_ts TIMESTAMP;
v_end_ts TIMESTAMP;
v_ms NUMERIC(10,3);
v_ret INT;
v_i INT;
v_r INT;
v_test_no INT;
BEGIN
-- 获取当前最大 test_no
SELECT COALESCE(MAX(test_no), 0) INTO v_test_no FROM perf_result;
-- 清理日志表数据
DELETE FROM perf_log;
COMMIT;
-- ===== 方案 1: INSERT TABLE (逐条自治事务) =====
FOR v_r IN 1..p_rounds LOOP
DELETE FROM perf_log;
COMMIT;
v_start_ts := SYSTIMESTAMP;
FOR v_i IN 1..p_loop_count LOOP
sp_insert_perf_log(v_i, USER, 'aaaaaaaaaa', SYSDATE);
END LOOP;
v_end_ts := SYSTIMESTAMP;
v_ms := ROUND((v_end_ts - v_start_ts) * 24 * 3600 * 1000, 3);
v_test_no := v_test_no + 1;
INSERT INTO perf_result(test_no, method, loop_count, round_no, elapsed_ms)
VALUES (v_test_no, 'INSERT TABLE (逐条自治事务)', p_loop_count, v_r, v_ms);
COMMIT;
RAISE INFO '[%行 Round %] INSERT TABLE (逐条): % ms', p_loop_count, v_r, v_ms;
END LOOP;
-- ===== 方案 2: INSERT TABLE + unnest_table (批量插入) =====
FOR v_r IN 1..p_rounds LOOP
DELETE FROM perf_log;
COMMIT;
v_tab := t_log_tab();
FOR v_i IN 1..p_loop_count LOOP
v_tab.EXTEND;
v_tab(v_i).seq_id := v_i;
v_tab(v_i).log_content := v_i || ',' || USER || ',aaaaaaaaaa,' ||
TO_CHAR(SYSDATE, 'yyyymmddhh24miss');
END LOOP;
v_start_ts := SYSTIMESTAMP;
INSERT INTO perf_log(seq_id, log_content)
SELECT t.seq_id, t.log_content FROM unnest_table(v_tab) t;
COMMIT;
v_end_ts := SYSTIMESTAMP;
v_ms := ROUND((v_end_ts - v_start_ts) * 24 * 3600 * 1000, 3);
v_test_no := v_test_no + 1;
INSERT INTO perf_result(test_no, method, loop_count, round_no, elapsed_ms)
VALUES (v_test_no, 'INSERT TABLE (unnest批量)', p_loop_count, v_r, v_ms);
COMMIT;
RAISE INFO '[%行 Round %] INSERT TABLE (unnest批量): % ms', p_loop_count, v_r, v_ms;
END LOOP;
-- ===== 方案 3: C fopen/fprintf (非 fenced) =====
FOR v_r IN 1..p_rounds LOOP
v_start_ts := SYSTIMESTAMP;
FOR v_i IN 1..p_loop_count LOOP
v_ret := udf_write_file('/tmp/perf_c.log',
v_i || ',' || USER || ',aaaaaaaaaa,' ||
TO_CHAR(SYSDATE, 'yyyymmddhh24miss'));
END LOOP;
v_end_ts := SYSTIMESTAMP;
v_ms := ROUND((v_end_ts - v_start_ts) * 24 * 3600 * 1000, 3);
v_test_no := v_test_no + 1;
INSERT INTO perf_result(test_no, method, loop_count, round_no, elapsed_ms)
VALUES (v_test_no, 'C fopen/fprintf (非fenced)', p_loop_count, v_r, v_ms);
COMMIT;
RAISE INFO '[%行 Round %] C write_file (非fenced): % ms', p_loop_count, v_r, v_ms;
END LOOP;
-- ===== 方案 4: C fopen/fprintf (fenced) =====
FOR v_r IN 1..p_rounds LOOP
v_start_ts := SYSTIMESTAMP;
FOR v_i IN 1..p_loop_count LOOP
v_ret := udf_write_file_fenced('/tmp/perf_c_fenced.log',
v_i || ',' || USER || ',aaaaaaaaaa,' ||
TO_CHAR(SYSDATE, 'yyyymmddhh24miss'));
END LOOP;
v_end_ts := SYSTIMESTAMP;
v_ms := ROUND((v_end_ts - v_start_ts) * 24 * 3600 * 1000, 3);
v_test_no := v_test_no + 1;
INSERT INTO perf_result(test_no, method, loop_count, round_no, elapsed_ms)
VALUES (v_test_no, 'C fopen/fprintf (fenced)', p_loop_count, v_r, v_ms);
COMMIT;
RAISE INFO '[%行 Round %] C write_file (fenced): % ms', p_loop_count, v_r, v_ms;
END LOOP;
-- ===== 方案 5: DBE_FILE 内置包 =====
FOR v_r IN 1..p_rounds LOOP
v_start_ts := SYSTIMESTAMP;
FOR v_i IN 1..p_loop_count LOOP
utl_write_log('perf_dbe_file.log',
v_i || ',' || USER || ',aaaaaaaaaa,' ||
TO_CHAR(SYSDATE, 'yyyymmddhh24miss'));
END LOOP;
v_end_ts := SYSTIMESTAMP;
v_ms := ROUND((v_end_ts - v_start_ts) * 24 * 3600 * 1000, 3);
v_test_no := v_test_no + 1;
INSERT INTO perf_result(test_no, method, loop_count, round_no, elapsed_ms)
VALUES (v_test_no, 'DBE_FILE 内置包', p_loop_count, v_r, v_ms);
COMMIT;
RAISE INFO '[%行 Round %] DBE_FILE: % ms', p_loop_count, v_r, v_ms;
END LOOP;
RAISE INFO '[%行] 全部方案测试完成', p_loop_count;
END;
/
-- 清理历史数据的存储过程
CREATE OR REPLACE PROCEDURE sp_clear_perf_data AS
BEGIN
DELETE FROM perf_result;
DELETE FROM perf_log;
COMMIT;
END;
/
-- ============================================================
-- 3. 功能验证
-- ============================================================
DO $$
BEGIN
PERFORM sp_insert_perf_log(0, 'verify', 'test', SYSDATE);
RAISE NOTICE 'A: INSERT TABLE verified, rows=%',
(SELECT COUNT(*) FROM perf_log WHERE seq_id = 0);
DELETE FROM perf_log WHERE seq_id = 0;
COMMIT;
END;
$$;
SELECT 'B: write_file = '
|| udf_write_file('/tmp/perf_verify.log', 'verify non-fenced') AS verify;
SELECT 'C: write_file_fenced = '
|| udf_write_file_fenced('/tmp/perf_verify.log', 'verify fenced') AS verify;
DO $$
BEGIN
PERFORM utl_write_log('perf_verify_dbe.log', 'verify dbe_file');
RAISE NOTICE 'D: DBE_FILE verified';
END;
$$;
-- ============================================================
-- 4. 执行测试: 10 / 100 / 1000
-- ============================================================
CALL sp_clear_perf_data();
CALL sp_perf_bench(10, 3);
CALL sp_perf_bench(100, 3);
CALL sp_perf_bench(1000, 3);
-- ============================================================
-- 5. 打印汇总报告
-- ============================================================
SELECT '============== 汇总报告 ==============' AS phase;
SELECT
method AS "方案",
loop_count AS "循环次数",
TO_CHAR(ROUND(AVG(elapsed_ms), 3), '999990.000') AS "平均耗时(ms)",
TO_CHAR(ROUND(MIN(elapsed_ms), 3), '999990.000') AS "最小耗时(ms)",
TO_CHAR(ROUND(MAX(elapsed_ms), 3), '999990.000') AS "最大耗时(ms)",
TO_CHAR(ROUND(loop_count::NUMERIC / (AVG(elapsed_ms) / 1000), 1),
'999990.0') AS "吞吐量(rows/s)"
FROM perf_result
GROUP BY method, loop_count
ORDER BY loop_count, AVG(elapsed_ms);
SELECT '========= 相对基线倍数 =========' AS phase;
SELECT
r.method AS "方案",
r.loop_count AS "循环次数",
TO_CHAR(r.avg_ms, '999990.000') AS "平均耗时(ms)",
TO_CHAR(ROUND(b.avg_ms / r.avg_ms, 1), '999990.0') AS "相对基线倍数"
FROM (
SELECT method, loop_count, ROUND(AVG(elapsed_ms), 3) AS avg_ms
FROM perf_result
GROUP BY method, loop_count
) r
CROSS JOIN (
SELECT loop_count, ROUND(AVG(elapsed_ms), 3) AS avg_ms
FROM perf_result
WHERE method = 'INSERT TABLE (逐条自治事务)'
GROUP BY loop_count
) b
WHERE r.loop_count = b.loop_count
ORDER BY r.loop_count, r.avg_ms;
SELECT '========== 详细数据 ==========' AS phase;
SELECT test_no, method, loop_count AS "循环次数", round_no AS "轮次",
TO_CHAR(elapsed_ms, '999990.000') AS "耗时(ms)",
test_time
FROM perf_result
ORDER BY test_no;
本文全文基本由AI编写,包括测试代码编写和执行测试也由AI自主完成。作者仅提供思路、数据库测试环境以及原始测试脚本样例,并进行了少量文本性描述补充。