DM8高级日志

高级日志
简介
行表和HUGE表在增删改查性能上存在差异,因此在实际的生产环境中,用户可能会同时使用一个行表来管理数据和一个HUGE表来分析数据。具体做法是对行表进行增删改操作,然后把行表中的数据复制到HUGE表中用于查询或分析。如果每次分析数据时都对行表进行全表查询插入HUGE表,性能较低。

为此提出一种解决方案:给行表添加日志辅助表用于记录行表的增删改和TRUNCATE操作,可以根据日志表实现对HUGE表的增量更新,以此来提高从行表复制数据到HUGE表的性能。

使用须知
增量更新过程,我们只提供日志的记录以及日志记录规则的制定,真正执行增量更新是由用户根据日志记录自行操作。辅助表中登记信息,为某一时间点后源表数据的增量变化信息登记。

创建日志辅助表
创建日志辅助表,有两种方式:一是建表时创建;二是修改表时创建。
1. 建表时候使用< 高级日志子句>创建日志辅助表
语法格式

CREATE TABLE < 表名定义> < 表结构定义>;
< 表名定义> ::= [< 模式名>.] < 表名>
< 表结构定义>::=< 表结构定义1> | < 表结构定义2>
< 表结构定义1>::= (< 列定义> {,< 列定义>} [,< 表级约束定义>{,< 表级约束定义>}]) [ON COMMIT  ROWS] [][< 空间限制子句>] [][< 压缩子句>] []< 高级日志子句> [] []
……
< 高级日志子句>::= WITH ADVANCED LOG
省略号(……)

2. 修改表时使用< 高级日志子句>添加日志表
语法格式

ALTER TABLE < 高级日志子句>;

删除日志辅助表
语法格式

ALTER TABLE xxx WITHOUT ADVANCED LOG;

删除日志辅助表的数据
语法格式

ALTER TABLE  TRUNCATE ADVANCED LOG;

数据清除后可能导致源表和HUGE无法同步,需慎重操作。

使用日志辅助表的规则与约束
日志辅助表命名为“表名$ALOG”,用于记录源表的操作但不涉及具体数据。规则与约束:
1. 每个源表仅支持设置一个日志辅助表。
2. 表删除的同时删除其日志辅助表。
3. 表更名时,日志表同步更名。
4. 由于其日志表名长度不得超过128,因此表名长度不得超过123。
5. 辅助表仅登记源表相关增删改及TRUNCATE等涉及数据变化的操作,却不涉及具体数据。
6. 源表执行ADD/DROP/MODIFY COLUMN的DDL操作时,也必须保证日志辅助表为空。
7. 如果表设置了高级日志功能,禁止或者不建议以下操作:
1) 禁止对源表创建聚集索引
2) 禁止删除源表上本存在的聚集索引
3) 禁止直接对分区表的子表执行DELETE、UPDATE、INSERT以及TRUNCATE
4) 禁止在ALTER TABLE时,新建、删除或者修改主键,使主键失效或者生效,或者删除主键列
5) 禁止对临时表、HUGE表和间隔分区表设置高级日志表,禁止查询插入建表方式设置高级日志表。
6) 禁止直接删除高级日志表以及创建后缀为”$ALOG”的表
7) 禁止合并分区
8) 禁止对表加列、删除列和修改列,禁止添加、分裂、交换和删除分区。交换分区时的普通表也禁止带有高级日志
9) 表备份还原后无法控制数据跟踪,无法保证同步数据的正确性。因此不建议对该表进行备份还原操作,或操作后需要人工干预处理

日志辅助表结构
高级日志辅助表“表名$ALOG”的结构如下:

列                   数据类型           说明
ORG_ROWID            BIGINT             源表ROWID。当OP_TYPE=0时,ORG_ROWID=0
OP_TYPE              SMALLINT           登记记录日志动作。
                                        0:TRUNCATE
                                        1:行插入
                                        2:批量插入起始
                                        3:批量插入结束
                                        4:更新
                                        5:删除
                                        6:删除后再插入(仅用于堆表)

