需求
將 DB2 數據庫中的表數據導入另一個 DB2 數據庫的表裏面。
源表(DB2):table1
目標表(DB2):table2
數據量:千萬級別
思路
當時直接使用 Kettle 將數據從源表導入到目標表中,但是考慮到數據量過於龐大,實際執行過程花費了很長時間,因此考慮採用分頁導入的方式來進行數據傳輸,即:
根據實際情況設置一個每次處理的數據量,比如:5,000條,然後根據總的數據條數和每次處理的數據量計算出一共分幾頁。
假設總數據量有:10,000,000,所以頁數爲:10,000,000/5,000=2000頁
注: 若存在小數,小數部分算一頁,比如:20.3算21頁
解決方案
根據上述思路,我們首先需要考慮如何計算得到總頁數,以及頁碼。可以考慮增加一個輔助配置表來存放頁碼,這也是我在網上看到的處理方法,但是不符合工作需求,所以我考慮必須自動計算一個表的總頁數,並生成頁碼。最後根據頁碼來循環導入數據。
主流程如下:onetable_A.kjb
流程說明:
- 首先我們建立一個作業流程,然後配置一個 START 節點;
- 第一個轉換流程用於查詢表數據的數目,並計算得到總頁數,以及得到一個頁碼集合;
- 再建一個作業流程,來循環處理頁碼,主要是將第幾頁的數據從源表同步到目標表中。
根據表數據生成頁碼
首先我們來重點關注第一個轉換流程,這也是本次實現過程中最重要的一點。
流程結構如下:
getTotal 節點用於查詢表數據的數目,其實就是在系統表中做查詢操作,具體實現如下:
點擊預覽
按鈕,結果如下:
Java 代碼腳本部分主要是根據上一步得到的數據數目來計算頁碼,並生成一個頁碼集合。關於 Java 代碼腳本的使用,這裏就不做介紹了。構建過程如下:
Java 代碼如下:
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
if (first) {
first = false;
/* TODO: Your code here. (Using info fields)
FieldHelper infoField = get(Fields.Info, "info_field_name");
RowSet infoStream = findInfoRowSet("info_stream_tag");
Object[] infoRow = null;
int infoRowCount = 0;
// Read all rows from info step before calling getRow() method, which returns first row from any
// input rowset. As rowMeta for info and input steps varies getRow() can lead to errors.
while((infoRow = getRowFrom(infoStream)) != null){
// do something with info data
infoRowCount++;
}
*/
}
Object[] r = getRow();
if (r == null) {
setOutputDone();
return false;
}
// It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
// enough to handle any new fields you are creating in this step.
/* TODO: Your code here. (See Sample)
// Get the value from an input field
String foobar = get(Fields.In, "a_fieldname").getString(r);
foobar += "bar";
// Set a value in a new output field
get(Fields.Out, "output_fieldname").setValue(r, foobar);
*/
//此處創建 r,是爲了獲取輸入參數TOTAL_SRC的值
r = createOutputRow(r, data.outputRowMeta.size());
double num = get(Fields.In, "TOTAL_SRC").getNumber(r);
int pageNum = 5000;
int pages = (int)num/pageNum +1; //計算總頁數
//生成頁碼,並輸出
for(int i=0;i<pages;i++){
//個人覺得r類似於輸出器,如果想將每個頁碼都輸出去,則必須獨立進行聲明,此步驟爲本人測試所得
r = createOutputRow(r, data.outputRowMeta.size());
get(Fields.Out, "PAGE").setValue(r, i); //將頁碼賦值給PAGE
//get(Fields.Out, "TJOBSEQ").setValue(r, get(Fields.In, "JOB_SEQ").getString(r));
//get(Fields.Out, "id").setValue(r, i);
putRow(data.outputRowMeta, r);
}
// Send the row on to the next step.
return true;
}
雖然我們想要得到的頁碼爲整數類型,但是在設置 PAGE 類型時需要設置爲 String 類型,否則會報錯,如下圖所示:
並不影響後續的使用。
對於下述流程進行測試驗證。
執行結果爲:
從結果可以看出,每個頁碼都被正確輸出。那麼接下來我們需要將頁碼複製到結果中,傳遞到接下來的作業流程中。
根據頁碼循環同步數據
頁碼生成完畢後,接下來就是根據頁碼從源表查詢數據,然後同步到目標表中。流程設計如下:
傳進來的頁碼必須先從結果中獲取到,然後再定義爲變量,才能被後續所使用。
第一個轉換流程的內部實現如下:
爲了區分,我們將變量名叫做 EPAGE。
接下來就是數據同步,查看 getData_Epage。
首先需要根據頁碼從源表中獲取到數據,注意這裏爲了使用頁碼條件,查詢得到的結果不得不多了一列結果。
表輸出時,注意不要勾選裁剪表,需要指定數據庫字段,將上面查到的結果中多的一列給剔除掉。如果不想在此處做數據庫字段指定操作,可以修改表輸入中的查詢語句。
select
REC_CREATOR,
REC_CREATE_TIME,
....
from (select ROW_NUMBER() over() as a, g001.* from
ZGROD112.D112_L2_FV_QD_CPCSEG002 g001) as temp
where a>=(5*${EPAGE}+1) and a<=(5*(${EPAGE}+1))
至此,關於大規模數據表之間的同步操作結束。
總結
本例重點講述瞭如何根據表數據的數目動態生成頁碼,從而減少了頁碼配置表的構建步驟,最終減輕數據同步對於內存資源的佔用。
拓展
實際工作中,我們不可能只對一張表做數據同步,往往會針對多張表做同步操作,所以對每張表還會有一個循環處理操作。關於這種情況的處理,需要一個額外的配置表,用來存放源系統和目標系統基本信息,以及源表和目標表信息,如果需要做增量同步操作,還可以加幾個字段。後續有時間可以簡單寫一下。