1. 程式人生 > >postgresql資料庫uuid重複引發血案

postgresql資料庫uuid重複引發血案

問題背景

、定時任務呼叫儲存過程、將資料插入臨時表時。出現了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重複的可能原因

  1. 伺服器生成uuid太快、導致重複?

  2. 還是說在伺服器正常但是真的同一時刻產生了重複的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。