ocean自增主键模式说明

OceanBase 自增主键模式说明

适用范围:OceanBase MySQL 兼容模式。AUTO_INCREMENT_MODE 的支持情况、默认值及具体行为应以目标版本为准。本文示例未连接实际实例执行,不应直接套用于原生 MySQL。

一、先区分四个概念

概念 作用 不保证什么
AUTO_INCREMENT 在插入时自动生成 ID 不保证编号连续
PRIMARY KEY (id) 强制 ID 非空且全表唯一 不表示 ID 就是创建时间或提交时间
AUTO_INCREMENT_MODE 控制自增值生成的有序性模式 不改变查询的排序规则
PARTITION BY HASH (id) 根据 ID 的 HASH 规则决定数据属于哪个分区 不按连续 ID 范围分区
text 复制代码
应用插入订单,不传 id
          |
          v
自增机制分配 id
          |
          v
HASH 分区规则确定目标分区
          |
          v
数据库写入数据并检查主键唯一性

这是概念上的职责划分,不代表数据库内部严格的执行步骤。

二、自增 ID 与并发插入

定义自增列后,应用可以不提供 ID:

sql 复制代码
INSERT INTO orders (user_id, amount)
VALUES (1001, 99.90);

并发插入时,数据库负责协调生成自增值,应用不需要使用 SELECT MAX(id) + 1 计算 ID。后者存在并发竞争,不应当作 ID 生成方案。

PRIMARY KEY (id) 是唯一性约束:即使应用手动提供 ID,数据库也不会允许相同 ID 的两条记录都成功提交。手动插入已有 ID 的普通 INSERT 会产生重复键错误。

为什么自增值不保证连续?

表中的 ID 可能是:

text 复制代码
101、102、105、108......

缺号不一定表示数据丢失。常见原因包括:

  • 事务分配 ID 后回滚,已分配的值通常不会回收。
  • 某些插入执行路径先分配 ID,随后因其他原因失败。
  • 已有记录被删除,空缺不会自动补回。
  • 预分配或缓存的自增值未用完,可能因重启等情况留下空缺,具体取决于版本和模式。
text 复制代码
事务 A:获得 101 -> 提交成功
事务 B:获得 102 -> 回滚
事务 C:获得 103 -> 提交成功

表中最终保留:101、103

因此不能使用 MAX(id) 计算订单总数,也不能仅凭缺号判断是否丢单。

三、AUTO_INCREMENT_MODE 的两种模式

3.1 ORDER:有序自增

ORDER 关注自动生成值的全局有序性。需要区分自增值的分配顺序、请求到达顺序和事务提交顺序,它们并不是同一件事。

text 复制代码
事务 A:先获得 ID 101,执行较慢
事务 B:后获得 ID 102,先提交
事务 A:随后提交

分配顺序:101 -> 102
提交顺序:B -> A

即使选择 ORDER:

  • ID 也不保证连续。
  • ID 大小也不代表事务提交先后。
  • ID 顺序也不能严格代替业务创建时间顺序。
  • 全局有序生成需要协调,应结合实际负载评估性能成本。

3.2 NOORDER:无序自增

NOORDER 不要求跨节点生成的自增值按分配时间全局递增,适用于更关注分布式生成效率的场景。

可以通过号段缓存理解可能出现的现象:

text 复制代码
分配端 A 缓存较小的一段 ID
分配端 B 缓存较大的一段 ID

B 先生成一个 ID:10001
A 后生成一个 ID:101

这里的数字和缓存分配端只是示意,不代表固定号段大小,也不代表每个分区都固定拥有一个号段。具体缓存粒度和分配实现以版本为准。

无序表示生成顺序不要求全局递增,不表示允许主键重复,也不表示查询结果无法排序。

3.3 模式对比

比较项 ORDER NOORDER
自动生成值的全局有序性 关注全局有序生成 不要求全局有序生成
是否保证无跳号 否 否
是否保证按事务提交顺序编号 否 否
PRIMARY KEY (id) 是否约束 ID 唯一 是 是
是否影响 ORDER BY id 的正确性 否 否
选择侧重点 有序生成需求 分布式生成效率

不要把某种模式当作所有版本的默认值。未显式指定时,应根据实际版本和表定义确认生效模式。

四、建表 DDL

以下两种方案二选一,不要在同一数据库中重复创建同名表。

4.1 ORDER 模式

