DM8高階日誌

eric0435發表於2021-12-21

高階日誌
簡介
行表和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.


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26015009/viewspace-2848578/,如需轉載,請註明出處,否則將追究法律責任。

相關文章