跳到主要内容

8.4 智能号段 ID

DLZ-DB 在 @TableId 默认的 IdType.SEQ 下使用内部号段生成器分配 ID。本章是维护者实现说明,不是额外的公共 API;业务代码只需根据 注解与选项 选择 IdType,不应导入实现层生成器或 Holder。

当前数据结构

号段上界保存在 sys_seq 表,不是旧文档中的 table_id_segment

CREATE TABLE IF NOT EXISTS sys_seq (
k VARCHAR(64) PRIMARY KEY,
id BIGINT NOT NULL DEFAULT 0,
step BIGINT NOT NULL DEFAULT 1000
);
  • k 是业务表名。
  • id 是数据库已预留号段的上界。
  • sys_seq 不存在时,当前实现会尝试自动创建,并从业务表的 MAX(id) 接续。

分配过程

  1. 每个“数据源名 + 表名”拥有独立的内存缓冲区。
  2. 号段内的 ID 通过 AtomicLong.compareAndSet 分配。
  3. 号段不足时,用条件 UPDATE 从 sys_seq 预留新号段:
UPDATE sys_seq SET id = id + ? WHERE k = ? AND id = ?
  1. 如果数据库返回的新号段与当前内存号段连续,本次批量请求会继续使用旧号段剩余值,再跨入新号段。
  2. 如果连续性已被外部修改破坏,实现会放弃本地剩余号段,从数据库的当前上界继续,优先避免重复 ID。

“零浪费”的真实边界

当前实现只能在进程未重启、且号段仍连续时利用旧号段剩余值。进程重启后,内存中未使用的已预留 ID 无法恢复,仍然可能出现空洞。因此不应对外承诺“应用重启零浪费”或全局严格连续。

多数据源与并发

  • 内存 key 包含当前 DataSourceConfig 名称,所以不同命名数据源的同名表不会共用本地缓冲区。
  • 多实例间通过 WHERE id = ? 的条件更新竞争号段,冲突时最多重试 5 次。
  • 批量插入会按需要数量预取连续 ID 区间。

限制

  1. 当前内部 DDL 和 MAX(id) 查询包含 IF NOT EXISTS 与反引号,不能仅根据方言注册就断言所有数据库都已完成验证。
  2. 业务表的主键列在当前自愈 SQL 中固定为 id
  3. 如果要数据库自增,应显式使用 @TableId(type = IdType.AUTO);如果要手工输入、雪花 ID 或 UUID,分别使用 INPUTASSIGN_IDASSIGN_UUID
  4. 严格连续 ID 通常与可用性、多实例并发和重启容错冲突;业务不应将“永不跳号”作为不可替代的正确性条件。