GBase 8c
其他
文章

GBase 8c 在过程里记流水时要小心自治事务边界

发表于2026-05-15 09:44:2032次浏览7个评论

GBase 8c 在过程里记流水时要小心自治事务边界

我最近看 GBase 8c 自治事务资料时,觉得它特别适合拿来讨论一个开发现场经常遇到的问题:业务过程失败了,排障流水也跟着回滚了。等真正去查问题时,只剩应用日志里几行模糊报错,数据库侧看不到入参、关键状态、校验失败原因。尤其是批处理、清结算、接口落库这类场景,失败链路的现场信息往往比成功数据更重要。

GBase 8c 支持在函数中定义自治事务,标识符为 PRAGMA AUTONOMOUS_TRANSACTION。我自己的理解是,它能让某段过程内的记录动作拥有独立提交边界,不随外层事务一起回滚。这个能力很好用,但也很容易被滥用。真正落到现场时,它更适合记录审计流水、错误明细、处理轨迹,不适合作为绕开事务一致性的业务捷径。

为什么普通流水会被回滚带走

先看一个常见写法。业务过程里先写处理日志,再做业务更新,最后发现校验失败抛异常:

CREATE TABLE ops_proc_log (
    log_id       bigint,
    biz_id       varchar(64),
    step_name    varchar(64),
    log_msg      varchar(500),
    create_time  timestamp
);

CREATE TABLE pay_order (
    order_id     varchar(64),
    order_status varchar(20),
    pay_amt      numeric(18,2),
    update_time  timestamp
);

伪过程逻辑如下:

INSERT INTO ops_proc_log
VALUES (10001, 'PAY202605150001', 'CHECK', 'start check', current_timestamp);

UPDATE pay_order
SET order_status = 'PAID',
    update_time = current_timestamp
WHERE order_id = 'PAY202605150001';

-- 后面发现对账金额不一致,抛出异常,外层事务回滚

如果日志表和业务表处在同一个事务里,业务更新回滚的同时,前面写入的 ops_proc_log 也会回滚。业务数据保持一致是对的,但排障线索消失了。

这时自治事务就有价值。它让“记录现场”这件事从外层业务事务里拆出来,失败也能留下独立提交的日志。

自治事务适合放在哪里

自治事务最容易被误用的地方,是把业务补偿、状态推进、库存扣减也写进去。我的习惯是把它限定在“非主业务事实”的记录类对象上。

用法 是否建议 原因
记录过程入参 建议 便于失败后还原现场
记录校验失败原因 建议 外层事务回滚后仍可排查
记录批次处理进度 视场景 要区分进度日志和业务状态
更新订单状态 不建议 容易破坏业务事务一致性
扣减账户余额 不建议 失败后可能形成脏业务事实
写审计流水 建议 审计记录通常要求独立留存

从落地角度看,我更愿意把自治事务写成一个很小的日志函数,外层过程只调用它,不在里面塞复杂业务逻辑。

一个更安全的日志函数形态

示例可以设计成这样:

CREATE TABLE ops_autonomous_log (
    log_id        bigint,
    module_name   varchar(64),
    biz_key       varchar(128),
    step_name     varchar(64),
    log_level     varchar(16),
    log_message   varchar(1000),
    create_time   timestamp
);

自治事务函数只做一件事:插入日志并提交。

CREATE OR REPLACE FUNCTION write_proc_log(
    p_log_id      bigint,
    p_module_name varchar,
    p_biz_key     varchar,
    p_step_name   varchar,
    p_log_level   varchar,
    p_log_message varchar
) RETURN void
AS
DECLARE
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    INSERT INTO ops_autonomous_log(
        log_id, module_name, biz_key, step_name,
        log_level, log_message, create_time
    ) VALUES (
        p_log_id, p_module_name, p_biz_key, p_step_name,
        p_log_level, p_log_message, current_timestamp
    );

    COMMIT;
END;
/

现场实现时,函数语法要结合数据库兼容模式、过程语言规范和项目开发标准确认。这里的重点不是语法花样,而是边界:自治函数只写日志,不读取复杂业务表,不修改主数据。

外层过程调用示例:

SELECT write_proc_log(
    20001,
    'PAY_CLEAR',
    'BATCH2026051501',
    'START',
    'INFO',
    'clear batch start'
);

SELECT write_proc_log(
    20002,
    'PAY_CLEAR',
    'BATCH2026051501',
    'CHECK_BALANCE',
    'ERROR',
    'balance check failed, diff amount over threshold'
);

自治事务不是“失败也要提交业务”的工具

这个边界要特别强调。自治事务提交后,不会因为外层事务失败而自动回滚。如果把它用在业务表上,就可能出现外层失败、局部业务数据已提交的情况。

外层事务结果 自治日志结果 是否符合预期
外层提交 日志提交 正常
外层回滚 日志提交 适合排障
外层回滚 业务状态被自治提交 高风险
外层重试 多条日志被记录 可接受,但要设计幂等标识

