GBase 8c 在过程里记流水时要小心自治事务边界
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
评论
热门帖子
- 12025-12-01浏览数:183149
- 22023-05-09浏览数:25911
- 42023-09-25浏览数:19552
- 52020-05-11浏览数:18169