GBase 8c
其他
文章

GBase 8c 序列号跳号这事,别按业务单据号去理解

发表于2026-05-14 09:33:2842次浏览1个评论

GBase 8c 序列号跳号这事,别按业务单据号去理解

我最近整理 GBase 8c 里自增编号相关问题时,发现序列对象经常被拿来和业务单据号混着讨论。现场一旦看到编号不连续,就有人怀疑事务丢了、主键乱了,甚至开始翻恢复记录。真正落到数据库机制上看,序列本来就不是“连续号码生成器”,它更像一个高并发下快速拿号的数据库对象。

在 GBase 8c 里,序列通常用于主键、流水表 ID、批次 ID 这类技术标识。它的价值是低耦合、高并发、减少热点竞争,而不是保证每个数字都能被业务看见。把序列当成发票号、合同号、财务凭证号来用,后面大概率会被“跳号”问题反复折腾。

序列的核心特点

我自己理解下来,序列有三个容易误解的特点。

特点 现场表现 排查时要注意什么
取号独立于事务回滚 事务回滚后,已经取过的值不会退回 看到空洞不等于数据丢失
cache 会提前分配 实例重启或连接异常后,缓存值可能没被使用 cache 越大,跳号感越明显
多会话并发取号 不同会话拿到的值不按提交顺序呈现 ID 大小不代表业务提交先后

这几个特点放到一起,就能解释很多现场疑问。比如一个批量插入任务失败回滚,序列值已经向前走了;一个应用连接池拿了号但还没插入,连接中断后也会留下空洞;多个业务线程并发提交时,后提交的数据 ID 反而更小,也并不奇怪。

先把序列对象查清楚

遇到跳号问题,我通常不会先看应用日志,而是先把对象关系确认清楚。尤其是历史库里,有些表的默认值来自序列,有些表是应用自己拼 ID,还有些表经历过迁移,序列当前值和表内最大值可能已经错开。

-- 查看当前 schema 下的序列对象
SELECT
    n.nspname AS schema_name,
    c.relname AS sequence_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'S'
ORDER BY n.nspname, c.relname;

-- 查看表字段默认值里是否引用序列
SELECT
    n.nspname AS schema_name,
    c.relname AS table_name,
    a.attname AS column_name,
    pg_get_expr(d.adbin, d.adrelid) AS default_expr
FROM pg_attrdef d
JOIN pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum
JOIN pg_class c ON c.oid = d.adrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE pg_get_expr(d.adbin, d.adrelid) LIKE '%nextval%'
ORDER BY 1,2,3;

这个查询能先回答一个关键问题:到底是谁在用这个序列。现场常见情况是多个表误用了同一个序列,或者测试脚本直接调用 nextval 做压测,导致业务表看起来跳得很厉害。

nextval、currval、setval 的使用边界

序列常用函数不复杂,但用错后影响很大。

函数 用途 我会提醒的风险
nextval 获取下一个序列值 调用即推进,不随事务回滚
currval 获取当前会话最近一次取到的值 当前会话没调用过 nextval 时不能拿来当全局当前值
setval 调整序列当前值 适合修复迁移后错位,不适合日常频繁调用

迁移后最容易遇到的是表里已经有数据,但序列还停留在旧值。表现是新插入数据时报主键冲突。处理思路不是删除数据,也不是让应用重试,而是把序列推进到表内最大值之后。

-- 示例:订单表迁移后修正序列位置
SELECT setval(
    'ods.order_id_seq',
    (SELECT COALESCE(MAX(order_id), 0) FROM ods.order_base),
    true
);

-- 验证下一次取号
SELECT nextval('ods.order_id_seq');

这里第三个参数设为 true,表示下一次 nextval 返回的是当前值之后的下一个值。现场执行前,我会先确认表上没有并发写入,至少要在维护窗口或应用暂停写入的条件下做。否则刚算完 MAX(id),另一边又插入了新数据,修复动作就可能和业务并发撞上。

cache 不是越大越好

很多人调序列 cache 是从性能角度出发,这个方向没问题。cache 大一点,可以减少频繁持久化序列状态带来的开销。但 cache 不是单纯的性能参数,它也会影响跳号幅度。

