首页 > 数据库 > 国内数据库 > OceanBase 自增列进化史及最佳实践
建议大家选择感兴趣的部分进行阅读。
也欢迎大家在评论区留言,对本文的内容进行批评和指正~
OceanBase 的 MySQL 模式下,自增列支持指定两种不同的自增模式,可以通过组户级配置项 default_auto_increment_mode [2] 控制默认模式,也可在建表时指定 auto_increment_mode。默认为 order。
INTEGER 列类型增长支持 Online 方式:
对于主键列 / 分区键 / 索引列 / 被生成列依赖的列 / 有 Check 约束的列,列类型如果为整型,当列类型修改为取值范围更大的整形列类型时(如 INT -> BIGINT),在 V4.2.1 版本中是通过双表双写的 Offline DDL 实现,转换过程中会加表锁,阻塞读写。
但从 V4.2.2 版本开始,将 Offline DDL 改进为 Online DDL,整型列类型增长将不再影响业务写入。
当减小一个表的自增字段的值时,需注意以下情况:
如果表中已存在数据并且自增列中的最大值不小于新指定的
AUTO_INCREMENT值时,新的
AUTO_INCREMENT值将自动调整为表中自增列现有最大值的下一个取值。
例如,自增列当前的最大值为 5,当前
AUTO_INCREMENT的值是 8,而
AUTO_INCREMENT设置为介于 0 到 6 之间的任何值,语句执行成功后,实际的
AUTO_INCREMENT值都会被调整为 6。和原生 MySQL 行为兼容。
在 4.2.3 之前的版本,所有表的 cache size 都会以 auto_increment_cache_size [3] 设置的值作为每个节点缓存的自增值的个数。
为了灵活控制不同表采用不同的缓存策略,新增 auto_increment_cache_size 表级 option,可以在建表/修改表时指定某张表的自增值 cache size。
| 场景 | NOORDER 模式(最佳性能) | ORDER 模式(最大兼容) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| GLOBAL 系统变量 | 含义 | 默认值 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
| 租户级配置项 | 含义 | 默认值 |
|---|---|---|
|
|
|
|
| 表 option | 含义 | 默认值 |
|---|---|---|
|
|
|
|
|
|
|
|
| SQL MODE | 含义 | 默认值 |
|---|---|---|
|
|
|
|
OceanBase MySQL 模式下的自增列,设计行为尽可能和 MySQL 兼容,对用户提供了如下特性:
单机数据库(例如 MySQL)中的自增列,直接移植到分布式数据库(例如 OceanBase)中后,在用的过程中,会观测到 “跳” 的情况。
以下内容为 OceanBase 自增列跳变原理,感兴趣的朋友可以选择性地进行阅读。
内容出自: OceanBase 官网文档 《自增列的跳变》 [4]。
在 MySQL 数据库中,自增列是数据库表中的一种列属性,它可以自动生成一个唯一的、递增的值,用于表示该行数据的唯一标识。OceanBase 数据库作为分布式数据库,其数据库表通常分布在多台不同的机器上,在尽可能与 MySQL 数据库兼容的同时还需要保证分布式多机场景下自增列生成的性能,从而会出现自增值生成过程中的跳变问题。
在 OceanBase 数据库中,自增列支持两种自增模式,即 NOORDER 模式和 ORDER 模式,默认为 ORDER 模式。其中:
下面从这两个模式来分别介绍自增值在生成过程中是如何发生跳变的。
OceanBase 数据库 V4.x 版本中通过指定
AUTO_INCREMENT_MODE = 'NOORDER' 创建的表,以及 V4.0.0(不含该版本)以下版本中创建的所有含自增列的表,均为 NOORDER 模式的自增表。
对于 NOORDER 模式的自增列,其内部原理如下图所示。
从上图可知,NOORDER 模式的自增列,其数据结构分为以下两部分:
NOORDER 模式的自增列中的每一个 OBServer 节点之间均保持独立,各节点可以自主从内部表获取自增区间记录到机器缓存中,从而加速自增值的生成。下面以几个典型场景为例,介绍 NOORDER 模式下自增值出现跳变的原因。
假设
auto_increment_cache_size 的值为 100,当分区表所在的 OBServer 节点 OBServer1、OBServer2 和 OBServer3 分别按以下顺序先后接收到
insert into values (null) 的请求时,它们内部的处理逻辑如下:
……
这样,表内插入数据的顺序就是
1,101,201,2,102,...,可以发现,自增值总是在发生跳变。
INSERT 语句插入指定的最大值
在 MySQL 数据库中,如果显式地向自增表中插入指定值,则后续生成的自增值都不会小于该值。
在 OceanBase 数据库的分布式场景下,当插入一个指定值且该值比自增表中其他值都大(即最大值)时,不仅 OBServer 节点自身需要知道当前插入了一个最大值,还需要同步给其它 OBServer 节点和内部表,该同步动作非常耗时,为了避免每次指定最大值时都执行同步操作,系统会在插入一个最大值时放弃当前的缓存,这样从当前值开始到下一个缓存值前都不需要再进行同步。
例如,当分区表所在的 OBServer1、OBServer2、OBServer3 分别按以下顺序先后接收到显式指定递增序列(
1, 2, 3, ...)的请求时,并且假设这些机器上均保存了缓存:
这样,如果插入部分值后,继续使用自增列来生成序列,就会发生自增值跳变。例如,OBServer1 的第一个区间 [1,100] 都没有使用而是直接跳到了 301。
除了多机环境,单机环境下,插入指定的最大值时,也会出现自增值跳变的问题。示例如下:
t1。
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。
自增列的缓存是一个内存结构,如果 OBServer 节点的机器发生了重启或宕机,该机器上未使用完的缓存区间不会写回内部表,这就导致未使用的这部分区间不会再被使用。例如,假设 OBServer1 上初始自增列的缓存区间为 [1,100],并且已经生成了自增值 1 和 2。此时,如果 OBServer1 发生了宕机,重启后,该机器上的缓存区间就变成了新的区间 [101,200],同时下一次的自增值为 101,最终自增值的顺序就是
1,2,101,...,即发生了跳变。
为了避免 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) 的请求时,它们内部的处理逻辑如下:
……
由此可见,ORDER 模式下,由于所有 OBServer 节点都从 Leader 处申请自增值,所以大部分情况下与单机场景一样,系统总是能生成连续的自增值序列。但是,在并发较高的多机场景,ORDER 模式的性能会比 NOORDER 模式差。
对于 ORDER 模式的自增列,虽然已经解决了
多机多分区生成自增值 和
通过
INSERT
语句插入指定的最大值 等场景下自增值跳变的问题,但是在作为 Leader 的 OBServer 节点的机器重启或宕机、发生切主的场景下,仍然会发生自增值的跳变。
在 ORDER 模式中,作为 Leader 的 OBServer 节点上保存了内存下的缓存区间,当作为 Leader 的 OBServer 节点的机器发生重启或宕机时,该区间内未使用的自增值不会被继续使用,而是使用新的缓存区间,从而导致自增值发生跳变。
注意
该场景下,仅作为 Leader 的 OBServer 节点发生重启或宕机时,才会发生自增值跳变的问题,其他作为 Follower 的 OBServer 节点由于不保存缓存,即使发生了宕机也不会影响自增值生成的连续性。
假设 OBServer2 上的初始自增列的缓存区间为 [1,100],并且已经生成了自增值 1 和 2,当集群内发生切主时,按照常规处理逻辑:
由此可知,从 101 到 3 发生了自增值不递增的问题。
为了避免上述来回切主从而导致的自增值不递增的问题,OceanBase 数据库会在切主时,将原 Leader 的 OBServer 节点上的缓存区间清理掉,从而导致自增值发生了跳变。
在 OceanBase 数据库中,序列(Sequence)是数据库按照一定规则生成的唯一且通常是递增的数值。通常被用于生成唯一标识符。
OceanBase 存在很多用户,业务原本跑在 DB2 / Oracle 上,但是后续准备选择 MySQL 的技术路线,需要从 DB2 / Oracle 向 Oceanbase MySQL 模式的租户进行迁移。
为了降低客户对之前在 DB2 / Oracle 中大量使用 sequence 的业务进行改造的复杂度,OceanBase 在 MySQL 模式下支持了和 Oracle 行为兼容的 sequence 功能。
详见 OceanBase 官网文档《创建和管理序列》 [5]章节,语法和 Oracle 保持兼容,本文中不再赘述。
-- 创建一张含有自增列 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 这个属性。
说明:
在创建序列时,由于默认的 CACHE 值过小,需要手动声明。单机 TPS 为 100 时,CACHE SIZE 建议设置为 360000。
自增列: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000003379529
[2]default_auto_increment_mode: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000002015589
[3]auto_increment_cache_size: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000003381414
[4]OceanBase 官网文档 《自增列的跳变》: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001431031
[5]OceanBase 官网文档《创建和管理序列》: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000641880
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70047136/viewspace-3091855/ ,如需转载,请注明出处,否则将追究法律责任。