sql 复制代码
CREATE TABLE orders (
    id BIGINT NOT NULL AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(18, 2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    PRIMARY KEY (id),
    KEY idx_created_at (created_at)
)
AUTO_INCREMENT_MODE = 'ORDER'
PARTITION BY HASH (id)
PARTITIONS 8;

4.2 NOORDER 模式

sql 复制代码
CREATE TABLE orders (
    id BIGINT NOT NULL AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(18, 2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    PRIMARY KEY (id),
    KEY idx_created_at (created_at)
)
AUTO_INCREMENT_MODE = 'NOORDER'
PARTITION BY HASH (id)
PARTITIONS 8;

两个示例都是 id 单列自增主键,按 ID 做 HASH 分区。8 只是示例分区数,应结合数据量和租户资源确定。不需要分区时,可以移除末尾的 PARTITION BY HASH (id) PARTITIONS 8,保留语句结尾分号。

4.3 为什么不继续按 created_at 分区?

前面的按月分区设计采用:

sql 复制代码
PRIMARY KEY (id, created_at)

该主键只保证组合唯一,不单独保证 id 唯一。在这里讨论的 OceanBase MySQL 分区表规则下,主键需要包含分区键。因此,要求 PRIMARY KEY (id) 时,应将分区键改为 id,或使用非分区表。

如果业务必须按创建时间分区,又必须由数据库约束 ID 全表唯一,应结合目标版本评估"包含分区键的联合主键 + ID 全局唯一索引"。全局唯一索引不等于 ID 单列主键。

五、号段分配不等于按号段分区

这两个问题需要分开理解:

问题 对应机制
下一个 ID 如何产生? 自增模式及其分配、缓存机制
这个 ID 对应的记录存在哪个分区? 表的分区规则

当前 HASH 分区不表示:

text 复制代码
分区 1:ID 1~10000
分区 2:ID 10001~20000
分区 3:ID 20001~30000

按连续 ID 范围划分属于 RANGE 分区。内部即使存在自增号段缓存,也不会把 HASH 分区自动变成 RANGE 分区。

六、跨分区排序

6.1 按 ID 排序没有正确性问题

sql 复制代码
SELECT *
FROM orders
ORDER BY id DESC
LIMIT 20;

该查询返回整个查询范围中 ID 最大的 20 条记录,不是每个分区分别返回一组未合并的结果。

text 复制代码
多个分区的数据
      |
      v
数据库全局排序或有序归并
      |
      v
返回 ID 最大的 20 条

实际算法由执行计划决定。ID 跳号及 NOORDER 生成模式都不影响排序正确性。不写 ORDER BY,则不能依赖默认返回顺序。

6.2 按创建时间展示订单

"ID 最大的订单"不一定等于"最新创建的订单"。需要按创建时间展示时使用:

sql 复制代码
SELECT *
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 20;

id 用于在创建时间相同时提供确定的排序顺序。当前 DATETIME 没有小数秒精度,同一秒创建多条订单很正常;即使改成 DATETIME(6),时间戳也不能保证唯一。

6.3 跨分区查询的成本

HASH 分区不按 ID 连续范围排列。全表按 ID 取前 20 条通常需要访问多个分区,再合并结果,但不一定需要读取所有记录。

id = 12345 可以据分区键定位分区;id < 9801 或仅按时间过滤,通常不能据此裁剪 ID 的 HASH 分区。实际性能应通过执行计划和数据规模验证。

七、按 ID 游标分页

sql 复制代码
-- 第一页
SELECT *
FROM orders
ORDER BY id DESC
LIMIT 20;

-- 下一页,9801 为上一页最后一条记录的 ID
SELECT *
FROM orders
WHERE id < 9801
ORDER BY id DESC
LIMIT 20;

该方式无需 ID 连续,也能避免大偏移量 OFFSET 的开销。但它不自动提供跨多次查询的一致快照:

  • 并发写入、删除或晚提交可能改变后续页看到的数据。
  • NOORDER 下,后生成的 ID 可能小于已有 ID,不能把 ID 游标直接当作完整的增量变更水位。
  • 即使使用 ORDER,较小 ID 的事务晚提交,也可能让按 id > 上次最大值 轮询的程序漏读。

需要可靠地捕获所有变更时,应评估 CDC 或具有明确一致性保证的同步方案,而不是仅依赖自增 ID。

八、选型与验证

业务需求 建议
ID 只作为唯一标识,侧重分布式生成效率 评估 NOORDER
需要自动生成值全局有序 评估 ORDER,并测试性能
展示最新创建的订单 使用创建时间排序,ID 作为次级排序键
编号严格连续、不允许跳号 另行设计业务编号机制,两种模式都不能直接满足
防止同一订单请求重试后重复创建 使用稳定的业务唯一键或幂等机制,自增主键本身不能识别重复请求

先检查版本,再检查实际表定义:

sql 复制代码
SELECT VERSION();

SHOW CREATE TABLE orders;

如果表定义未显示模式,不要仅凭这一点推断默认值,应查阅对应版本文档。若建表提示不支持 AUTO_INCREMENT_MODE,需要核对版本和兼容模式,而不是假定设置已经生效。

九、总结

自增负责生成 ID,主键负责约束唯一,分区负责组织数据,ORDER BY 负责查询排序。

  • ORDER 与 NOORDER 区分的是生成有序性,不是查询能否排序。
  • 两种模式都不保证 ID 连续,也不保证 ID 顺序等于事务提交顺序。
  • HASH 分区和自增号段是独立的概念。
  • 跨分区按 ID 排序可以得到正确的全局结果,但需要评估执行成本。
  • 默认模式及可用语法必须结合目标 OceanBase 版本确认。
相关推荐
知行产研20 分钟前
易控智驾:毛利率跃升17.2个百分点,矿山无人驾驶从“规模换增长“迈入“盈利拐点“
数据库·mysql
千里码aicood30 分钟前
基于CART算法的图书分类系统设计与实现
数据库·算法·分类
饺子大魔王的男人1 小时前
Redis 内存和连接异常怎么排查?用 redis_exporter 搭一套可远程抓取的监控链路
数据库·redis·bootstrap
可涵不会debug1 小时前
第一次学 LangGraph:用一个快递案例搞懂 State、Node、Edge 和 compile
服务器·数据库·mysql
IvorySQL2 小时前
PostgreSQL 日报 |RLS 机密性绕过漏洞(9 月 27 日)
数据库·postgresql
夕除2 小时前
redis--RDB AOF
数据库·redis·缓存
数据工匠老o2 小时前
"能用应用层解决的不用存储过程",十年数据库经验总结
数据库·架构
AC赳赳老秦2 小时前
公开音频转写信息提取:OpenClaw 处理发布会与听证会文本并提取核心决策信息
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
老纪的技术唠嗑局3 小时前
异步索引特性解析:OceanBase 如何提升持续写入场景下的索引检索性能
数据库
老纪的技术唠嗑局3 小时前
Tibo 谈 Codex:harness 总比模型快一步
数据库·人工智能