本文為您介紹在Hologres中INSERT ON CONFLICT語句的用法。
應用場景
INSERT ON CONFLICT
命令適用于通過SQL方式導入數據的場景。
使用數據集成或Flink寫入數據時,如果需要對主鍵重復的行數據執行更新或跳過操作,則需進行如下配置:
通過DataWorks的數據集成導入數據。
數據集成已內置
INSERT ON CONFLICT
功能,該功能的實現原理請參見Hologres Writer。同時,您需要進行如下配置:離線同步數據時,寫入沖突策略選擇忽略(Ignore)或者更新(Replace)。
實時同步數據時,寫入沖突策略選擇忽略(Ignore)或者更新(Replace)。
說明同步數據時,Hologres的表均需要設置主鍵,才能更新數據。
通過Flink寫入數據。
通過Flink寫入數據默認寫入沖突策略使用
InsertOrIgnore
(保留首次出現的數據,忽略后續所有數據),但是需要您在Hologres建表時設置主鍵。詳情請參見Flink全托管概述。如果使用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。 常用表達式 |
技術原理
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常見問題排查指南中的方法處理。