COLMAP               VARBINARY(2048)    当OP_TYPE=3时,记录的是批量插入结束的ROWID;
                                        当OP_TYPE=4时,是记录的更新列的列号。例如0xA3,即二进制的10100011,
                                        表示更新的列为第1、2、6、8列,与DM_BIT_TEST()配合使用;其他情况为null

COL_0     与源表的第一个主键列类型相同  源表的第一个主键列
COL_1     与源表的第二个主键列类型相同  源表的第二个主键列
COL_n     ...                           ...

系统过程
高级日志辅助表中的COLMAP列记录的数据,用&操作只能获取前64列的更新情况,因为会数据溢出。增加系统过程DM_BIT_TEST()用于获取一个VARBINARY数据的第N位的数值。
语法格式

DM_BIT_TEST(DATA varbinary, nth int);

功能:返回二进制数据varbinary第nth位是0还是1(最低位序号为1)。如果超过了位数则返回0。
例 0xF1转为二进制后为11110001,从低位开始第5位为1。二进制1011从低位开始第三位为0。

SQL> SELECT DM_BIT_TEST(0xF1,5),DM_BIT_TEST(1011,3);

LINEID     DM_BIT_TEST(0xF1,5) DM_BIT_TEST(1011,3)
---------- ------------------- -------------------
1          1                   0

使用高级日志同步数据的原则
用户根据表定义创建数据同步的目标表,自己编写同步DMSQL脚本来进行同步。对于同步,建议遵守如下的原则:
1. 如果源表有主键,如果用户没有特殊的限制或要求,目标表最好也设置同样的主键。
2. 如果源表没有主键,为了准确同步,最好在目标表上添加一个辅助同步的主键列,同步时将org_rowid列的值插入该列中。
3. 用户同步数据的脚本基本逻辑如下:

declare
/*遍历日志表的游标*/
cursor c IS select * from t01$alog for update;
/*同步用的变量*/
r t01$alog %rowtype;
/*同步批量插入用的变量*/
bi_start t01$alog %rowtype;
org_rec t01%rowtype;
begin
/*遍历日志表,根据各记录的op_type进行同步*/
open c;
loop
fetch c into r;
exit when c%notfound;
if (r.op_type = 0) then
print 'truncate' ;
execute immediate 'truncate table t01';
elseif (r.op_type = 1 or r.op_type = 6) then
print 'insert ' || r.org_rowid;
execute immediate 'insert ....'
elseif (r.op_type = 2) then
bi_start = r;
print 'batch insert start';
elseif (r.op_type = 3) then
print 'batch insert last ' || bi_start.org_rowid || ' ' || cast( r.colmap as bigint);
execute immediate 'insert ....'
elseif (r.op_type = 4) then
print 'update ' || r.org_rowid;
select * into org_rec from t01 where ……;
execute immediate 'update ....' using bi_start… r…;
elseif (r.op_type = 5) then
print 'delete ' || r.org_rowid;
execute immediate 'delete ....'
end if;
end loop;
close c;
/*清理日志表*/
execute immediate 'alter table t01 truncate advanced log';
end;
/

4. 如果在数据同步时源表仍有并发的DML,脚本中查询日志时要使用for update子句。

5. 同步脚本根据源表的结构有所不同:
1) 如果源表有聚集主键
在同步时可使用日志辅助表中的org_rowid和主键列辅助源表定位。使用主键列定位目标表。
2) 如果源表有主键,但不是聚集主键
直接根据org_rowid定位数据,最好不要使用主键列来定位源表。主键列仅用来定位目标表。
如果该情况下更新了主键列,对于聚集主键,将是删除后更新,如果不是聚集主键,仍是记录更新,日志辅助表中的主键列仍是原值,所以非聚集主键时主键列不要用来定位源表。
3) 如果没有主键
使用org_rowid来进行源表的定位;目标表的定义根据用户自己的方式使用org_rowid定位。
6. 如果源表中没有聚集索引,批量插入时可以根据OP_TYPE=3时的org_rowid(批量插入起始ROWID)和COLMAP中的数据(批量插入结束ROWID)范围查询源表插入目标表;如果有聚集索引,考虑到组合索引无法进行范围查询,只能使用第一个主键和rowid进行范围查询。
7. MPP环境下,因为高级日志表是本地表,所以同步数据的时候,只能各个节点单独做同步。

