問題背景
、定時任務調用存儲過程、將數據插入臨時表時。出現了uuid重復的報錯。
報錯信息
[SQL]select DB_DATA.PR_SELECT() [Err] ERROR: duplicate key value violates unique constraint "pk_result_select" DETAIL: Key (c_id)=(3d0e61c6615092883cc5e29198aaffb7) already exists. CONTEXT: SQL statement "insert into DB_DATA.RESULT_SELECT(C_ID,AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD) select replace(cast(uuid_generate_v4() as varchar),'-','') as
排查問題
查看該函數
drop function "DB_DATA"."pr_select_bak"(); CREATE OR REPLACE FUNCTION "DB_DATA"."pr_select_bak"() RETURNS "pg_catalog"."void" AS $BODY$ BEGIN truncate table DB_DATA.result_select_bak; insert into DB_DATA.result_select_bak(C_ID,AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID, CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD) select replace(cast(uuid_generate_v4() as varchar),'-','') as C_ID,T1.AJLBID,T1.AJBSID, T1.AJBS,T1.AH,T1.JBFYID,T1.CBSPTID,T1.CBRID,T1.LARQ,T1.JARQ,T1.XGSJ,T1.AJJZJDID,T1.YZCD from ( SelectdistinctAJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from (select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_QT where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_SF where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_ZX where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_WS where AJLBID = 1 ) T2 ) T1; insert into DB_DATA.result_select_bak(C_ID,AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID, CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD) select replace(cast(uuid_generate_v4() as varchar),'-','') as C_ID,T1.AJLBID,T1.AJBSID, T1.AJBS,T1.AH,T1.JBFYID,T1.CBSPTID,T1.CBRID,T1.LARQ,T1.JARQ,T1.XGSJ,T1.AJJZJDID,T1.YZCD from ( select distinct AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from (select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_QT where AJLBID = 2 --后面還有許多where條件不一樣insert 的就不一一列舉了 ...... END $BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION "DB_DATA"."pr_select_bak"() OWNER TO "atybase";
查看該存儲過程並沒有什么特別之處
觀察uuid重復的規律
環境linux、數據庫版本abase3.5.1、每次插入表總數:76824
調用15次存儲過程操作查看uuid重復的條數:
-
無重復:3次
-
重復一條:5次
-
重復兩條:4次
-
重復三條:2次
-
重復四條:1次
上網查了下uuid重復的概率:每秒產生10億筆UUID,100年后只產生一次重復的機率是50%.如果地球上每個人都各有6億筆UUID,發生一次重復的機率是50%
關於postgresql uuid重復的一片文章:連接當機器每微秒可以產生多個UUID時,在多個進程中有可能產生重復值。
原因就是前面對uuid.c的分析。因為本機唯一碼必須確保同一個微秒內不能產生多個UUID,所以盡量不要並行產生。
猜測uuid重復的可能原因
-
服務器生成uuid太快、導致重復?
-
還是說在服務器正常但是真的同一時刻產生了重復的uuid。(這種情況就像被隕石擊中一樣、從實驗結果的高命中可以基本排除)
疑問
這些重復的uuid是不同的insert生成的、還是一個insert里面就能生成重復的uuid?
為了解開疑問:首先將臨時表result_select_bak去掉主鍵約束、添加一個序號(XH)字段用於記錄是哪個insert插入的數據。
測試過程
DROP TABLE IF EXISTS "DB_DATA"."result_select_bak"; CREATE TABLE "DB_DATA"."result_select_bak" ( "c_id" varchar(35) COLLATE "default" NOT NULL, --中間字段不一一列舉 "yzcd" int4, --添加序號 "xh" int4 ) WITH (OIDS=FALSE); CREATE OR REPLACE FUNCTION "DB_DATA"."pr_select_bak"() RETURNS "pg_catalog"."void" AS $BODY$ BEGIN truncate table DB_DATA.result_select_bak; insert into DB_DATA.result_select_bak(C_ID,AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID, CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD,XH) select replace(cast(uuid_generate_v4() as varchar),'-','') as C_ID,T1.AJLBID, T1.AJBSID,T1.AJBS,T1.AH,T1.JBFYID,T1.CBSPTID,T1.CBRID,T1.LARQ,T1.JARQ, T1.XGSJ,T1.AJJZJDID,T1.YZCD,1 from ( select distinct AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from ( select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_QT where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_SF where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_ZX where AJLBID = 1 union all select AJLBID,AJBSID,AJBS,AH,JBFYID,CBSPTID,CBRID,LARQ,JARQ,XGSJ,AJJZJDID,YZCD from DB_DATA.RESULT_SELECT_WS where AJLBID = 1 ) T2 ) T1; insert into DB_DATA.result_select_bak(C_ID,AJLBID,AJBSID,AJBS,AH,JBFYID, CBSPTID, CBRID, LARQ,JARQ,XGSJ,AJJZJDID,YZCD,XH) select replace(cast(uuid_generate_v4() as varchar),'-','') as C_ID,T1.AJLBID, T1.AJBSID,T1.AJBS,T1.AH,T1.JBFYID,T1.CBSPTID,T1.CBRID,T1.LARQ, T1.JARQ,T1.XGSJ,T1.AJJZJDID,T1.YZCD,2 ..... END $BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION "DB_DATA"."pr_select_bak"() OWNER TO "atybase";
測試結果
abase2=# select c_id from DB_DATA.result_select_bak group by c_id having count(*)>1; c_id ---------------------------------- 69d74a5ed31b8d51a59cf6d244cef763 (1 row) --相同序號、說明是一個insert里面產生了相同的uuid abase2=# select c_id,xh from DB_DATA.result_select_bak where c_id = '69d74a5ed31b8d51a59cf6d244cef763'; c_id | xh ----------------------------------+---- 69d74a5ed31b8d51a59cf6d244cef763 | 2 69d74a5ed31b8d51a59cf6d244cef763 | 2 (2 rows) abase2=# select c_id,xh from DB_DATA.result_select_bak where c_id = '0cac29558223c7b3cd72f53116d62a2d'; c_id | xh ----------------------------------+---- 0cac29558223c7b3cd72f53116d62a2d | 2 0cac29558223c7b3cd72f53116d62a2d | 1 (2 rows) abase2=# select c_id,xh from DB_DATA.result_select_bak where c_id = '1ea8c12e58169105fa93ec1d838b6f07'; c_id | xh ----------------------------------+---- 1ea8c12e58169105fa93ec1d838b6f07 | 9 1ea8c12e58169105fa93ec1d838b6f07 | 1 (2 rows) ...
經測試發現不管是同一個insert還是不同的insert都有可能生成相同的uuid。
到這一步我開始懷疑是不是服務器有問題了。但是這種小概率事件真的就發生在我身上了嗎?我還是不太相信小概率事件會發生
轉換角度
想到默認abase安裝擴展會有三個uuid函數:uuid_generate_v1()、uuid_generate_v4()、uuid_generate_v1mc()。所以考慮使用select uuid_generate_v1();替換掉uuid_generate_v4()看結果如何。但是報錯找不到該函數。
開始懷疑
是不是插件的問題呢?
將abase3.5.1自帶的uuid插件uuid-ossp.so。替換掉/opt/thunisoft/arterybase/3.5/lib/postgresql/uuid-ossp.so、然后重啟數據庫。在DB_DATA下面創建擴展函數:create extension “uuid_ossp”
再次測試
執行最開始的存儲過程沒有發現重復uuid、多測試了幾次還是沒有、這個時候感覺找到問題所在了應該就是插件的問題。
為了驗證正確性然后測試修改后添加了序號的存儲過程發現還是有重復的數據。開始納悶了! 詳細對比這兩函數獲取uuid的方式: 正常獲取、uuid:replace(cast(uuid_generate_v4() as varchar,’-’,’’)) 異常獲取、uuid:replace(public.uuid_generate_v4():text,’-’,’’) 正常獲取:不加schema默認獲取當前DB_DATA下面的uuid_generate_v4()函數。 異常獲取:獲取了public下面的uuid_generate_v4();
查看public下面的函數
CREATE OR REPLACE FUNCTION "public"."uuid_generate_v4()" RETURNS "pg_catalog"."varchar" AS $BODY$BEGIN --Routne body goes here... RETURN md5(random()::text || now::text); END $BODY LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION "public"."uuid_generate_v4"() OWNER TO "atybase";
對比自帶uuid函數
CREATE OR REPLACE FUNCTION "public"."uuid_generate_v4"() RETURNS "pg_catalog"."uuid" AS '$libdir/uuid-ossp', 'uuid_generate_v4' LANGUAGE 'c' VOLATILE STRICT COST 1; ALTER FUNCTION "public"."uuid_generate_v4"() OWNER TO "sa";
發現問題
觀察可以看到該函數被重新定義了、沒有使用基礎動態鏈接庫、而是使用了隨機數和當前時間組合md5加密的方式、導致uuid重復。
結語
在安裝abase3.5.1以上版本時默認會再public下面創建uuid函數、直接調用即可、不需要再去手動創建。如果在腳本中使用了set search_path to db_xxx;然后去調用uuid_generate_v4(),會報錯找不到該函數、可以使用set search_path to public,db_xxx;同時指定多個schema。