數據一致性比對(番外)

我寫過很多如何去對數、如何批量對數的技術文檔,最近項目遇到這個問題,我才發現在官方博客上還沒有發佈過這個課題的文章。這就像燈下黑,太長用到的知識點,反而沒有意識到其重要性。

注:這裏對數的場景就是指在阿里雲平臺使用dataworks等大數據開發工具集成業務系統數據庫(oracle等)數據上雲到maxcompute的場景,所以,示例的SQL也是針對maxcompute。

先說說一般業務上怎麼對數的,我們做了一個報表,出了一個數據“某個產品賣了30個”。這個不只是在大數據平臺上有這個數據,在業務系統也有這個數據,這些統計動作在業務系統通過程序和人工也會有一份,一般做好報表後會先對這個數據。

所以,第一線反饋回來的數據就是這個彙總數據不一致的問題。然而這個結果是非常概括的,因爲就像我感覺這個月工資少發了5毛一樣,如果我不看我的工資條我其實不知道自己是不是少發了。工資條不只是一個彙總數據,裏面有我稅前工資、獎金(浮動)、社保、扣稅等一系列的明細數據,這些數據讓我去判斷我是不是少了5毛,而加工過的數據是複雜的。

說到這裏,我其實就像表達一個事情,對數是要對明細數據。這是一切計算後事實的基礎,可以拿出來作證的。

所以,兩邊都查一下這個彙總值使用的表的對應的記錄,比如說查詢“今天這個產品ID的售賣記錄”。結果就發現業務系統有31筆,而大數據平臺有30筆。

即便到了這裏,其實我們仍然不知道期間發生了什麼,爲什麼會丟失數據。另外我們還不知道其他商品ID的數據是不是也有丟失的,還有其他的表的數據是不是也會發生類似的情況。

1.明細數據比對

既然最終都是對明細數據,那麼我是不是可以直接比對明細數據呢?回答是:正確。

一般發生這種情況,首先要比對業務系統和大數據平臺兩個表的數據。

1.再利用全量集成工具,從業務系統的數據庫全量抽取一遍數據到大數據平臺。比對數據一定要把數據放到一起,隔空比對是不存在的。因爲大數據平臺的容量是數百倍於業務系統的,所以,一般都在大數據平臺比對。(這裏有一個悖論,如果集成工具本身就有缺陷,導致抽取過程中就丟數據,豈不是永遠沒辦法比對了。所以,如果對這個工具也不確定就得從數據庫導出數據到文件,然後再加載到某個數據庫上去比對。在這裏,通過我對離線集成這個產品的常年使用經驗,這個工具是非常可靠的,還未遇到過這個問題。)

2.根據主鍵關聯,比對2個表中的主鍵的差異。如果是上面提到的記錄丟失的問題,這一步做完就很容易比對出來了。這裏還會發現一個問題,就是業務系統的表是不斷變化的,所以,這時與大數據平臺的表對比會有差異。這個差異的核心原因是:大數據平臺的表是業務系統表在每日的日末(00:00:00)的一個時點數據,而業務系統的數據是一直在變化的。所以,即便有差異超出預期也不要驚慌。如果是使用實時同步可以從歸檔日誌中獲取到這期間數據的每一條變化,可以追溯變化原因。如果沒有實時同步,也可以通過表中與時間相關字段去判斷數據是否被更新掉。要是什麼都沒有(這種情況也是存在的),那就去罵罵設計表的業務系統開發(沒錯,是他們的鍋),也可以跟業務去詳細瞭解一下,這行記錄是不是今天做的,而不是昨天。

3.還有一種情況,就是主鍵一致,數據內容(主鍵之外的字段)不一致。這種情況,還是需要考慮數據變化的情況,可以從日誌、時間字段、業務等幾個角度去比對。如果發現數據確實不符合預期,就需要查詢同步工具的問題。

2.比對SQL分析

在上面的章節,我描述了比對今天新抽取的全量表和上日在maxcompute上使用前日全量和上日增量合併的上日全量的環節。比對兩張表集合是否一致的SQL方法其實比較簡單,大家第一時間就會想到集合操作。在oracle裏面有Minus、except,同樣在maxcompute裏面也有。但是爲了便於分析問題,我還是自己寫了一個SQL。示例SQL(maxcompute sql)如下:

