写点什么

详解数仓中 sequence 的应用场景及优化

  • 2023-03-09
    中国香港
  • 本文字数:4452 字

    阅读完需:约 15 分钟

详解数仓中sequence的应用场景及优化

本文分享自华为云社区《GaussDB(DWS)关于sequence的那些事》,作者:Arrow0lf 。

什么是 sequence


sequence,也称作序列,是用来产生唯一整数的数据库对象。序列的值按照一定的规则自增/自减,一般常被用作主键。GaussDB(DWS)中,创建 sequence 时会同时创建一张同名的元数据表,用来记录 sequence 相关的信息,例如:


postgres=# create sequence seq;CREATE SEQUENCEpostgres=# 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 中的创建流程如下图所示:



具体过程为:


  1. 接受 SQL 命令的 CN 从 GTM 申请 UUID;

  2. GTM 返回一个 UUID;

  3. CN 将拿到的 UUID 与用户创建的 sequenceName 绑定;

  4. CN 将绑定关系下发给其他节点上,其他节点同步创建 sequence 元数据表;

  5. 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 SEQUENCEpostgres=# 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 TABLEpostgres=#\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: noDistribute By: HASH(a)Location Nodes: ALL DATANODESOptions: 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 TABLEpostgres=# create sequence test_b_seq owned by test.b;CREATE SEQUENCEpostgres=# alter sequence test_b_seq owner to jerry;  --jerry为test表的属主,如果当前用户即为属主,可不执行此语句ALTER SEQUENCEpostgres=# alter table test alter b set default nextval('test_b_seq'), alter b set not null;ALTER TABLEpostgres=# \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: noDistribute By: HASH(a)Location Nodes: ALL DATANODESOptions: 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 TABLEpostgres=# postgres=# create table test2(a int) distribute by hash(a);CREATE TABLEpostgres=# 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值并删除sequenceselect nextval('test_b_seq');   --记录该值,作为新建sequence的start valuedrop sequence test_b_seq;-- 新建seqeunce并绑定目标表create sequence test_b_seq START with xxx cache 10000 owned by test.b;  -- xxx替换为上一步查到的nextvalalter 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


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 3
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
详解数仓中sequence的应用场景及优化_数据库_华为云开发者联盟_InfoQ写作社区