应用实例
创建不带主键的源表
1. 创建源表

SQL> Create table t01(a int, b int, c varchar);
executed successfully
used time: 16.049(ms). Execute id is 2438.

SQL> insert into t01 values(88,88, '原始数据1');
affect rows 1

used time: 0.689(ms). Execute id is 2440.
SQL> insert into t01 values(99,99, '原始数据2');
affect rows 1

used time: 0.430(ms). Execute id is 2442.

2. 在源表上创建日志辅助表

SQL> Alter table t01 with advanced log;
executed successfully
used time: 28.284(ms). Execute id is 2443.

3. 查看日志辅助表结构

SQL> Select tabledef('SYSDBA','T01$ALOG');

LINEID     TABLEDEF('SYSDBA','T01$ALOG')
---------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1          CREATE TABLE "SYSDBA"."T01$ALOG"
(
"ORG_ROWID" BIGINT NOT NULL,
"OP_TYPE" SMALLINT NOT NULL,
"COLMAP" VARBINARY(2048),
CLUSTER PRIMARY KEY("ORG_ROWID", "OP_TYPE")) STORAGE(ON "MAIN", CLUSTERBTR) ;

used time: 0.846(ms). Execute id is 2445.

4. 在源表中删除1行数据。

SQL> delete from t01 where a=88;
affect rows 1

used time: 1.329(ms). Execute id is 2447.

SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ----------
1          1                    5           NULL

used time: 0.567(ms). Execute id is 2449.

5. 在源表中更新1行数据。

SQL> update t01 set c='hello world' where a=99;
affect rows 1

used time: 0.907(ms). Execute id is 2451.
SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ----------
1          1                    5           NULL
2          2                    4           0x04

used time: 0.220(ms). Execute id is 2452.

6. 在源表中再次更新同1行数据。这一操作在日志表中没有记录。因为将源表上一条(99,99, ‘原始数据2’)的数据更新为(99,99,’hello world’)之后,又再次更新为(99,99,’hello world!’)。这两步更新操作的最终结果就和直接更新为(99,99,’hello world!’)一样,所以两步操作只有一条记录。

SQL> update t01 set c='hello world!' where a=99;
affect rows 1

used time: 0.976(ms). Execute id is 2457.
SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ----------
1          1                    5           NULL
2          2                    4           0x04

used time: 0.307(ms). Execute id is 2458.

7. 先清空源表数据,再查看日志辅助表的变化。发现日志辅助表中也清空了之前的记录,只记录下了清空源表的操作。

SQL> Truncate table t01;
executed successfully
used time: 28.025(ms). Execute id is 2460.
SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ----------
1          0                    0           NULL

used time: 0.414(ms). Execute id is 2461.

8. 在源表中批量插入100行数据。单机情况下,大于100条才叫批量插入。

SQL> insert into t01 select level a,level+1 b,level c connect by level< =100 order by a,b; affect rows 100 used time: 2.692(ms). Execute id is 2464. SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ------------------
1          0                    0           NULL
2          1                    2           NULL
3          1                    3           0x0000000000000064

used time: 0.343(ms). Execute id is 2466.

9. 在源表中插入1行数据。

SQL> insert into t01 values(1001,1002,1003);
affect rows 1

used time: 0.539(ms). Execute id is 2468.
SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP
---------- -------------------- ----------- ------------------
1          0                    0           NULL
2          1                    2           NULL
3          1                    3           0x0000000000000064
4          101                  1           NULL

used time: 0.178(ms). Execute id is 2470.

10.同步数据
创建huge表。因为不带主键,为了准确同步,在目标表huge_t01上添加一个辅助同步的主键列c_rowid,同步时将org_rowid列的值插入该列中

SQL> create huge table huge_t01 (c_rowid bigint, a int, b int, c varchar(1024));
executed successfully
used time: 26.784(ms). Execute id is 2474.

运行同步脚本。同步脚本由用户根据实际情况自行编写。本例中脚本如下:

SQL> declare
2   /*遍历日志表的游标*/
3   cursor c IS select * from t01$alog for update;
4   /*同步用的变量*/
5   r t01$alog %rowtype;
6   /*同步批量插入用的变量*/
7   bi_start t01$alog %rowtype;
8   set_sql varchar;
9   upd_sql varchar;
10  i int;
11  begin
12  /*遍历日志表,根据各记录的op_type进行同步*/
13  open c;
14  loop
15  fetch c into r;
16  exit when c%notfound;
17  if (r.op_type = 0) then
18  print 'truncate' ;
19  execute immediate 'truncate table huge_t01;';
20  elseif (r.op_type = 1 or r.op_type = 6) then
21  print 'insert ' || r.org_rowid;
22  execute immediate 'insert into huge_t01 select rowid,* from t01 where rowid=?;' using r.org_rowid;
23  elseif (r.op_type = 2) then
24  bi_start = r;
25  print 'batch insert start';
26  elseif (r.op_type = 3) then
27  print 'batch insert last ' || bi_start.org_rowid || ' ' || cast( r.colmap as bigint);
28  execute immediate 'insert into huge_t01 select rowid,* from t01 where rowid>= ? and rowid< = ?;' using r.org_rowid, cast(r.colmap as bigint); 29 elseif (r.op_type = 4) then 30 print 'update ' || r.org_rowid; 31 set_sql = ''; 32 i = 0; 33 if (dm_bit_test(r.colmap,1)) = 1 then set_sql = set_sql || 'a = org.a'; i = i+1; end if; 34 if (dm_bit_test(r.colmap,2)) = 1 then if i > 0 then set_sql = set_sql ||','; end if; set_sql = set_sql || 'b = org.b'; i = i+1; end if;
35  if (dm_bit_test(r.colmap,3)) = 1 then if i > 0 then set_sql = set_sql ||','; end if; set_sql = set_sql || 'c = org.c'; i = i+1; end if;
36  upd_sql = 'declare org t01%rowtype; begin select * into org from t01 where rowid=?; update huge_t01 set ' || set_sql || ' where c_rowid=?; end;';
37  execute immediate upd_sql using r.org_rowid, r.org_rowid;
38  elseif (r.op_type = 5) then
39  print 'delete ' || r.org_rowid;
40  execute immediate 'delete from huge_t01 where c_rowid=?;' using r.org_rowid;
41  end if;
42  end loop;
43  close c;
44  /*清理日志表*/
45  execute immediate 'alter table t01 truncate advanced log';
46  end;
47  /
DMSQL executed successfully
used time: 197.177(ms). Execute id is 2478.

11.查询huge表中的数据。可以看出,huge_t01上的数据都是源表创建了日志辅助表之后的增量数据。

SQL> Select count(*) from huge_t01;

LINEID     COUNT(*)
---------- --------------------
1          101

used time: 0.622(ms). Execute id is 2479.

创建带主键的源表
1. 创建带有日志辅助表的源表

SQL> Create table t01(a int, b int, c varchar, primary key(a,b)) with advanced log;
executed successfully
used time: 14.030(ms). Execute id is 2834.

2. 查看日志辅助表结构

SQL> Select tabledef('SYSDBA','T01$ALOG');

LINEID     TABLEDEF('SYSDBA','T01$ALOG')
---------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1          CREATE TABLE "SYSDBA"."T01$ALOG"
(
"ORG_ROWID" BIGINT NOT NULL,
"OP_TYPE" SMALLINT NOT NULL,
"COLMAP" VARBINARY(2048),
"COL_0" INTEGER,
"COL_1" INTEGER,
CLUSTER PRIMARY KEY("ORG_ROWID", "OP_TYPE")) STORAGE(ON "MAIN", CLUSTERBTR) ;

used time: 0.637(ms). Execute id is 2844.

3. 清空源表

SQL> Truncate table t01;
executed successfully
used time: 14.417(ms). Execute id is 2849.

SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP     COL_0       COL_1
---------- -------------------- ----------- ---------- ----------- -----------
1          0                    0           NULL       NULL        NULL

used time: 0.760(ms). Execute id is 2857.

4. 在源表中插入一条记录

SQL> insert into t01 values(1001,1002,1003);
affect rows 1

used time: 0.621(ms). Execute id is 2860.
SQL> Select * from t01$alog;

LINEID     ORG_ROWID            OP_TYPE     COLMAP     COL_0       COL_1
---------- -------------------- ----------- ---------- ----------- -----------
1          0                    0           NULL       NULL        NULL
2          1                    1           NULL       1001        1002

used time: 0.313(ms). Execute id is 2861.

5. 同步数据
创建huge表。

SQL> create huge table huge_t01 (a int, b int, c varchar(1024), primary key(a,b));
executed successfully
used time: 24.819(ms). Execute id is 2868.

运行同步脚本。同步脚本由用户根据实际情况自行编写。本例中脚本如下:

SQL> declare
2   /*遍历日志表的游标*/
3   cursor c IS select * from t01$alog for update;
4   /*同步用的变量*/
5   r t01$alog %rowtype;
6   /*同步批量插入用的变量*/
7   bi_start t01$alog %rowtype;
8   set_sql varchar;
9   upd_sql varchar;
10  i int;
11  begin
12  /*遍历日志表,根据各记录的op_type进行同步*/
13  open c;
14  loop
15  fetch c into r;
16  exit when c%notfound;
17  if (r.op_type = 0) then
18  print 'truncate' ;
19  execute immediate 'truncate table huge_t01;';
20  elseif (r.op_type = 1 or r.op_type = 6) then
21  print 'insert ' || r.org_rowid;
22  execute immediate 'insert into huge_t01 select * from t01 where rowid=?;' using r.org_rowid;
23  elseif (r.op_type = 2) then
24  bi_start = r;
25  print 'batch insert start';
26  elseif (r.op_type = 3) then
27  print 'batch insert last ' || bi_start.org_rowid || ' ' || cast( r.colmap as bigint);
28  execute immediate 'insert into huge_t01 select * from t01 where rowid>= ? and rowid< = ?;' using r.org_rowid, cast(r.colmap as bigint); 29 elseif (r.op_type = 4) then 30 print 'update ' || r.org_rowid; 31 set_sql = ''; 32 i = 0; 33 if (dm_bit_test(r.colmap,1)) = 1 then set_sql = set_sql || 'a = org.a'; i = i+1; end if; 34 if (dm_bit_test(r.colmap,2)) = 1 then if i > 0 then set_sql = set_sql ||','; end if; set_sql = set_sql || 'b = org.b'; i = i+1; end if;
35  if (dm_bit_test(r.colmap,3)) = 1 then if i > 0 then set_sql = set_sql ||','; end if; set_sql = set_sql || 'c = org.c'; i = i+1; end if;
36  upd_sql = 'declare org t01%rowtype; begin select * into org from t01 where rowid=?; update huge_t01 set ' || set_sql || ' where a = ? and b = ?; end;';
37  execute immediate upd_sql using r.org_rowid, r.col_0, r.col_1;
38  elseif (r.op_type = 5) then
39  print 'delete ' || r.org_rowid;
40  execute immediate 'delete from huge_t01 where a= ? and b = ?;' using r.col_0, r.col_1;
41  end if;
42  end loop;
43  close c;
44  /*清理日志表*/
45  execute immediate 'alter table t01 truncate advanced log';
46  end;
47  /
DMSQL executed successfully
used time: 134.846(ms). Execute id is 2872.

6. 查询huge表中的数据。可以看出,huge_t01上的数据都是源表创建了日志辅助表之后的增量数据。

SQL> select * from huge_t01;

LINEID     A           B           C
---------- ----------- ----------- ----
1          1001        1002        1003

used time: 1.276(ms). Execute id is 2875.

发表评论

电子邮件地址不会被公开。