--限定日期分區,比對上日
select count(t1.BATCH_NUMBER) as cnt_left
,count(t2.BATCH_NUMBER) as cnt_right
,count(concat(t1.BATCH_NUMBER,t2.BATCH_NUMBER)) as pk_inner
,count(case when t1.BATCH_NUMBER is not null and t2.BATCH_NUMBER is null then 1 end) as pk_left
,count(case when t2.BATCH_NUMBER is not null and t1.BATCH_NUMBER is null then 1 end) as pk_right
,count(case when nvl(t1.rec_id ,'') = nvl(t2.rec_id ,'') then 1 end) as col_diff_rec_id
,count(case when nvl(t2.rec_creator ,'') = nvl(t1.rec_creator ,'') then 1 end) as col_diff_rec_creator
,count(case when nvl(t2.rec_create_time,'') = nvl(t1.rec_create_time,'') then 1 end) as col_diff_rec_create_time
from ods_dev.o_rz_lms_im_timck01 t1 -- 開發環境重新初始化的今天數據
full join ods.o_rz_lms_im_timck01 t2 -- 生產環節昨日臨時增量合併的數據
on t1.BATCH_NUMBER =t2.BATCH_NUMBER 
and t1.IN_STOCK_TIME =t2.IN_STOCK_TIME
and t1.OP_NO =t2.OP_NO 
and t1.STOCK_CODE =t2.STOCK_CODE 
and t1.YP_ID =t2.YP_ID 
and t2.ds='20230426'
where t1.ds='20230426'
;
--cnt_left 9205131 說明:左表有記錄數 9205131
--cnt_right 9203971 說明:右表有記錄數 9203971
--pk_inner 9203971 說明:主鍵關聯一致記錄數 9203971
--pk_left 1160 說明:左表比右表多記錄數 1160
--pk_right 0 說明:右表比左表多有記錄數 0
--col_diff_rec_id 9203971 說明:字段一致記錄數與主鍵一致相同,說明關聯上的兩個表該字段一致
--col_diff_rec_creator 9203971 說明:同上
--col_diff_rec_create_time 9203971 說明:同上

在上面的例子中,左表是今天重新初始化的數據,右表是在maxcompute上merge的上日全量數據。在比對之前,我們其實就應該瞭解這兩個表的數據必然是不一致的。雖然是同一張表,但是時點是不一致的。

不一致包括幾種:

1.t1表中存在的主鍵,在t2表中不存在;

2.t2表中存在的主鍵,在t1表中不存在;

3.t1和t2表中都存在的主鍵,但是主鍵之外的字段值不一致;

4.t1和t2表中都存在的主鍵,但是主鍵之外的字段值一致;

除去第4中情況,其他3種狀態都是兩個表的值不一致,都需要進一步覈查。如果是正常情況下,第1種情況是在今天零點以後新insert到業務庫中的數據,第2種情況是今天零點以後delete的業務庫中的數據,第3種情況是今天零點之後update的數據,第4種情況是業務表中今天零點之後未被更新的數據。

瞭解這些狀況後,我們就可以依據可循去識別

3.Dataworks實時同步日誌表

如果同步纔有的是DataWorks的離線同步,其實觀測以上數據變化是有些困難的。如果我們採用實時同步,數據庫數據的變化是都會保留下來的。上一章節中提到的變化,都可以從日誌中觀測到。下面SQL,就是我查詢這個變化使用的SQL,查詢(dataworks實時數據同步日誌表)示例SQL如下:

select from_unixtime(cast(substr(to_char(_execute_time_),1,10) as bigint)) as yy
,get_json_object(cast(_data_columns_ as string),"$.rec_id") item0
,x.*
from ods.o_rz_lms_odps_first_log x -- 實時同步數據源 o_rz_lms 的日誌表
where year='2023' and month='04' and day>='10' --數據區間限制
--and hour ='18'
and _dest_table_name_='o_rz_lms_im_timck01' --數據表限制
-- 以下爲主鍵字段
and get_json_object(cast(_data_columns_ as string),"$.yp_id") ='L1'
and get_json_object(cast(_data_columns_ as string),"$.batch_number") ='Y1'
and get_json_object(cast(_data_columns_ as string),"$.in_stock_time") ='2'
and get_json_object(cast(_data_columns_ as string),"$.op_no") ='9'
and get_json_object(cast(_data_columns_ as string),"$.stock_code") ='R'
--and _operation_type_='D'
order by _execute_time_ desc 
limit 1000
;

 

-- _execute_time_ 數據操作時間
-- _operation_type_ 操作類型 增刪改UDI 
-- _sequence_id_ 序列號,不會重複
-- _before_image_ 修改前數據
-- _after_image_ 修改後數據
-- _dest_table_name_ 操作的表名
-- _data_columns_ 操作的數據內容JSON

DataWorks的實時同步的數據源每隔一段時間就會實時寫入到一張以“數據源名+_odps_first_log”命名的表中,表有年、月、日、時四級分區。該表的主鍵並不是數據操作時間,而是序列號“_execute_time_”,所以,一行數據主鍵的更新順序是按照“_execute_time_”更新。

一行數據更新是有前後兩個狀態的,所以有“_before_image_ 修改前數據、_after_image_ 修改後數據”這兩個字段來標識前後狀態。

數據是在字段“_data_columns_”中,以JSON格式存儲。爲了識別其中的某一行數據,我使用函數解析了對應的字段,以此來確定自己要的數據。

4.持續的質量保障

到這裏,我並未講如何去處理數據不一致的情況。如果確實發現數據不一致,可用的處理方式就是重新初始化全量數據。在這裏要強調一點,如果離線全量集成工具是可信的,全量初始化的數據就不會丟。但是如果這種方式不可信,則就要更換方法。

在很多情況下,源端做一些業務變更也會偶發數據異常。在數據丟失原因未查明的情況下,需要經常的去做數據一致性的比對,做到防患於未然。所以,日常監控數據數據一致性也是非常重要的。

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章