如果创建表时需要某个数值列的值不重复并且保持递增,这就是自增列。
自增列的列类型需要被定义为 AUTO_INCREMENT。
本文内容大纲
- OceanBase 中自增列的进化史(Evolutionary History)
- 使用 OceanBase 自增列的最佳实践(Best Practice)
- 附录
- 自增列跳变之谜(Rethinking in Auto Increment)
- MySQL 模式下的扩展功能 —— 序列(Sequence)
建议大家选择感兴趣的部分进行阅读。
也欢迎大家在评论区留言,对本文的内容进行批评和指正~
OceanBase 中自增列的进化史(Evolutionary History)
4.0.0 版本
OceanBase 的 MySQL 模式下,自增列支持指定两种不同的自增模式,可以通过组户级配置项 default_auto_increment_mode 控制默认模式,也可在建表时指定 auto_increment_mode。默认为 order。
- ORDER:底层是基于集中缓存的自增列,自增列全局递增,更好地兼容 MySQL 行为。
- NOORDER:分布式缓存的自增列,只保证全局唯一,分区表拥有更好的性能(只保证分区内自增,不保证全局自增)。
4.2.2 版本
INTEGER 列类型增长支持 Online 方式:
对于主键列 / 分区键 / 索引列 / 被生成列依赖的列 / 有 Check 约束的列,列类型如果为整型,当列类型修改为取值范围更大的整形列类型时(如 INT -> BIGINT),在 V4.2.1 版本中是通过双表双写的 Offline DDL 实现,转换过程中会加表锁,阻塞读写。
但从 V4.2.2 版本开始,将 Offline DDL 改进为 Online DDL,整型列类型增长将不再影响业务写入。
4.2.3 及以上版本
自增值起点支持改小
当减小一个表的自增字段的值时,需注意以下情况:
如果表中已存在数据并且自增列中的最大值不小于新指定的 AUTO_INCREMENT值时,新的 AUTO_INCREMENT值将自动调整为表中自增列现有最大值的下一个取值。
例如,自增列当前的最大值为 5,当前 AUTO_INCREMENT的值是 8,而 AUTO_INCREMENT设置为介于 0 到 6 之间的任何值,语句执行成功后,实际的 AUTO_INCREMENT值都会被调整为 6。和原生 MySQL 行为兼容。
自增值 cache size 支持在表级别设置
在 4.2.3 之前的版本,所有表的 cache size 都会以 auto_increment_cache_size 设置的值作为每个节点缓存的自增值的个数。
为了灵活控制不同表采用不同的缓存策略,新增 auto_increment_cache_size 表级 option,可以在建表/修改表时指定某张表的自增值 cache size。
场景NOORDER 模式(最佳性能)ORDER 模式(最大兼容)多机多分区生成自增列值不同机器分别缓存自增区间,表内整体插入的数据顺序会跳变。
但全表自增值不会因跳变而“浪费”。所有的节点都在leader 申请自增值,所以不会跳变,但性能比noorder 差。显式指定自增列值(insert、insert on duplicate、replace)如果显式往自增列插入指定值,插入后会刷新各节点自增值缓存区间,以保证后续生成的自增值都不会小于该值。
存在因缓存刷新导致的自增值跳变和“浪费”。不会跳变。insert on duplicate/replace into 场景不指定自增列早期版本需要刷新全局cache。目前各分支的最新版本都无需再刷新。不会因这个场景特殊跳变。早期版本需要刷新全局cache。目前各分支的最新版本都无需再刷新。不会因这个场景特殊跳变。机器重启/宕机机器重启或宕机后,无法复用宕机前的剩余缓存值区间,需要重新获取。
存在因机器重启/宕机导致的自增值跳变和“浪费”。在leader节点维护了缓存区间,当leader节点发生重启/宕机时,该区间内未使用的自增值不会被继续使用,从而发生跳变和“浪费”。注意,这里跳变的场景仅发生在leader节点,其它follower节点由于不保存缓存,即使宕机也不会影响自增值的生成连续性。主动切主(如升降配、升级OBServer)老版本每次切主时会丢弃当前缓存区间,重新获取。
存在自增值跳变和“浪费”。
4.2.3 版本开始,优化为正常切主不跳变,但需要注意:切主会影响 server 的可用性,刚切主完成部分 insert 请求可能会存在重试的情况,这样可能会有小范围的数据跳跃。老版本发生切主时,会把原始leader的缓存区间清理,从而发生跳变和“浪费”。
4.2.3 版本开始,优化为正常切主不跳变,但需要注意:切主会影响 server 的可用性,刚切主完成部分 insert 请求可能会存在重试的情况,这样可能会有小范围的数据跳跃。使用 OceanBase 自增列的最佳实践(Best Practice)
什么情况下需要将自增列数据类型设置为 bigint?
- 业务本身数据增长快,保留数据周期长。
- 客户不在意建表使用 bigint 还是 int。
- leader 打散,机器切主的概率变大(宕机、random 负载均衡等),跳变的概率变大。
- noorder 模式,需要显式指定自增列值。
什么情况下允许自增列数据类型保持为 int?
- 业务数据量本身远小于 int 上限。
- 从 MySQL 迁移过来,客户坚持使用 int 类型,否则应用存在兼容性问题。
- 单机场景,切主的场景很少。
- 有一定的监控运维能力,自增值个数接近上限时进行运维处理,如重建表导数、或int 改 bigint(4.2.2.开始online)等。
什么情况下可以将自增列改为 noorder?
- 当用户无自增列表级有序需求,并期望优化高并发操作的性能时,可以将 order 修改为 noorder。
- 当用户有自增列表级有序需求,但是 leader 都在一个 OBServer 上时,且期望优化高并发操作的性能时,可以将 order 修改为 noorder。
什么情况下可以改小自增列 cache?
- 跳变问题比较突出。
- 业务流量很低。
- 性能不敏感。
- 性能要求有一点高,但是为单机模式,leader 集中在 1 个节点。
自增列相关配置
GLOBAL 系统变量含义默认值auto_increment_cache_size用于设置缓存的自增值个数4.0 开始默认 100wauto_increment_increment用于设置自增步长默认为 1,也支持 session 级设置auto_increment_offset用于确定自增列的起始值默认为 1,也支持 session 级设置租户级配置项含义默认值default_auto_increment_mode用于设置默认的自增列自增模式
+ order:自增列数据保持连续且递增
+ noorder:自增列只保证自增值唯一4.0 之前,只支持 noorder;
4.0 及之后版本,新增该配置项,开始支持 order 模式,默认也为 order。表 option含义默认值AUTO_INCREMENT_MODE自增列为 order 还是 noorder 模式。未指定时取 default_auto_increment_mode 配置项设置的值。auto_increment_cache_size控制内存中自增列一次申请缓存的个数。未指定时取 auto_increment_cache_size 系统变量设置的值。SQL MODE含义默认值NO_AUTO_VALUE_ON_ZERO指定该 SQL MODE 时,自增列插入 0 设置为 0,而不去取下一个自增值非默认的 SQL MODE附录
自增列跳变之谜(Rethinking in Auto Increment)
OceanBase MySQL 模式下的自增列,设计行为尽可能和 MySQL 兼容,对用户提供了如下特性:
- 向自增列主动插入一个值 i 后,后继这张表上自动生成的值都需要比 i 大。
- 每个分区上自动生成的值,总是单调递增的。
单机数据库(例如 MySQL)中的自增列,直接移植到分布式数据库(例如 OceanBase)中后,在用的过程中,会观测到 “跳” 的情况。
以下内容为 OceanBase 自增列跳变原理,感兴趣的朋友可以选择性地进行阅读。
内容出自: OceanBase 官网文档 “自增列的跳变”。
在 MySQL 数据库中,自增列是数据库表中的一种列属性,它可以自动生成一个唯一的、递增的值,用于表示该行数据的唯一标识。OceanBase 数据库作为分布式数据库,其数据库表通常分布在多台不同的机器上,在尽可能与 MySQL 数据库兼容的同时还需要保证分布式多机场景下自增列生成的性能,从而会出现自增值生成过程中的跳变问题。
在 OceanBase 数据库中,自增列支持两种自增模式,即 NOORDER 模式和 ORDER 模式,默认为 ORDER 模式。其中:
- ORDER 模式:基于集中缓存的自增列。设置为该模式后,自增列的值全局递增。
- NOORDER 模式:基于分布式缓存的自增列,设置为该模式后,仅保证自增列的值全局唯一。
下面从这两个模式来分别介绍自增值在生成过程中是如何发生跳变的。
NOORDER 模式
OceanBase 数据库 V4.x 版本中通过指定 AUTO_INCREMENT_MODE = 'NOORDER' 创建的表,以及 V4.0.0(不含该版本)以下版本中创建的所有含自增列的表,均为 NOORDER 模式的自增表。
对于 NOORDER 模式的自增列,其内部原理如下图所示。
从上图可知,NOORDER 模式的自增列,其数据结构分为以下两部分:
- 内部表:负责持久化当前已经使用的自增值位点。
- 缓存:记录在内部结构中的一段自增值区间,通过向内部表申请获得。
NOORDER 模式的自增列中的每一个 OBServer 节点之间均保持独立,各节点可以自主从内部表获取自增区间记录到机器缓存中,从而加速自增值的生成。下面以几个典型场景为例,介绍 NOORDER 模式下自增值出现跳变的原因。
场景 1:多机多分区生成自增值
假设 auto_increment_cache_size 的值为 100,当分区表所在的 OBServer 节点 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。
除了多机环境,单机环境下,插入指定的最大值时,也会出现自增值跳变的问题。示例如下:
- obclient> CREATE TABLE t1 (c1 int not 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 模式中所提到的 多机多分区生成自增值 和 通过 INSERT 语句插入指定的最大值 等比较常用的使用场景下发生自增值跳变的问题,OceanBase 数据库在 V4.x 版本开始新增了 ORDER 模式的自增列,并且该模式为创建表后的默认模式,能更好地兼容 MySQL 数据库。
对于 ORDER 模式的自增列,其内部原理如下图所示。
与 NOORDER 模式相比,ORDER 模式的自增列会在所有 OBServer 节点中选取当前集群的 Leader 作为自增列服务的 Leader,其它作为 Follower 的 OBServer 节点需要通过发送 RPC 请求来从作为 Leader 的 OBServer 节点处申请自增值,而作为 Leader 的 OBServer 节点会从内部表申请自增区间来作为自增缓存。
例如,同样在多机多分区场景中,假设自增列的 auto_increment_cache_size 为 100,当分区表所在的 OBServer 节点 OBServer1、OBServer2 和 OBServer3 分别按以下顺序先后接收到 insert into values (null) 的请求时,它们内部的处理逻辑如下:
- OBServer1 发现自身不是 Leader,发送 RPC 请求给 OBServer2,OBServer2 从内部表申请一段自增区间 [1,100],并给 OBServer1 返回一个自增值 1。
- OBServer2 发现自身是 Leader,并且存在缓存区间 [2,100],直接生成一个自增值 2。
- OBServer3 发现自身不是 Leader,发送 RPC 请求给 OBServer2,OBServer2 发现存在缓存区间 [3,100],并给 OBServer3 返回一个自增值 3。
……
由此可见,ORDER 模式下,由于所有 OBServer 节点都从 Leader 处申请自增值,所以大部分情况下与单机场景一样,系统总是能生成连续的自增值序列。但是,在并发较高的多机场景,ORDER 模式的性能会比 NOORDER 模式差。
对于 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 节点上的缓存区间清理掉,从而导致自增值发生了跳变。
MySQL 模式下的扩展功能 —— 序列(Sequence)
功能定义
在 OceanBase 数据库中,序列(Sequence)是数据库按照一定规则生成的唯一且通常是递增的数值。通常被用于生成唯一标识符。
引入原因
OceanBase 存在很多用户,业务原本跑在 DB2 / Oracle 上,但是后续准备选择 MySQL 的技术路线,需要从 DB2 / Oracle 向 Oceanbase MySQL 模式的租户进行迁移。
为了降低客户对之前在 DB2 / Oracle 中大量使用 sequence 的业务进行改造的复杂度,OceanBase 在 MySQL 模式下支持了和 Oracle 行为兼容的 sequence 功能。
相关语法
详见 OceanBase 官网中的《创建和管理序列》章节,语法和 Oracle 保持兼容,本文中不再赘述。
适用场景
- 从 DB2 / Oracle 向 Oceanbase MySQL 模式的租户进行迁移的场景。
- 自增列(increment column)和表绑定的特性无法满足业务使用要求。序列(sequence)不和表绑定,可独立创建,也可跨表使用。
- 自增列(increment column)没有 CYCLE 能力,达到 MAXVALUE 后会罢工的特性无法满足业务使用要求。序列(sequence)支持循环序列,有 CYCLE 能力。
常见问题
序列(sequence)和自增列(increment column)的异同是什么?
- 自增列(increment column)和表绑定。序列(sequence)不和表绑定,可独立创建,也可跨表使用。
- 自增列(increment column)没有 CYCLE 能力。序列(sequence)支持循环序列,有 CYCLE 能力。
- -- 创建一张含有自增列 id 的表,自增列(increment column)和表强绑定
- obclient [test]> CREATE TABLE t1(id bigint not null auto_increment primary key, name varchar(50));
- Query OK, 0 rows affected (0.489 sec)
- obclient [test]> INSERT INTO t1(name) VALUES('A'),('B'),('C');
- Query OK, 3 rows affected (0.036 sec)
- obclient [test]> SELECT * FROM t1;
- +----+------+
- | id | name |
- +----+------+
- | 1 | A |
- | 2 | B |
- | 3 | C |
- +----+------+
- 3 rows 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;
- Query OK, 0 rows affected (0.073 sec)
- obclient [test]> SELECT seq1.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 1 |
- +---------+
- 1 row in set (0.012 sec)
- obclient [test]> SELECT seq1.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 3 |
- +---------+
- 1 row in set (0.004 sec)
- obclient [test]> SELECT seq1.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 5 |
- +---------+
- 1 row 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;
- Query OK, 0 rows affected (0.095 sec)
- obclient [test]> SELECT seq7.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 1 |
- +---------+
- 1 row in set (0.009 sec)
- obclient [test]> SELECT seq7.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 3 |
- +---------+
- 1 row in set (0.005 sec)
- obclient [test]> SELECT seq7.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 5 |
- +---------+
- 1 row in set (0.005 sec)
- obclient [test]> SELECT seq7.nextval FROM DUAL;
- +---------+
- | nextval |
- +---------+
- | 1 |
- +---------+
- 1 row in set (0.001 sec)
- -- 序列除了可用于顶层 SELECT,还可用在 INSERT 与 UPDATE 中
- obclient [test]> create table t2(c1 int);
- Query OK, 0 rows affected (0.192 sec)
- obclient [test]> insert into t2 values(seq7.nextval);
- Query OK, 1 row affected (0.009 sec)
- obclient [test]> select * from t2;
- +------+
- | c1 |
- +------+
- | 3 |
- +------+
- 1 row in set (0.001 sec)
- obclient [test]> update t2 set c1 = seq7.nextval;
- Query OK, 1 row affected (0.010 sec)
- Rows matched: 1 Changed: 1 Warnings: 0
- obclient [test]> select * from t2;
- +------+
- | c1 |
- +------+
- | 5 |
- +------+
- 1 row in set (0.001 sec)
复制代码说明:
序列和自增列这里还存在一个区别
- 创建序列时,默认为 NOORDER 属性(为了和 Oracle 行为兼容)。
- 创建自增列时,默认为 ORDER 属性(为了和 MySQL 行为兼容)。
如果基于性能开销考虑,创建序列时需要如何设置相关属性?
创建序列时,如果设置 ORDER 属性,为了保证全局有序,每一次获取 NEXTVALUE 的操作都需要到中心节点去更新一张特定的内部表,在高并发场景下,可能会存在较高的锁冲突。如果不要求序列值递增,只要求唯一,建议将序列的属性设置为 NOORDER。
同时,对性能要求较高时,还应该关注 CACHE / NOCACHE 这个属性。
- NOCACHE:表示 OBServer 内不缓存自增值。这种模式下每次调用 NEXTVAL 都会触发一次内部表 SELECT 与 UPDATE,会影响数据库的性能。
- CACHE:用来指定每个 OBServer 内存中缓存的自增值个数,默认值为 20。
说明:
在创建序列时,由于默认的 CACHE 值过小,需要手动声明。单机 TPS 为 100 时,CACHE SIZE 建议设置为 360000。
最后为大家推荐这个 OceanBase 开源负责人老纪的公众号「老纪的技术唠嗑局」,会持续更新和#数据库、#AI、#技术架构 相关的各种技术内容。欢迎感兴趣的朋友们关注!
「老纪的技术唠嗑局」不仅希望能持续给大家带来有价值的技术分享,也希望能和大家一起为开源社区贡献一份力量。如果你对 OceanBase 开源社区认可,点亮一颗小星星 ✨ 吧!你的每一个Star,都是我们努力的动力。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |