OceanBase v4.2 及以上版本拥有简洁、高性能、批量、随机数据 的插入功能。
背景
在实践中发现,功能测试、压力测试、PoC 等场景下都会涉及到随机数据生成,OceanBasev4.2 之前的版本存在两类问题!
- 随机函数种类少,不支持数据分布控制,需要手写 UDF 或 PL 包。
- 多行数据生成时,需要用 CONNECT BY 或 CTE,它们不仅语法复杂,而且数据行数较多时存在性能问题
下面用两个场景来说明我们亟需更好用的接口。
场景一:OceanBase 测试
OceanBase 拥有大量的 mysqltest 测试用例,但这些用例中创建的表一般都不超过百行数据,导致一些潜在场景覆盖不到。为了增加覆盖率,需要给表中灌入更多数据,但在 v4.2 版之前这并不是一件容易事:
- insert into values 方法手工构造 values 很费劲,有多少行数据就要构造多少组值。
- insert into select 方法构造多行数据需要使用复杂的语法,并且性能不高,导致很少有工程师使用。
- 需要测试数据倾斜场景时,必须手工构造倾斜值,最后设计出来的 case 倾斜值的 NDV 大部分都是1、2 或者3,测试效果大打折扣。
- 需要测试长字符串场景时,只能使用repeat、lpad、rpad 这类函数来构造长字符串,这些方法构造出来的字符串很有规律,通过存储层1z、zstd 等压缩算法处理后占用空间会很小,也可能导致测试效果不尽人意。
场景二:OceanBasePoC
通过 CTE 法和 CONNECT BY 法生成 1000 万行数据插入到数据库中,性能都不理想,只能通过“手工倍增法”

为了让传统 MySQL 客户快速的体验 OceanBase 极速的性能,可以在 QuickStart 中让他构建一个十万行的表来体验极速查询性能。构建十万行数据,无论是insert into values 方法,还是“手工倍增法”,体验都很糟糕。
OceanBase v4.2 提供了全新的多行数据导入功能,彻底解决了上述痛点。它包含如下特性:
1.简洁易记的导数语法。
2.支持任意长度的随机字符串生成函数。
3.支持分布函数,轻松构造倾斜数据。
4.Oracle 模式下引入原生内置随机函数,解决 PL 包性能不足问题。
OceanBase v4.2 随机行数据生成方法
随机数
为 MVSQL 和 Oracle 模式统一增加了一套原生函数,提供完善的功能和最好的性能。
1.无论 MySQL 还是 Oracle 模式,都增加同名函数,丰富了函数种类。
2.无论 MySQL 还是 Oracle 模式,都提供原生内置函数,性能最优。
3.随机函数支持传入种子值,使得随机序列可复现,对测试友好。



随机函数部分,在已有的 rand()浮点随机数函数基础上,引入了直接生成整数值的random()函数,直接生成随机字符串的 randstr()函数。同时,还引入了normal、unifomm、zipf 等几个分布控制函数,这使得能轻松控制生成数据的分布规律。
关于生成器表达式是一个比较新的概念,特别说明如下:
每个随机分布函数都需要一个生成器表达式(gen)作为其最后一个参数。生成器表达式可以是常量或变量:
。如果是常量,则随机分布函数的结果是常量。
。如果是变量,则随机分布函数的结果是可变的。任何可转换为64位整数的表达式都可以用作生成器表达式
。任何随机分布函数的随机性都直接与其生成器表达式的随机性相关。对于大多数实际目的random()函数是随机生成整数值的最佳选择。
由数据生成函数生成的序列不能保证有序且没有间隙。这是因为数字可能会以并行的方式、不同步地生成。
行数据生成
Table function 是一种在 SQL 语言中使用的函数,它能够返回一张数据表作为结果。与传统的 SQL 函数只能返回标量值不同,table function 可以返回多行、多列的数据集。新增 generator 函数,并允许在 table function 中调用它,最终返回 N 行数据。语法为:table(qenerator(N));
N 是一个大于等于 0的 64 位正整数。