CREATE SEQUENCE ods.order_id_seq
    START WITH 1
    INCREMENT BY 1
    MINVALUE 1
    NO MAXVALUE
    CACHE 100;

我的经验是按对象用途分层设置。

使用场景 cache 倾向 说明
技术主键、高频写入事实表 可以适当增大 更关注吞吐,跳号可接受
低频字典表、配置表 保守设置 性能压力小,没必要放大空洞
对外展示编号 不建议直接用序列 应该另做业务编号规则
财务、合同、审计类单据 不建议用普通序列保证连续 连续性应由业务流程和号段表控制

如果业务方只是要求“唯一”,序列很合适。如果业务方要求“连续、可解释、可追溯、按日期重置”,那就已经超出普通序列的边界了。

分布式环境里更不能拿 ID 当时间线

GBase 8c 支持分布式部署,序列在这种环境下更容易被误读。应用从 CN 连接,数据落到不同 DN,多个会话并发写入时,ID 的增长顺序和事务提交顺序、业务发生顺序并不完全一致。

我在现场一般会明确三条开发规范。

规范 原因 推荐做法
不用 ID 判断业务先后 并发提交会打乱可见顺序 用业务时间、提交时间、状态流转时间
不用 ID 判断是否漏数 序列天然可能跳号 用行数、校验表、业务状态核对
不直接暴露技术 ID 用户容易把空洞理解成异常 对外编号单独生成

比如订单列表排序,如果直接 ORDER BY order_id DESC,看起来简单,但在并发写入和补录数据场景下并不严谨。我更倾向于使用业务创建时间,再用 ID 做次级排序。

SELECT order_id, order_no, create_time, order_status
FROM ods.order_base
WHERE customer_id = 'C20260514001'
ORDER BY create_time DESC, order_id DESC
LIMIT 50;

对外连续编号可以用号段表

如果确实要做业务连续号,我更倾向于把它从序列里拆出来,做成业务号段或编号服务。数据库里可以用一张号段表记录业务维度、日期、当前值,由应用在事务中加锁更新。

CREATE TABLE biz_no_segment (
    biz_type       varchar(32) PRIMARY KEY,
    current_value  bigint NOT NULL,
    updated_at     timestamp NOT NULL DEFAULT current_timestamp
);

-- 取号时锁定对应业务类型
BEGIN;

SELECT current_value
FROM biz_no_segment
WHERE biz_type = 'CONTRACT_202605'
FOR UPDATE;

UPDATE biz_no_segment
SET current_value = current_value + 1,
    updated_at = current_timestamp
WHERE biz_type = 'CONTRACT_202605';

COMMIT;

这种方式吞吐不如序列,但它满足的是另一类目标:可控、可解释、能按业务维度管理。真正落到现场时,技术主键和业务编号分离,后续沟通成本会低很多。

跳号问题的排查清单

排查项 SQL 或动作 目的
查表内最大 ID SELECT MAX(id) FROM table_name; 判断序列是否落后
查默认值来源 查询 pg_attrdef 确认字段是否使用序列
查调用方 应用 SQL、作业脚本、存储过程 找出额外 nextval 调用
查 cache 设置 查看序列定义 判断跳号幅度是否和 cache 相关
查批处理失败记录 作业日志、事务回滚记录 判断是否大量取号未提交

我不太建议把跳号问题包装成数据库异常。更稳妥的做法是先确认它是不是机制内现象,再判断是否影响业务语义。如果只是技术主键跳号,通常不需要修。如果业务把序列当成了连续单据号,就要调整设计边界。

参考资料

GBase 8c 文档介绍 https://www.gbase.cn/docs/gbase-8c/%E6%AC%A2%E8%BF%8E/
GBase 8c SQL参考指南 https://www.gbase.cn/docs/gbase-8c/05%20SQL%E5%8F%82%E8%80%83/SQL%E8%AF%AD%E6%B3%95
GBase 8c 开发者指南 系统模式 https://www.gbase.cn/docs/gbase-8c/03%20%E5%BC%80%E5%8F%91%E8%80%85%E6%8C%87%E5%8D%97/%E7%B3%BB%E7%BB%9F%E6%A8%A1%E5%BC%8F

评论

登录后才可以发表评论
GBase用户47954发表于 3个月前
感谢作者的精彩分享!