在应用系统中,自增列和序列被广泛用于表数据的主键或序号,以实现数据的递增或唯一性。然而,在 OceanBase 数据库的分布式架构下,自增列和序列与传统集中式数据库存在显著差异。如果忽视这些差异,将导致不同的实现效果和性能。
因此,本篇文档将帮助您深入了解 OceanBase 数据库自增列和序列的概念、实现方式、行为特性,并提供相应的使用建议。
基本概念
自增列(AUTO_INCREMENT):数据库表中的一种列属性,可以自动生成唯一的、递增的值,用于表示该行数据的唯一标识。
序列(SEQUENCE):数据库按照一定规则生成的唯一且通常是递增的数值,通常被用于生成唯一标识符,独立于表存在。
功能变更记录
OceanBase 数据库在不同版本中对自增列和序列功能进行了持续优化:
V3.2.1 版本:自增主键支持作为分区键,自增列的值全局唯一,但在分区内不保证始终增长,和原生 MySQL 行为不同。INSERT 均生成 distribute insert 计划,性能会有下降。
V4.0.0 版本:OceanBase 数据库的 MySQL 模式下,自增列支持指定两种不同的自增模式。
您可通过租户级配置项 DEFAULT_AUTO_INCREMENT_MODE 控制默认模式,也可在建表时指定 AUTO_INCREMENT_MODE。
默认为 ORDER。
ORDER:底层是基于集中缓存的自增列,自增列全局递增,更好地兼容 MySQL 行为。
NOORDER:分布式缓存的自增列,只保证全局唯一,分区表拥有更好的性能(只保证分区内自增,不保证全局自增)。
V4.2.2 版本:INTEGER 列类型增长支持 Online 方式。对于主键列、分区键、索引列、被生成列依赖的列和有 Check 约束的列,列类型如果为整型,当列类型修改为取值范围更大的整形列类型时(如 INT -> BIGINT),在 V4.2.1 版本中通过双表双写的 Offline DDL 实现,转换过程中会加表锁,阻塞读写。从 V4.2.2 版本开始,将 Offline DDL 改进为 Online DDL,整型列类型增长将不再影响业务写入。
V4.2.3 及以上版本:自增值起点支持改小。当减小一个表的自增字段的值时,如果表中已存在数据并且自增列中的最大值不小于新指定的 AUTO_INCREMENT 值时,新的 AUTO_INCREMENT 值将自动调整为表中自增列现有最大值的下一个取值。
例如,自增列当前的最大值为 5,当前 AUTO_INCREMENT 的值是 8,而 AUTO_INCREMENT 设置为介于 0 到 6 之间的任何值。
语句执行成功后,实际的 AUTO_INCREMENT 值都会被调整为 6。和原生 MySQL 行为兼容。
在 OceanBase 数据库 V4.2.3 版本中,您可以为每个表单独设置自增值缓存大小(CACHE SIZE)。在此版本之前,所有表的自增值缓存大小都由全局参数 AUTO_INCREMENT_CACHE_SIZE 控制,应用于每个节点。
现在,您可以在创建表时,通过指定 AUTO_INCREMENT_CACHE_SIZE 参数,为不同的表设置不同的自增值缓存策略。在 OceanBase 数据库 V4.2.3 版本中,ORDER 模式下,切主后不会跳变。
自增列实现原理
OceanBase 数据库的自增列支持两种模式,其核心区别在于缓存管理方式。
NOORDER 模式(分布式缓存):每个 OBServer 节点独立从内部表申请自增区间并缓存,性能高。缺点是不保证全局递增,易出现跳变。适用于对性能敏感或允许跳变的业务。
ORDER 模式(集中式缓存):选举一个 Leader OBServer 作为自增服务节点,其他节点通过 RPC 向其申请值。优点是在大多数情况下可生成连续递增的值,兼容 MySQL 行为。缺点是在高并发下性能低于 NOORDER,Leader 切换时仍可能跳变。在 V4.2.3 版本之后,ORDER 模式切主后不会跳变。
自增列与集中式数据库对比
序列(SEQUENCE)实现机制
OceanBase 数据库的用户中,许多业务最初运行在 DB2 或 Oracle 上,现计划转向 MySQL,需要将 DB2 或 Oracle 的业务迁移至 OceanBase 数据库 MySQL 模式的租户。为减少客户在迁移过程中对大量使用 SEQUENCE 的业务进行改造的复杂度,OceanBase 数据库在 MySQL 模式下提供了与 Oracle 行为兼容的 SEQUENCE 功能。
适用场景:
从 DB2/Oracle 向 OceanBase 数据库 MySQL 模式的租户进行迁移的场景。
自增列(increment column)和表绑定的特性无法满足业务使用要求。序列(SEQUENCE)不和表绑定,可独立创建,也可跨表使用。
自增列(increment column)没有 CYCLE 能力,达到 MAXVALUE 后会罢工的特性无法满足业务使用要求。序列(SEQUENCE)支持循环序列,有 CYCLE 能力。
自增列创建语法
-- 创建表时定义自增列(默认 ORDER 模式)CREATE TABLE t_user(id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50)) AUTO_INCREMENT = 1;-- 显式指定 NOORDER 模式CREATE TABLE t_order(id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,order_no VARCHAR(20)) AUTO_INCREMENT_MODE = 'NOORDER';
序列创建语法
序列是独立于表的对象,可用于多表共享主键或复杂业务编号生成。
CREATE SEQUENCE seq_order_idSTART WITH 1INCREMENT BY 1MINVALUE 1MAXVALUE 9999999999NOCYCLENOORDERCACHE 100;
序列与自增列对比
自增列:与表绑定,只能用于单个表的主键生成
序列:独立于表存在,可跨表使用,支持循环序列(CYCLE)
示例对比:
-- 创建一张含有自增列 id 的表,自增列(increment column)和表强绑定obclient [test]> CREATE TABLE t1(id bigint not null AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));obclient [test]> INSERT INTO t1(name)VALUES('A'),('B'),('C');obclient [test]> SELECT * FROM t1;+----+------+| id | name |+----+------+| 1 | A || 2 | B || 3 | C |+----+------+3rows in set(0.021 sec)-- 创建一个序列,起始值是 1,最小值是 1,最大值是 5,步长是 2,序列的值不循环生成obclient [test]> CREATE SEQUENCE seq1 START WITH 1 MINVALUE 1 MAXVALUE 5 INCREMENT BY 2 NOCYCLE;obclient [test]> SELECT seq1.nextval FROM DUAL;+---------+| nextval |+---------+| 1 |+---------+1row in set(0.012 sec)obclient [test]> SELECT seq1.nextval FROM DUAL;+---------+| nextval |+---------+| 3 |+---------+1row in set(0.004 sec)obclient [test]> SELECT seq1.nextval FROM DUAL;+---------+| nextval |+---------+| 5 |+---------+1row in set(0.004 sec)-- 如果设置 NOCYCLE,达到 MAXVALUE 后,无法继续生成更大的序列obclient [test]> SELECT seq1.nextval FROM DUAL;ERROR 4332 (HY000): sequence exceeds MAXVALUE and cannot be instantiated-- 再创建一个序列,起始值是 1,最小值是 1,最大值是 5,步长是 2,序列的值循环生成(在内存中预分配的自增值个数是 2)obclient [test]> CREATE SEQUENCE seq7 START WITH 1 MINVALUE 1 MAXVALUE 5 INCREMENT BY 2 CYCLE CACHE 2;obclient [test]> SELECT seq7.nextval FROM DUAL;+---------+| nextval |+---------+| 1 |+---------+1row in set(0.009 sec)obclient [test]> SELECT seq7.nextval FROM DUAL;+---------+| nextval |+---------+| 3 |+---------+1row in set(0.005 sec)obclient [test]> SELECT seq7.nextval FROM DUAL;+---------+| nextval |+---------+| 5 |+---------+1row in set(0.005 sec)obclient [test]> SELECT seq7.nextval FROM DUAL;+---------+| nextval |+---------+| 1 |+---------+1row in set(0.001 sec)-- 序列除了可用于顶层 SELECT,还可用在 INSERT 与 UPDATE 中obclient [test]> CREATE TABLE t2(c1 int);obclient [test]> INSERT INTO t2 VALUES(seq7.nextval);obclient [test]> SELECT * FROM t2;+------+| c1 |+------+| 3 |+------+1row in set(0.001 sec)obclient [test]> UPDATE t2 SET c1 = seq7.nextval;obclient [test]> SELECT * FROM t2;+------+| c1 |+------+| 5 |+------+1row in set(0.001 sec)
说明
序列和自增列的区别:
创建序列时,默认为 NOORDER 属性(为了和 Oracle 行为兼容)。
创建自增列时,默认为 ORDER 属性(为了和 MySQL 行为兼容)。
NOORDER 模式跳变原因分析
由于 OceanBase 数据库是分布式数据库,自增机制在高可用切换、合并、宕机等场景下可能出现跳变(Gap),即自增值不连续。
场景 1:多机多分区生成自增值
假设 AUTO_INCREMENT_CACHE_SIZE 的值为 100,当分区表所在的节点 OBServer1、OBServer2 和 OBServer3 分别按以下顺序先后接收到 INSERT INTO VALUES (NULL) 的请求时,它们内部的处理逻辑如下:
OBServer1 发现自身没有缓存时,向内部表申请一段自增区间 [1,100],并且生成一个自增值 1。
OBServer2 发现自身没有缓存时,向内部表申请一段自增区间 [101,200],并且生成一个自增值 101。
OBServer3 发现自身没有缓存时,向内部表申请一段自增区间 [201,300],并且生成一个自增值 201。
OBServer1 使用缓存 [2,100] 生成自增值 2。
OBServer2 使用缓存 [102,200] 生成自增值 102。
……
此时表内插入数据的顺序为 1,101,201,2,102,...,即自增值总是在发生跳变。
场景 2:通过 INSERT 语句插入指定的最大值
在 MySQL 数据库中,如果显式地向自增表中插入指定值,则后续生成的自增值都不会小于该值。如果发现插入了一个区间内的值,会放弃自己的缓存。
在 OceanBase 数据库的分布式场景下,当插入一个指定值且该值比自增表中其他值都大(即最大值)时,不仅 OBServer 节点自身需要知道当前插入了一个最大值,还需要同步给其它 OBServer 节点和内部表,该同步动作非常耗时,为了避免每次指定最大值时都执行同步操作,系统会在插入一个最大值时放弃当前的缓存,这样从当前值开始到下一个缓存值前都不需要再进行同步。
例如,当分区表所在的 OBServer1、OBServer2、OBServer3 分别按以下顺序接收到显式指定递增序列(1, 2, 3, ...)的请求时,并且假设这些机器上均保存了缓存:
OBServer1 接收到值 1,放弃缓存 [1,100],重新从内部表获取到一段新的缓存区间 [301,400],并且把 101 作为一个同步值同步到内部表和其它 OBServer 节点。
OBServer2 接收到值 2,对比发现该值比当前的缓存区间 [101,200] 中的值小,不做操作。
OBServer3 接收到值 3,对比发现该值比当前的缓存区间 [201,300] 中的值小,不做操作。
OBServer1 接收到值 4,对比发现该值比当前的缓存区间 [301,400] 中的值小,不做操作。 ……
这样,如果插入部分值后,继续使用自增列来生成序列,就会发生自增值跳变。例如,OBServer1 的第一个区间 [1,100] 都没有使用而是直接跳到了 301。
除了多机环境,单机环境下,插入指定的最大值时,也会出现自增值跳变的问题。示例如下:
创建一个含自增列的表 t1:
obclient> CREATE TABLE t1(c1 intnot null AUTO_INCREMENT) AUTO_INCREMENT_MODE = 'NOORDER';
同时,AUTO_INCREMENT_CACHE_SIZE 的值为 100。
向该表中多次插入数据:
obclient> INSERT INTO t1 VALUES(NULL);obclient> INSERT INTO t1 VALUES(3);obclient> INSERT INTO t1 VALUES(NULL);
插入成功后,查看表中的数据:
obclient> SELECT * FROM t1;
查询结果如下:
+-----+| c1 |+-----+| 1 || 3 || 101 |+-----+
根据查询结果,发现自增列从 3 跳到了 101。
场景 3:机器重启或宕机
自增列的缓存是一个内存结构,如果 OBServer 节点的机器发生了重启或宕机,该机器上未使用完的缓存区间不会写回内部表,这就导致未使用的这部分区间不会再被使用。
例如,假设 OBServer1 上初始自增列的缓存区间为 [1,100],并且已经生成了自增值 1 和 2。此时,如果 OBServer1 发生了宕机,重启后,该机器上的缓存区间就变成了新的区间 [101,200],同时下一次的自增值为 101,最终自增值的顺序就是 1,2,101,...,即发生了跳变。
ORDER 模式跳变原因分析
为了解决 NOORDER 模式下的自增值跳变问题,OceanBase 数据库在 V4.x 版本中引入了 ORDER 模式。ORDER 模式更好地兼容 MySQL 数据库。它能避免多机多分区生成自增值时的跳变问题。也能避免通过 INSERT 语句插入指定最大值时的跳变问题。
对于 ORDER 模式的自增列,虽然解决了多机多分区生成自增值和通过 INSERT 语句插入指定的最大值等场景下自增值跳变的问题,但是在作为 Leader 的 OBServer 节点的机器重启或宕机、发生切主的场景下,仍然会发生自增值的跳变。
场景 1:机器重启或宕机
在 ORDER 模式中,作为 Leader 的 OBServer 节点上保存了内存下的缓存区间,当作为 Leader 的 OBServer 节点的机器发生重启或宕机时,该区间内未使用的自增值不会被继续使用,而是使用新的缓存区间,从而导致自增值发生跳变。
说明
该场景下,仅作为 Leader 的 OBServer 节点发生重启或宕机时,才会发生自增值跳变的问题,其他作为 Follower 的 OBServer 节点由于不保存缓存,即使发生了宕机也不会影响自增值生成的连续性。
场景 2:切主
假设 OBServer2 上的初始自增列的缓存区间为 [1,100],并且已经生成了自增值 1 和 2,当集群内发生切主时,按照常规处理逻辑:
切主到 OBServer1,OBServer1 从内部表申请一段新的自增区间 [101,200],继续生成自增值 101 和 102。
OBServer2 机器重启成功后,再次切回到 OBServer2,继续用上一次的缓存区间 [3,100],生成自增值 3 和 4。
由此可知,从 101 到 3 发生了自增值不递增的问题。
为了避免上述来回切主从而导致的自增值不递增的问题,OceanBase 数据库会在切主时,将原 Leader 的 OBServer 节点上的缓存区间清理掉,从而导致自增值发生了跳变。
不同模式下的跳变行为
序列跳变原因
序列的跳变主要来自 CACHE 机制和 ORDER 模式:
CACHE 导致跳变:当设置 CACHE 100,系统预分配 100 个值。若节点宕机,未使用的值将丢失,重启后从下一个缓存段开始,导致跳变。
NOORDER 模式下并发跳变:多个会话同时获取 NEXTVAL,不保证顺序,可能乱序返回。
ORDER 模式保证顺序但牺牲性能:使用 ORDER 可确保序列严格递增,但需加锁协调,影响并发性能。
缓存(CACHE)设置建议
说明
若业务不能容忍跳变,可关闭缓存(NOCACHE),但需接受性能下降。
序列在 V4.2.3 后支持 order + cache,也可以避免跳变。
排序(ORDER)设置建议
注意
ORDER 模式需跨节点协调,成本高,仅在金融等强有序场景使用。
数据类型选择建议
何时需要将自增列数据类型设置为 BIGINT?
-
客户数据增长快,保留数据周期长。
-
客户不在意建表使用 BIGINT 还是 INT。
-
Leader 打散,机器切主的概率变大(宕机、random 负载均衡等),跳变的概率变大。
-
NOORDER 模式,需要显式指定自增列值。
何时允许自增列数据类型保持为 INT?
-
业务数据量本身远小于 INT 上限。
-
从 MySQL 迁移过来,业务必须使用 INT 类型,否则应用存在兼容性问题。
-
单机场景,切主的场景很少。
-
有一定的监控运维能力,自增值个数接近上限时进行运维处理,如重建表导数、或 INT 改 BIGINT(V4.2.2 开始 online)等。
模式选择建议
何时可以将自增列改为 NOORDER?
当用户无自增列表级有序需求,并期望优化高并发操作的性能时,可以将 ORDER 修改为 NOORDER。
当用户有自增列表级有序需求,但是 Leader 都在一个 OBServer 上时,且期望优化高并发操作的性能时,可以将 ORDER 修改为 NOORDER。
说明
在单机版本中,ORDER 和 NOORDER 模式的性能差异不明显。然而,在多机(多分区)场景下,NOORDER 模式在高并发情况下的性能优于 ORDER 模式。
何时可以改小自增列 CACHE?
跳变问题比较突出。
业务流量很低。
性能不敏感。
性能要求有一点高,但是为单机模式,Leader 集中在 1 个节点。
相关配置参数
GLOBAL 系统变量
租户级配置项
表 OPTION
性能优化建议
如果基于性能开销考虑,创建序列时需要如何设置相关属性?
创建序列时,如果设置 ORDER 属性,为了保证全局有序,每一次获取 NEXTVALUE 的操作都需要到中心节点去更新一张特定的内部表,在高并发场景下,可能会存在较高的锁冲突。如果不要求序列值递增,只要求唯一,建议将序列的属性设置为 NOORDER。
同时,对性能要求较高时,还应该关注 CACHE / NOCACHE 这个属性。
-
NOCACHE:表示 OBServer 内不缓存自增值。这种模式下每次调用 NEXTVAL 都会触发一次内部表 SELECT 与 UPDATE,会影响数据库的性能。
-
CACHE:用来指定每个 OBServer 内存中缓存的自增值个数,默认值为 20。
说明
在创建序列时,由于默认的 CACHE 值过小,需要手动声明。单机 TPS 为 100 时,CACHE SIZE 建议设置为 360000。
您也可以使用 ORDER + CACHE,每隔 CACHE 个值会操作内部表,可以避免跳变。
CACHE SIZE 表级指定
当前用户可以通过配置 AUTO_INCREMENT_CACHE_SIZE 来控制内存中自增列一次申请缓存的个数。CACHE SIZE 通常设置越大,性能会越好,但是如果跳变次数多,很可能快速导致自增列耗尽,从而开始报错主键冲突。


