本文分享自华为云社区《GaussDB(DWS)关于sequence的那些事》,作者:Arrow0lf 。
什么是 sequence
sequence,也称作序列,是用来产生唯一整数的数据库对象。序列的值按照一定的规则自增/自减,一般常被用作主键。GaussDB(DWS)中,创建 sequence 时会同时创建一张同名的元数据表,用来记录 sequence 相关的信息,例如:
postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# select * from seq;
sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called | uuid
---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------+---------
seq | -1 | 1 | 1 | 9223372036854775807 | 1 | 1 | 0 | f | f | 1600007
(1 row)
复制代码
其中,sequence_name 表示 sequence 的名字,last_value 当前无意义,start_value 表示 sequence 的初始值,increment_by 表示 sequence 的步长,max_value 表示 sequence 的最大值,min_value 表示最小值,cache_value 表示为了快速获取下一个序列值而预先存储的 sequence 值个数(定义 cache 后不能保证 sequence 值的连续性,会产生空洞,详见下文)。log_cnt 表示 WAL 日志记录的 sequence 值个数,由于在 DWS 中 sequence 是从 GTM 获取和管理,因此 log_cnt 无实际意义;is_cycled 表示 sequence 在达到最小或最大值后是否循环继续,is_called 表示该 sequence 是否已被调用(仅表示在当前实例是否被调用,例如在 cn_5001 上调用之后,cn_5001 上该原数据表的值变为 t,cn_5002 上该字段仍为 f),uuid 代表该 sequence 的唯一标识。
GaussDB(DWS)中,通过 GTM(Global Transaction Manager,名为全局事务管理器)负责生成和维护全局事务 ID、事务快照、Sequence 等需要全局唯一的信息。sequence 在 DWS 中的创建流程如下图所示:
具体过程为:
接受 SQL 命令的 CN 从 GTM 申请 UUID;
GTM 返回一个 UUID;
CN 将拿到的 UUID 与用户创建的 sequenceName 绑定;
CN 将绑定关系下发给其他节点上,其他节点同步创建 sequence 元数据表;
CN 将 UUID 和 sequence 的 startID 发送到 GTM 端,在 GTM 行进行永久保存。
因此,sequence 的维护和申请实际是在 GTM 上完成的。当申请 nextval,每个执行 nextval 调用的实例会根据该 sequence 的 uuid 到 GTM 上申请序列值,每次申请的序列值范围与 cache 有关,只有当 cache 消耗完之后才会继续到 GTM 上申请。因此,增大 sequence 的 cache 有利于减少 CN/DN 与 GTM 通信的次数。接下来,将详细介绍 sequence 在 DWS 中的使用场景和注意事项。
如何创建 sequence
GaussDB(DWS)中,有两种创建 sequence 的方法:
方法一:直接创建 sequence,并通过 nextval 调用,举例:
postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# insert into t_dest select nextval('seq'),* from t_src;
INSERT 0 0
复制代码
方法二:建表时使用 serial 类型,会自动创建一个 sequence,并且会将该列的默认值设置为 nextval,举例:
postgres=# create table test(a int, b serial) distribute by hash(a);
NOTICE: CREATE TABLE will create implicit sequence "test_b_seq" for serial column "test.b"
CREATE TABLE
postgres=#\d+ test
Table "public.test"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+--------------------------------------------------+---------+--------------+-------------
a | integer | | plain | |
b | integer | not null default nextval('test_b_seq'::regclass) | plain | |
Has OIDs: no
Distribute By: HASH(a)
Location Nodes: ALL DATANODES
Options: orientation=row, compression=no
复制代码
本例中,会自动创建一个名为 test_b_seq 的 sequence。其实严格来讲,serial 类型是一个“伪类型”,本质上,serial 其实是 int 类型,只不过在创建时会同时创建一个 sequence,并与该列相关联,本质上,方法二中的例子与下面的写法等价:
postgres=# create table test(a int, b int) distribute by hash(a);
CREATE TABLE
postgres=# create sequence test_b_seq owned by test.b;
CREATE SEQUENCE
postgres=# alter sequence test_b_seq owner to jerry; --jerry为test表的属主,如果当前用户即为属主,可不执行此语句
ALTER SEQUENCE
postgres=# alter table test alter b set default nextval('test_b_seq'), alter b set not null;
ALTER TABLE
postgres=# \d+ test
Table "public.test"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+--------------------------------------------------+---------+--------------+-------------
a | integer | | plain | |
b | integer | not null default nextval('test_b_seq'::regclass) | plain | |
Has OIDs: no
Distribute By: HASH(a)
Location Nodes: ALL DATANODES
Options: orientation=row, compression=no
复制代码
sequence 在业务中的常见用法
sequence 在业务中常被用作在导入时生成主键或唯一列,常见于数据迁移场景。不同的迁移工具或业务导入场景使用的入库方法不同,常见的方法主要可以分为 copy 和 insert。对于 seqeunce 来讲,这两种场景在处理时略有差别。
场景一:insert 下推场景
postgres=# create table test1(a int, b serial) distribute by hash(a);
NOTICE: CREATE TABLE will create implicit sequence "test1_b_seq" for serial column "test1.b"
CREATE TABLE
postgres=#
postgres=# create table test2(a int) distribute by hash(a);
CREATE TABLE
postgres=#
postgres=#
postgres=# explain verbose insert into test1(a) select a from test2;
QUERY PLAN
------------------------------------------------------------------------------------------------
id | operation | E-rows | E-distinct | E-memory | E-width | E-costs
----+------------------------------------+--------+------------+----------+---------+---------
1 | -> Streaming (type: GATHER) | 1 | | | 4 | 18.41
2 | -> Insert on public.test1 | 40 | | | 4 | 18.25
3 | -> Seq Scan on public.test2 | 40 | | 1MB | 4 | 16.24
Targetlist Information (identified by plan id)
---------------------------------------------------------
1 --Streaming (type: GATHER)
Node/s: All datanodes
3 --Seq Scan on public.test2
Output: test2.a, nextval('test1_b_seq'::regclass)
Distribute Key: test2.a
====== Query Summary =====
-------------------------------
System available mem: 4669440KB
Query Max mem: 4669440KB
Query estimated mem: 1024KB
Parser runtime: 0.045 ms
Planner runtime: 12.622 ms
Unique SQL Id: 972921662
(22 rows)
复制代码
由于在 nextval 在 insert 场景下可以下推到 DN 执行,因此,不管是使用 default 值的 nextval,还是显示调用 nextval,nextval 都会被下推到 DN 执行,在上例的执行计划中也能看出,nextval 的调用在 sequence 层,说明是在 DN 执行的。此时,DN 直接向 GTM 申请序列值,且各 DN 并行执行,因此效率相对较高。
场景二:copy 场景
在业务开发过程中,入库方式除了 insert 外,还有 copy 入库的场景。此类场景多见于将文件内容 copy 入库、使用 CopyManager 接口入库等,此外,CDM 数据同步工具,其实现方式也是通过 copy 的方式批量入库。在 copy 入库过程中,如果 copy 的目标表使用了默认值,且默认值为 nextval,处理过程如下:
此场景下,由 CN 负责向 GTM 申请序列值,因此,当 sequence 的 cache 值较小,CN 会频繁和 GTM 建联并申请 nextval,出现性能瓶颈。下面,将针对此种场景说明业务上的性能表现和优化方法。
sequence 相关的典型优化场景
业务场景:某业务场景使用 CDM 数据同步工具做数据迁移,从源端入库目标端 GaussDB(DWS)。导入速率与经验值相差较大,业务将 CDM 并发从 1 调整为 5,同步速率仍无法提升。查看语句执行情况,除 copy 入库外,其余业务均正常执行,无性能瓶颈,且观察无资源瓶颈,因此初步判断为该业务自身存在瓶颈,查看该表 copy 相关的作业等待视图情况:
如图所示,由于 CDM 作业起了 5 个并发,因此在活跃视图中可以看到 5 个 copy 语句,根据这 5 个 copy 语句对应的 query_id 查看等待视图情况如上图所示。可以看到,这 5 个 copy 中,同一时刻,仅有 1 个 copy 在向 GTM 申请序列值,其余的 copy 在等待轻量级锁。因此,即使作业中开启了 5 并发在运行,实际效果比 1 并发并不能带来明显提升。
问题原因:目标表在建表时使用了 serial 类型,默认创建的 sequence 的 cache 为 1,导致在并发 copy 入库时,CN 频繁与 GTM 建联,且多个并发之间存在轻量锁争抢,导致数据同步效率低。
解决方案:此种场景下可以调大 sequence 的 cache 值,防止频繁 GTM 建联带来的瓶颈。本例中,业务每次同步的数据量在 10 万左右,综合其他使用场景评估,将 cache 值修改为 10000(实际使用时应根据业务设置合理的 cache 值,既能保证快速访问,又不会造成序列号浪费)。
当前 GaussDB(DWS)不支持通过 alter sequence 的方式修改 cache 值,那么如何修改已有 sequence 的 cache 值呢?以第二节中方法二的 test 表为例,可以通过如下方式达到修改 cache 的目的:
-- 解除当前sequence与目标表的关联关系
alter sequence test_b_seq owned by none;
alter table test alter b drop default;
-- 记录当前的seqeunce值并删除sequence
select nextval('test_b_seq'); --记录该值,作为新建sequence的start value
drop sequence test_b_seq;
-- 新建seqeunce并绑定目标表
create sequence test_b_seq START with xxx cache 10000 owned by test.b; -- xxx替换为上一步查到的nextval
alter sequence test_b_seq owner to jerry; --jerry为test表的属主,如果当前用户即为属主,可不执行此语句
alter table test alter b set default nextval('test_b_seq'), alter b set not null;
复制代码
参考链接:
https://bbs.huaweicloud.com/blogs/180833
https://bbs.huaweicloud.com/blogs/338904
点击关注,第一时间了解华为云新鲜技术~
评论