本文為您介紹在Hologres中INSERT ON CONFLICT語句的用法。
應用情境
INSERT ON CONFLICT
命令適用於通過SQL方式匯入資料的情境。
使用Data Integration或Flink寫入資料時,如果需要對主鍵重複的行資料執行更新或跳過操作,則需進行如下配置:
通過DataWorks的Data Integration匯入資料。
Data Integration已內建
INSERT ON CONFLICT
功能,該功能的實現原理請參見Hologres Writer。同時,您需要進行如下配置:離線同步資料時,寫入衝突策略選擇忽略(Ignore)或者更新(Replace)。
即時同步資料時,寫入衝突策略選擇忽略(Ignore)或者更新(Replace)。
說明同步資料時,Hologres的表均需要設定主鍵,才能更新資料。
通過Flink寫入資料。
通過Flink寫入資料預設寫入衝突策略使用
InsertOrIgnore
(保留首次出現的資料,忽略後續所有資料),但是需要您在Hologres建表時設定主鍵。如果使用ctas
文法,則寫入衝突策略預設為InsertOrUpdate
(替換部分已有資料)。
命令介紹
INSERT ON CONFLICT語句用於在指定列插入某行資料時,如果主鍵存在重複的行資料,則對該資料執行更新或跳過操作,實現UPSERT(INSERT OR UPDATE)的效果。INSERT ON CONFLICT
的文法格式如下。
INSERT INTO <table_name> [ AS <alias> ] [ ( <column_name> [, ...] ) ]
{ VALUES ( { <expression> } [, ...] ) [, ...] | <query> }
[ ON CONFLICT [ conflict_target ] conflict_action ]
where conflict_target is pk
ON CONSTRAINT constraint_name
and conflict_action is one of:
DO NOTHING
DO UPDATE SET { <column_name> = { <expression> } |
( <column_name> [, ...] ) = ( { <expression> } [, ...] ) |
} [, ...]
[ WHERE condition ]
參數說明如下表所示。
參數 | 描述 |
table_name | 插入資料的目標表名稱。 |
alias | 別名。目標表的替代名稱。 |
column_name | 目標表中目標列名稱。 |
DO NOTHING | InsertOrIgnore,即在指定列插入某行資料時,如果主鍵存在重複的行資料,則對該資料執行跳過操作。 |
DO UPDATE | InsertOrUpdate,即在指定列插入某行資料時,如果主鍵存在重複的行資料,則對該資料執行更新操作。 存在如下情況:
重要 |
expression | 對應列執行的相關運算式,您可以參考Postgres來設定運算式。 常用運算式 |
技術原理
INSERT ON CONFLICT的技術實現原理同UPDATE,詳情請參見UPDATE。不同表格儲存體格式(行存、列存、行列共存)在更新時的細節處理會略有不同,這就導致不同儲存模式的表在更新時,效能會有不同。而根據業務的需求,INSERT ON CONFLICT又可以分為InsertOrIgnore
、InsertOrReplace
、InsertOrUpdate
,三者的具體區別如下:
更新模式 | 說明 |
InsertOrIgnore | 寫入時忽略更新,結果表有主鍵,即時寫入時如果主鍵重複,丟棄後到的資料,通過 |
InsertOrUpdate | 寫入更新,結果表有主鍵,即時寫入時如果主鍵重複,按照主鍵更新。分為整行更新和部分列更新,部分列更新指如果寫入的一行資料不包含所有列,缺失的列不更新。通過 |
InsertOrReplace | 寫入覆蓋,結果表有主鍵,即時寫入時如果主鍵重複,按照主鍵更新。如果寫入的一行資料不包含所有列,缺失的列的資料補Null,需要通過 |
根據UPDATE的原理,當表設定不同的儲存格式時,不同UPDATE模式下的更新效能如下:
列存表不同寫入模式的效能排序如下。
結果表無主鍵效能最高。
結果表有主鍵時:
InsertOrIgnore > InsertOrReplace >= InsertOrUpdate(整行)> InsertOrUpdate(部分列)
。
行存表不同寫入模式的效能排序如下。
InsertOrReplace = InsertOrUpdate(整行)>= InsertOrUpdate(部分列) >= InsertOrIgnore
。
使用限制
INSERT ON CONFLICT
語句的條件必須包含所有主鍵。Hologres HQE在執行INSERT ON CONFLICT時,本身不會保序(保證順序),因此不能實現keep first、keep last的效果,都是keep any。但在實際應用中,如果資料來源有主鍵重複資料需要去重,建議使用keep last,命令如下:
--保留重複資料的最後一條資料 set hg_experimental_affect_row_multiple_times_keep_last = on;
使用樣本
INSERT ON CONFLICT
語句的樣本用法:說明Hologres從V2.1.17版本起支援Serverless Computing能力,針對巨量資料量離線匯入、大型ETL作業、外表巨量資料量查詢等情境,使用Serverless Computing執行該類任務可以直接使用額外的Serverless資源,避免使用執行個體自身資源,無需為執行個體預留額外的計算資源,顯著提升執行個體穩定性、減少OOM機率,且僅需為任務單獨付費。Serverless Computing詳情請參見Serverless Computing概述,Serverless Computing使用方法請參見Serverless Computing使用指南。
準備表和資料:
begin ; create table test1 ( a int NOT NULL PRIMARY KEY, b int, c int ); commit ; insert into test1 values (1,2,3);
不同情境下的使用樣本:
說明下面的每個情境樣本結果不相互依賴,沒有循序關聯性,都是基於上述已建立的表和資料的結果。
情境1:實現InsertOrIgnore,即主鍵重複不更新。
INSERT INTO test1 (a, b, c) VALUES (1, 1, 1) ON CONFLICT (a) DO NOTHING; --更新後test1表的資料為: a b c 1 2 3
情境2:實現InsertOrUpdate的整行更新,可以通過如下兩種方式實現。
方式1:在
SET..EXCLUDED
中列出所有的列。INSERT INTO test1 (a, b, c) VALUES (1, 1, 1) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b, c = EXCLUDED.c; --更新後test1表的資料為: a b c 1 1 1
方式2:使用
ROW(EXCLUDED.*)
代表更新所有列。INSERT INTO test1 (a, b, c)VALUES (1, 1, 1) ON CONFLICT (a) DO UPDATE SET (a,b,c) = ROW(EXCLUDED.*); --更新後test1表的資料為: a b c 1 1 1
情境3:實現InsertOrUpdate的部分列更新,即只更新指定列,缺失的列不更新。
--要實現部分列更新的效果,需要在set後列出想要更新的列 INSERT INTO test1 (a, b, c) VALUES (1, 1, 1) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; --表中c列不更新,更新後test1表的資料為: a b c 1 1 3
情境4:實現InsertOrReplace,即整行覆蓋,如果有缺失的列,缺失的列補null。
--如果要實現InsertOrReplace,且缺失的列補null,則需要在insert的值中手動補null。 INSERT INTO test1 (a, b,c) VALUES (1, 1,null) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b,c = EXCLUDED.c; --更新後test1表的資料為: a b c 1 1 \N
情境5:從另外一張test2表更新test1表資料。
--準備test2表和資料 CREATE TABLE test2 ( d int NOT NULL PRIMARY KEY, e int, f int ); INSERT INTO test2 VALUES (1, 5, 6); --將test2整表替換test1表相同主鍵的行 INSERT INTO test1 (a, b, c) SELECT d,e,f FROM test2 ON CONFLICT (a) DO UPDATE SET (a,b,c) = ROW (excluded.*); --更新後test1表資料如下: a b c 1 5 6 --將test2整表替換test1表相同主鍵的行,但調整了更新映射關係,即test2的e列更新到c列,f列更新到b列 INSERT INTO test1 (a, b, c) SELECT d,e,f FROM test2 ON CONFLICT (a) DO UPDATE SET (a,c,b) = ROW (excluded.*); --更新後test1表資料如下: a b c 1 6 5
行存表
INSERT ON CONFLICT
語句的最佳化:Hologres對行存表的更新情境實行了最佳化,建議您在使用時將UPDATE列的順序與INSERT的順序保持一致,並且更新為整行更新。
INSERT INTO test1 (a, b, c) SELECT d,e,f FROM test2 ON CONFLICT (a) DO UPDATE SET a = excluded.a, b = excluded.b, c = excluded.c; INSERT INTO test1 (a, b, c) SELECT d,e,f FROM test2 ON CONFLICT (a) DO UPDATE SET(a,b,c) = ROW (excluded.*)
常見報錯
問題現象
對資料來源執行
INSERT ON CONFLICT
語句時出現如下兩種報錯其中一個。報錯一:
duplicate key value violates unique constraint
。報錯二:
Update row with Key (xxx)=(yyy) multiple times
。報錯三(OOM問題):
Total memory used by all existing queries exceeded memory limitation
。
問題原因一:資料來源存在重複資料。
Hologres相容PostgreSQL,使用的也是標準PostgreSQL文法。在標準的PostgreSQL語義中,對資料來源執行
INSERT ON CONFLICT
語句時,資料來源不能包含重複資料,如果包含重複資料則會產生上述報錯。說明資料來源重複是指待插入的資料中包含重複資料,不是指待插入的資料與表裡的資料重複。
使用
INSERT ON CONFLICT
語句插入資料時包含重複資料,樣本語句如下。INSERT INTO test1 VALUES (1, 2, 3), (1, 2, 3) ON CONFLICT (a) DO UPDATE SET (a, b, c) = ROW (excluded.*);
解決方案:
如果資料來源包含重複資料,可以配置如下參數,保留重複資料的最後一條資料:
set hg_experimental_affect_row_multiple_times_keep_last = on;
問題原因二:資料來源因TTL到期出現重複資料。
資料來源中有表設定過表資料生命週期(TTL),表中有部分資料已經過了TTL,因TTL不是準確的時間,導致到期的資料未被清理,匯入時主鍵(PK)資料重複,從而出現報錯。
解決方案:
Hologres從 V1.3.23版本開始,通過以下命令能快速修正因TTL到期PK重複的資料。執行該命令後,系統會將該表PK重複的資料清理掉,清理策略預設為Keep Last即保留重複PK中最後一條寫入的PK資料,其餘重複PK資料進行清理。
說明原則上來說PK不會出現重複資料,因此該命令僅清理因TTL導致PK重複的資料。
該命令僅Hologres V1.3.23及以上版本使用,若執行個體版本較低,請升級執行個體。
call public.hg_remove_duplicated_pk('<schema>.<table_name>');
使用樣本:假設有兩個表,
tbl_1
為目標表,tbl_2
為源表且配置了TTL,時間設定為300s
。將tbl_2
的資料整行更新至tbl_1
,因TTL到期後,tbl_2
的主鍵重複,導致報錯。BEGIN; CREATE TABLE tbl_1 ( a int NOT NULL PRIMARY KEY, b int, c int ); CREATE TABLE tbl_2 ( d int NOT NULL PRIMARY KEY, e int, f int ); CALL set_table_property('tbl_2', 'time_to_live_in_seconds', '300'); COMMIT; INSERT INTO tbl_1 VALUES (1, 1, 1), (2, 3, 4); INSERT INTO tbl_2 VALUES (1, 5, 6); --過300s後再向tbl_2插入資料 INSERT INTO tbl_2 VALUES (1, 3, 6); --將tbl_2整表替換tbl_1表相同主鍵的行,PK因ttl重複了導致更新報錯 INSERT INTO tbl_1 (a, b, c) SELECT d,e,f FROM tbl_2 ON CONFLICT (a) DO UPDATE SET (a,b,c) = ROW (excluded.*); --錯誤原因:ERROR: internal error: Duplicate keys detected when building hash table. --guc清理tbl_2的PK重複資料,策略為keep last, call public.hg_remove_duplicated_pk('tbl_2'); --再重新匯入tbl_1資料,資料匯入成功
問題原因三:執行個體本身記憶體資源不足,無法支撐本次巨量資料量寫入任務。
解決方案:
推薦使用Hologres Serverless Computing能力執行本次巨量資料量寫入任務。Hologres從V2.1.17版本起支援Serverless Computing能力,針對巨量資料量離線匯入、大型ETL作業、外表巨量資料量查詢等情境,使用Serverless Computing執行該類任務可以直接使用額外的Serverless資源,避免使用執行個體自身資源,無需為執行個體預留額外的計算資源,顯著提升執行個體穩定性、減少OOM機率,且僅需為任務單獨付費。Serverless Computing詳情請參見Serverless Computing概述,Serverless Computing使用方法請參見Serverless Computing使用指南。
參考OOM常見問題排查指南中的方法處理。