我更倾向于给自治事务相关对象加命名规范,比如 ops_audit_trace_ 开头,避免开发把业务表误放进去。代码评审时只要看到 PRAGMA AUTONOMOUS_TRANSACTION,就要追问:它写的是不是主业务事实?

记录失败日志还要考虑幂等和噪声

自治事务让日志能留下来,但并不意味着日志可以随便写。批处理失败重试、接口超时重发、应用自动补偿都会带来重复日志。没有幂等设计,排障时日志会变成噪声。

我常用这几个字段:

字段 用途
module_name 区分业务模块
biz_key 关联业务主键或批次号
step_name 定位处理阶段
log_level 区分 INFO/WARN/ERROR
request_id 对接应用链路追踪
retry_no 标识第几次重试
create_time 还原时间线

示例扩展:

ALTER TABLE ops_autonomous_log ADD request_id varchar(128);
ALTER TABLE ops_autonomous_log ADD retry_no integer;
ALTER TABLE ops_autonomous_log ADD host_name varchar(128);

排查时可以这样看:

SELECT biz_key,
       step_name,
       log_level,
       log_message,
       create_time
FROM ops_autonomous_log
WHERE module_name = 'PAY_CLEAR'
  AND biz_key = 'BATCH2026051501'
ORDER BY create_time;

如果错误日志量很大,还要给运维留清理策略。自治事务日志不是审计日志的完整替代品,也不能无限增长。高频批处理建议按日期或批次归档。

异常处理里调用自治日志更实用

自治事务最适合放在异常处理路径里。外层事务失败时,异常信息、关键入参、影响范围都能留下。

BEGIN
    SELECT write_proc_log(
        30001, 'ORDER_SYNC', 'REQ202605150001',
        'START', 'INFO', 'sync start'
    );

    UPDATE pay_order
    SET order_status = 'SYNCED'
    WHERE order_id = 'PAY202605150001';

EXCEPTION
    WHEN OTHERS THEN
        SELECT write_proc_log(
            30002, 'ORDER_SYNC', 'REQ202605150001',
            'EXCEPTION', 'ERROR', 'sync failed'
        );
        RAISE;
END;

我会避免在异常里做大查询、大对象拼接、复杂循环。异常路径本身就处在不稳定状态,自治日志函数要足够轻量,失败也不要拖垮主流程。

权限和安全也要单独设计

自治事务函数通常会被很多业务过程调用。如果权限放得太宽,可能被拿来写入伪造日志;如果权限太窄,又可能导致异常时日志写不进去。

项目 建议
日志表写权限 只授予日志函数所属用户
业务用户 只授予执行日志函数权限
日志内容 避免写敏感明文,如密码、证件完整号
清理权限 由运维或审计角色控制
查询权限 按模块或角色拆分

示例:

GRANT EXECUTE ON FUNCTION write_proc_log TO app_pay_user;
REVOKE INSERT, UPDATE, DELETE ON ops_autonomous_log FROM app_pay_user;

我更倾向于让业务账号“只能调用函数,不能直接改日志表”。这样既保留使用便利,也减少日志被篡改的风险。

落地前我会做这几项检查

检查项 重点
是否只写日志类对象 防止业务事实绕开外层事务
是否有批次号或请求号 便于串起链路
是否考虑重试重复 避免日志误判
是否控制日志大小 防止高频错误打满空间
是否有清理归档策略 运维可持续
是否有权限隔离 防止伪造和篡改
是否在测试库验证回滚行为 确认外层回滚后日志仍在

自治事务是一个很有用的工具,但它适合小心使用。我的理解是,它不是用来“突破事务”的,而是用来在事务失败时保留排障和审计线索。只要边界画清楚,很多生产问题的定位成本会明显下降。

参考资料

自治事务 | GBASE南大通用
https://www.gbase.cn/docs/gbase-8c/03%20%E5%BC%80%E5%8F%91%E8%80%85%E6%8C%87%E5%8D%97/%E8%87%AA%E6%B2%BB%E4%BA%8B%E5%8A%A1

GBase 8c数据库使用 | GBASE南大通用
https://www.gbase.cn/docs/gbase-8c/03%20%E5%BC%80%E5%8F%91%E8%80%85%E6%8C%87%E5%8D%97/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BD%BF%E7%94%A8

南大通用GBase 8c企业级数据库特性之兼容性
https://www.gbase.cn/community/post/4794

评论

登录后才可以发表评论
GBase用户51882发表于 3个月前
厉害了
用户头像
山佳发表于 3个月前
学会了
用户头像
山佳发表于 3个月前
厉害了
紫菀发表于 3个月前
加油
GBase用户47954发表于 3个月前
感谢作者的精彩分享!
xiaowuyun发表于 3个月前
继续努力
青菇凉发表于 3个月前
三人行必有我师