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)接续。
分配过程
- 每个“数据源名 + 表名”拥有独立的内存缓冲区。
- 号段内的 ID 通过
AtomicLong.compareAndSet分配。 - 号段不足时,用条件 UPDATE 从
sys_seq预留新号段:
UPDATE sys_seq SET id = id + ? WHERE k = ? AND id = ?
- 如果数据库返回的新号段与当前内存号段连续,本次批量请求会继续使用旧号段剩余值,再跨入新号段。
- 如果连续性已被外部修改破坏,实现会放弃本地剩余号段,从数据库的当前上界继续,优先避免重复 ID。
“零浪费”的真实边界
当前实现只能在进程未重启、且号段仍连续时利用旧号段剩余值。进程重启后,内存中未使用的已预留 ID 无法恢复,仍然可能出现空洞。因此不应对外承诺“应用重启零浪费”或全局严格连续。
多数据源与并发
- 内存 key 包含当前
DataSourceConfig名称,所以不同命名数据源的同名表不会共用本地缓冲区。 - 多实例间通过
WHERE id = ?的条件更新竞争号段,冲突时最多重试 5 次。 - 批量插入会按需要数量预取连续 ID 区间。
限制
- 当前内部 DDL 和
MAX(id)查询包含IF NOT EXISTS与反引号,不能仅根据方言注册就断言所有数据库都已完成验证。 - 业务表的主键列在当前自愈 SQL 中固定为
id。 - 如果要数据库自增,应显式使用
@TableId(type = IdType.AUTO);如果要手工输入、雪花 ID 或 UUID,分别使用INPUT、ASSIGN_ID、ASSIGN_UUID。 - 严格连续 ID 通常与可用性、多实例并发和重启容错冲突;业务不应将“永不跳号”作为不可替代的正确性条件。