table generator 也可以和其它表做 join:


性能评测
在 OceanBase 中,对比了 Connect By、Recursive CTE 和 Table Generator 生成行数据性能,每行包含一列整数。生成 1000 万行数据,Table Generator 只需 2 秒,完全满足日常需求。

生成数据示例
有主键表随机数据生成
推荐搭配 sequence 对象(OB 的 MySQL 模式也支持 Sequence 功能)

Note:为了尽可能提高生成数据的性能,sequence cache 大小不要低于 100 万。
千万行级别的随机数据生成
推荐配合使用 OceanBase 4.1推出“旁路导入”功能,以获得最高的性能。只需要添加 append enable parallel dml parallel(8)的 hint 即可,此处使用了并行度 8:

Note:考虑到 OceanBase 4.2 版本旁路导入的最佳实践,建议用一条 insert 语句完成单表全部数据插入,不要拆成多条 insert 来做。
生成包含多个宏块的数据
为了测试包含多个宏块的场景,需要插入大量的数据。但是偶尔会发现,即使插入了大量行OceanBase 凭借其强大的压缩能力,把这些数据都给压缩没了。即使插入了数十万行,还装不满一个宏块。
Oracle 模式下为了解决这个问题,可以在建表时加上 NOCOMPRESS 属性,这样,插入很少的数据就能装满一个宏块。例如:

MySQL 模式下没有 NOCOMPRESS 选项,可以使用 randstr() 来生成足够长的随机串避免压缩,

测试并行执行场景推荐使用本方法,有助于提前暴露数据切分相关问题。
倾斜数据生成
让数据符合正态分布或 zipf 分布,能构造出数据倾斜。例如下面随机生成 20 行数据zipf 分布可以让小数字出现的频率更高:

Note: zipf 生成的数字的分布的特点是小数字出现频率高,大数字出现频率低。
长短不一的字符串生成

批量插入单词
一些场景下,希望插入的字符串有一定规律,不要长得像乱码。比如,插入的内容是字典里的单词。可以通过预先构造一个单词表解决这个问题:

插入部分 null 值
在数据集中掺入 null 值,常能有效暴露一些潜在 bug。MySQL 模式中可以用 if 来实现在随机数中掺null,Oracle 模式下,可以用 decode 来实现。下面的例子里,都以10% 的概率生成 null 值:


在回归测试生成稳定的随机数据
一些测试要求数据必须稳定,否则每次回归的结果都不一样。只需要传入一个常数种子(seed)到随机函数中就可以保证每次插入到表中的数据是一样的。所谓 seed 就是给random() 函数传入一个任意的常量值,seed 相同,每次执行输出的结果都相同。例如下面的例子中,3就是 seed。

加速数据插入
配合并行DML(PDML)可以加速数据插入速度:

如果没有事务要求,也可以搭配上旁路导入功能,导数性能可以更高:

Note:OceanBase v4.2 版本的旁路导入功能还不支持事务,在未来版本里会添加事务支持。
附录:OceanBase 老版本随机数据生成方法
随机数
随机数生成提供了下列方法:



可以看到:
1.MySQL 模式下随机函数种类太少(云平台客户大部分使用的是 MySQL 模式)
2.虽然 Oracle 包提供的随机函数是比较丰富的,但是目前因为实现缘故,在大批量数据插入场景使用 DBMS RANDOM 包有比较大的性能开销。
行数据生成
为了生成 1000 行数据,老版本的 OceanBase 使用如下方法:


可以看到:
1.语法的确是比较复杂,记起来不容易。
2.两个方法的实现性能目前都不太好。
实验:使用随机数据快速生成测试数据
OceanBase 数据库 V4.2.1
随机函数、Table generator 语法同时适用于 MySQL 模式和 Oracle 模式。
随机数
OceanBase V4.2 版本增加了一套内置函数,提供完善的功能和最好的性能。
随机函数
函数名称 | 描述 |
RANDOM([N]) | 随机生成一个 64 位整数。N 是整数,为随机种子,可选。 |
RANDSTR(N, gen) | 随机生成长度为 N 的字符串,gen 为随机方法,可选值为:
此种组合可以有效控制字符串的分布方式,更有效地模拟用户场景。 |
分布控制
函数名称 | 描述 |
NORMAL(<mean>, <stddev> , <gen>) | 正态分布(高斯分布),返回一个符合正态分布(normal distribution,又称高斯分布)的浮点数。 |
UNIFORM(<min> , <max> , <gen>) | 均匀分布,返回一个符合均匀分布(uniform distribution)的整数或浮点数。 |
ZIPF(<s> , <N> , <gen>) | 齐夫分布,返回一个符合齐夫分布(zipf distribution)的整数。 |
行数据生成
Table function 是一种在 SQL 语言中使用的函数,它能够返回一张数据表作为结果。与传统的 SQL 函数只能返回标量值不同,table function 可以返回多行、多列的数据集。新增 generator 函数,并允许在 table function 中调用它,最终返回 N 行数据。
函数语法为:
table(generator(N));说明:N 是一个大于等于 0 的 64 位正整数。
语法结构:
select [随机函数] from table(generator(N));连接数据库
使用 test 用户登录
obclient -h127.0.0.1 -utest@sys -P2881 -Dtestdb -A
--参数说明
testdb 数据库已预建完成,不需要手动创建。
test 用户拥有管理权限,已预授权。生成有主键表随机数据
推荐搭配 sequence 对象一起使用:
创建测试表 t1
obclient [testdb]> create table t1 (c1 bigint primary key, c2 bigint);创建 sequence 对象
create sequence s1 cache 1000000 noorder;
说明:为了尽可能提高生成数据的性能,sequence cache 大小不要低于 100 万。插入随机数
insert into t1 select s1.nextval, random() from table(generator(1000));查看所生成的随机数
select * from t1;返回结果如下:
+------+----------------------+
| c1 | c2 |
+------+----------------------+
| 1 | -7631875434627638408 |
| 2 | 4008152446477750061 |
| 3 | -2275109593681479463 |
| ... | ...... |
| 998 | 4165316234861708796 |
| 999 | 114129515204635614 |
| 1000 | -3607988344825280313 |
+------+----------------------+
1000 rows in set千万行级别的随机数据生成
推荐配合使用 OceanBase V4.1 推出旁路导入功能,以获得最高的性能。只需要添加append enable_parallel_dml parallel(8) hint 即可,此处使用了并行度 8:
设置 ob_query_timeout 的值
set ob_query_timeout = 1000000000;创建表 t1
drop table if exists t1;
create table t1 (c1 bigint, c2 varchar(10));生成千万行级别的随机数据
insert /*+ append enable_parallel_dml parallel(8) */ into t1 select random(), randstr(10, random()) from table(generator(10000000));注意:考虑到 OceanBase V4.2 版本旁路导入的最佳实践,建议用一条 insert 语句完成单表全部数据插入,不要拆成多条 insert 来做。
查看所生成的随机数
obclient [testdb]> select count(*) from t1;返回结果如下:
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+
1 row in set生成包含多个宏块的数据
为了测试包含多个宏块的场景,需要插入大量的数据。但是偶尔会发现,即使插入了大量行,OceanBase 凭借其强大的压缩能力,把这些数据都给压缩没了。即使插入了数十万行,还装不满一个宏块。 MySQL 模式下可以使用 randstr() 来生成足够长的随机串避免压缩后数据量过少。
创建表 t1
drop table if exists t1;
create table t1 (c1 bigint, c2 varchar(10000));生成宏块数据
insert into t1 select random(), randstr(1000, random()) from table(generator(1));查看所生成的随机数
select * from t1;返回结果如下:
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| c1 | c2 |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 5727839101725986866 | l6X09KVyJmFWHeFSv4d4R8p0p0Je3gTQ3sFKJsrqNm18rkfA9sbahGxOvuBEjwJo92pqD0tsP6zyFgZOpMrcxCd2NMlgbuRIfOBKXmBi9EtEDUFchKniliZ6TGhYnitUNe36hYh8ZONoBUPWbMdyNm5spIf09s94JKF4p6vupi18LOJmdsd6nqriByFk9I9odoPklQp0N8TCbAbGBAzOhc38be1678LarIJE3ynQnUHeZETulOtkXCJeJcX01wZMZqbAzUjIvMxibKVeBYXgP0nyjsL0VaFChMtub6vorI3CjQneNOxGDo1oXKt81K9ELYpKXCHGpYzUj0x4ZeJAVi52dmd8hkbgl2vsJc12NOFa7oxEDodgZEVa5m1KPkLyZElc3SveBkB85sLWtCxeT4HUHmRYfadSTQrEbADgBWDGXi1OX2FYvSpqh4FKns9gRkf0beZmXQfkB2TSDMTQLq5u9klipa5Gf6diJyBidyr0JybKB2bwv4V0bqHORy3wbMBspgfO1Y5y50lo1qVIbItYD65c7gBWp8jSpe5iXSbEFypkPI3Mt4byRCPEzi3sBwZAr25kDibuNy34Lq5YDGRyveRkxOf0n63A1MdeHIHmfYFqziPuZ2fAVoN8dMZ0tmTKJYHcDsnC5WfCj2n2jy1KFqL4Jgl41252Hmz2fAJ2L21MDkz8ByjQToTo3efSri7GJeBKbYtupgVeDcj07GTKhWp8b0BYNYlgR4ny5ObsdKlSpKf2TSLAbmRUPmlmh4DSTIBaTkRgb8vMJKRGrQ1496NsjKBmFeZKvuDuJgTcJiZs9uhEhSZIXsZuTSr6dcvkxqDgPyfczwjm3I181CXC9OzoDCVabyPeZQxsjItAh6zqxSxkbYvCZkbMLwR0Fi1cLEhQVC7M5iLuzG3Q1sFqdsFwtUNIpYlojGBOB2DsbmD0VMl8ToTmNWnWjwVWlAd0nsZ0Ny10PYZC7MbStI1i5K5iHkVmnwZULeXW |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.002 sec)倾斜数据生成
可以让数据符合正态分布或 zipf 分布,这样就能构造出数据倾斜。
例如下面随机生成 20 行数据,zipf 分布可以让小数字出现的频率更高:
创建表 t1
drop table if exists t1;
create table t1 (c1 bigint);生成倾斜数据的随机数据
insert into t1 select zipf(1, 20, random()) from table(generator(20));查看所生成的随机数
select * from t1;返回结果如下:
+------+
| c1 |
+------+
| 8 |
| 5 |
| 18 |
| 14 |
| 0 |
| 0 |
| 0 |
| 2 |
| 17 |
| 9 |
| 6 |
| 0 |
| 1 |
| 5 |
| 1 |
| 4 |
| 12 |
| 12 |
| 1 |
| 19 |
+------+
20 rows in set (0.004 sec)注意:zipf 生成的数字的分布的特点是小数字出现频率高,大数字出现频率低。
长短不一的字符串生成
创建表 t1
drop table if exists t1;
create table t1 (c1 varchar(100));生成长短不一的随机数据
insert into t1 select randstr(1+zipf(1, 20, random()), random()) from table(generator(20));查看所生成的随机数
select * from t1;返回结果如下:
+----------------------+
| c1 |
+----------------------+
| A |
| nm |
| 9WH6 |
| tK7m72 |
| Q |
| fs |
| 3kN2bOdeZsrGJGt |
| for |
| Z21Kj |
| BEB |
| XqBON |
| Lc5 |
| e |
| i |
| 5YjkDI |
| P |
| pi1gfUvaHyj |
| nyJWX |
| 5QlYlMlIv |
| FmxqZgVUtUZ8V05kP4Tg |
+----------------------+
20 rows in set (0.002 sec)批量插入单词
一些场景下,希望插入的字符串有一定规律,不要长得像乱码。
比如,插入的内容是字典里的单词。可以通过预先构造一个单词表解决这个问题:
创建表 t1,t2
drop table if exists t1;
create table t1 (c1 int, c2 varchar(10));
create table t2 (c1 varchar(10));构建单词表 t1
insert into t1 values (0, 'hello'), (1, 'world'), (2, 'movie');插入单词
insert /*+ parallel(3) enable_parallel_dml */ into t2 select b.c2 from table(generator(1000)) a, t1 b where b.c1 = random() % 3;说明:由于上一步中插入单词时使用了动态随机筛选条件(random() % 3),导致查询出的表数据行数可能会有差异,请以实际查询结果为准。
查询所生成的数据
select * from t2;返回结果如下:
+-------+
| c1 |
+-------+
| hello |
| hello |
| hello |
| ..... |
| movie |
| movie |
| movie |
+-------+
664 rows in set插入部分 NULL 值
在数据集中掺入 NULL 值,常能有效暴露一些潜在 bug。MySQL 模式中可以用 if 来实现在随机数中掺 NULL,Oracle 模式下,可以用 decode 来实现。下面的例子里,都以 10% 的概率生成 NULL 值:
创建表 t1
drop table if exists t1;
create table t1 (c1 varchar(100));生成随机数据
insert into t1 select if(random(4) % 10 = 0, null, random(4)) from table(generator(10));查看所生成的随机数
select * from t1;返回结果如下:
+----------------------+
| c1 |
+----------------------+
| 5267436225003336391 |
| NULL |
| -851690886662571060 |
| 1738617244330437274 |
| -8073957877497551694 |
| 885116094377146851 |
| -8183226488433301506 |
| 6294187330509591201 |
| -8511555461190104804 |
| 4732822798680798032 |
+----------------------+
10 rows in set (0.002 sec)Mysqltest 中如何生成稳定的随机数据
Mysqltest 要求数据必须稳定,否则每次回归的结果都不一样。只需要传入一个常数种子(seed)到随机函数中就可以保证每次插入到表中的数据是一样的。所谓 seed 就是给 random() 函数传入一个任意的常量值,seed 相同,每次执行输出的结果都相同。例如下面的例子中,3 就是 seed。
创建表 t1
drop table if exists t1;
create table t1 (c1 bigint);创建表 t2
drop table if exists t2;
create table t2 (c1 bigint);往 t1,t2 表内生成随机数据
insert into t1 select random(3) from table(generator(10));
insert into t2 select random(3) from table(generator(10));查看 t1 表数据
select * from t1;返回结果如下:
+----------------------+
| c1 |
+----------------------+
| 1084041170817055659 |
| -5612168153867183641 |
| 1664657641377715667 |
| -2833993413536137579 |
| 1103034804049852293 |
| -2558513787582595740 |
| 4376380862814081111 |
| -1426722524934308120 |
| 3776508982995411530 |
| -6158710767394100221 |
+----------------------+
10 rows in set (0.002 sec)查看 t2 表数据
select * from t2;返回结果如下:
+----------------------+
| c1 |
+----------------------+
| 1084041170817055659 |
| -5612168153867183641 |
| 1664657641377715667 |
| -2833993413536137579 |
| 1103034804049852293 |
| -2558513787582595740 |
| 4376380862814081111 |
| -1426722524934308120 |
| 3776508982995411530 |
| -6158710767394100221 |
+----------------------+
10 rows in set (0.002 sec)



