Insert Into
云數(shù)據(jù)庫(kù) SelectDB 版兼容標(biāo)準(zhǔn)SQL語(yǔ)法,可通過(guò)標(biāo)準(zhǔn)的Insert Into方式導(dǎo)入數(shù)據(jù)。
背景信息
Insert Into命令是MySQL等數(shù)據(jù)庫(kù)中常用的數(shù)據(jù)導(dǎo)入方式。云數(shù)據(jù)庫(kù) SelectDB 版兼容標(biāo)準(zhǔn)SQL語(yǔ)法,支持通過(guò)Insert Into命令導(dǎo)入數(shù)據(jù)。包含以下兩種:
Insert Into tbl SELECT ...
Insert Into tbl (col1, col2, ...) VALUES (1, 2, ...), (1,3, ...);
重要此命令不建議在生產(chǎn)環(huán)境中使用。
Insert Into Select
通過(guò)SelectDB提供的大量SQL函數(shù)、聯(lián)邦查詢能力,Insert Into Select可以對(duì)SelectDB內(nèi)部數(shù)據(jù)、外部數(shù)據(jù)湖數(shù)據(jù)等進(jìn)行高效的計(jì)算處理,然后導(dǎo)入SelectDB的新表中,用來(lái)進(jìn)一步進(jìn)行數(shù)據(jù)分析服務(wù)。
內(nèi)表數(shù)據(jù)ETL
如果數(shù)據(jù)已經(jīng)在SelectDB表中,可通過(guò)Insert Into Select進(jìn)行數(shù)據(jù)ETL轉(zhuǎn)換,然后導(dǎo)入到一個(gè)新表中。示例如下。
INSERT INTO bj_store_sales
SELECT id, total, user_id, sale_timestamp FROM store_sales WHERE region = "bj";
數(shù)據(jù)湖數(shù)據(jù)同步
如果數(shù)據(jù)在數(shù)據(jù)湖等外部系統(tǒng)中,可以在SelectDB中創(chuàng)建Catalog,映射到數(shù)據(jù)湖等外部系統(tǒng)中的數(shù)據(jù),然后通過(guò)Insert Into Select將其中的數(shù)據(jù)導(dǎo)入到SelectDB表中。SelectDB支持對(duì)接Hive、Iceberg、Hudi、Elasticsearch、JDBC等數(shù)據(jù)源,詳細(xì)請(qǐng)參見湖倉(cāng)一體。
如下以Hive數(shù)據(jù)源為例,介紹如何同步數(shù)據(jù)湖數(shù)據(jù)到SelectDB中。
創(chuàng)建Hive Catalog,即可通過(guò)聯(lián)邦查詢?cè)L問(wèn)Hive中的數(shù)據(jù),示例如下。
CREATE CATALOG test_catalog comment 'hive catalog' PROPERTIES (
'type'='hms',
'hive.metastore.uris' = 'thrift://127.0.0.1:7004',
'dfs.nameservices'='HANN',
'dfs.ha.namenodes.HANN'='nn1,nn2',
'dfs.namenode.rpc-address.HANN.nn1'='nn1_host:rpc_port',
'dfs.namenode.rpc-address.HANN.nn2'='nn2_host:rpc_port',
'dfs.client.failover.proxy.provider.HANN'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);
通過(guò)Insert Into Select,同步Hive數(shù)據(jù)到SelectDB中,并指定導(dǎo)入作業(yè)唯一標(biāo)識(shí)Label。
INSERT INTO bj_store_sales
WITH LABEL test_label
SELECT id, total, user_id, sale_timestamp FROM test_catalog.test_db.store_sales WHERE region = "bj";
Insert Into Values
Insert Into Values是MySQL等數(shù)據(jù)庫(kù)中常用的數(shù)據(jù)寫入方式,建議僅用于測(cè)試環(huán)境的使用。典型的使用方式是直接通過(guò)SQL客戶端、JDBC程序發(fā)送數(shù)據(jù)寫入請(qǐng)求。
創(chuàng)建待導(dǎo)入的SelectDB數(shù)據(jù)表如下。
CREATE TABLE test_table
(
id int,
name varchar(50),
age int
)
DISTRIBUTED BY HASH(id) BUCKETS 4
PROPERTIES("replication_num" = "1");
SQL示例
BEGIN;
INSERT INTO db.tbl VALUES(),(),();
INSERT INTO db.tbl VALUES(),(),();
INSERT INTO db.tbl VALUES(),(),();
COMMIT;
JDBC程序示例
public static void main(String[] args) throws Exception {
// 單次導(dǎo)入插入語(yǔ)句的數(shù)量。
int insertNum = 10;
// 單條插入攢批的數(shù)量。
int batchSize = 10000;
String URL="jdbc:mysql://<IP地址>:<MySQL協(xié)議端口>/test_db?useLocalSessionState=true"; // VPC ID所對(duì)應(yīng)的IP地址。您可以登錄VPC控制臺(tái)在VPC列表中找到目標(biāo)VPC ID所對(duì)應(yīng)的IP地址。
Connection connection = DriverManager.getConnection(URL, "admin", "password"); // 云數(shù)據(jù)庫(kù)SelectDB版實(shí)例的賬號(hào)和密碼。
Statement statement = connection.createStatement();
statement.execute("begin");
// 拼接多條插入語(yǔ)句。
for (int num = 0; num < insertNum; num++) {
StringBuilder sql = new StringBuilder();
sql.append("Insert Into test_tbl values ");
for(int i = 0; i < batchSize; i++){
if(i > 0){
sql.append(",");
}
// 拼接一行數(shù)據(jù),如:姓名,年齡??筛鶕?jù)具體業(yè)務(wù)修改。
sql.append("('zhangsan',18)");
}
//add sql to batch: Insert Into tbl values(),(),()
statement.addBatch(sql.toString());
}
statement.addBatch("commit");
statement.executeBatch();
// 關(guān)閉資源。
statement.close();
connection.close();
}
最佳實(shí)踐
查看返回結(jié)果。
Insert Into操作是一個(gè)同步操作,返回結(jié)果即表示操作結(jié)束。您需要根據(jù)返回結(jié)果的不同,進(jìn)行對(duì)應(yīng)的處理。
執(zhí)行成功,結(jié)果集為空。
如果 insert 對(duì)應(yīng) select 語(yǔ)句的結(jié)果集為空,則返回如下:
INSERT INTO tbl1 SELECT * FROM empty_tbl; Query OK, 0 rows affected (0.02 sec)
Query OK
表示執(zhí)行成功。0 rows affected
表示沒有數(shù)據(jù)被導(dǎo)入。執(zhí)行成功,結(jié)果集不為空。
在結(jié)果集不為空的情況下。返回結(jié)果分為如下幾種情況。
INSERT INTO tbl1 SELECT * FROM tbl2; Query OK, 4 rows affected (0.38 sec) {'label':'insert_8510c568-9eda-****-9e36-6adc7d35291c', 'status':'visible', 'txnId':'4005'} INSERT INTO tbl1 with label my_label1 SELECT * FROM tbl2; Query OK, 4 rows affected (0.38 sec) {'label':'my_label1', 'status':'visible', 'txnId':'4005'} INSERT INTO tbl1 SELECT * FROM tbl2; Query OK, 2 rows affected, 2 warnings (0.31 sec) {'label':'insert_f0747f0e-7a35-****-affa-13a235f4020d', 'status':'visible', 'txnId':'4005'} INSERT INTO tbl1 SELECT * FROM tbl2; Query OK, 2 rows affected, 2 warnings (0.31 sec) {'label':'insert_f0747f0e-7a35-****-affa-13a235f4020d', 'status':'committed', 'txnId':'4005'}
其中,
Query OK
表示執(zhí)行成功。4 rows affected
表示總共有4行數(shù)據(jù)被導(dǎo)入。2 warnings
表示被過(guò)濾的行數(shù)。同時(shí)會(huì)返回一個(gè) JSON 串。{'label':'my_label1', 'status':'visible', 'txnId':'4005'} {'label':'insert_f0747f0e-7a35-****-affa-13a235f4020d', 'status':'committed', 'txnId':'4005'} {'label':'my_label1', 'status':'visible', 'txnId':'4005', 'err':'some other error'}
其中,
label
為您指定的 label 或自動(dòng)生成的label,label是該Insert Into導(dǎo)入作業(yè)的標(biāo)識(shí),每個(gè)導(dǎo)入作業(yè),都有一個(gè)在單database內(nèi)部唯一的label。status
表示導(dǎo)入數(shù)據(jù)是否可見,如果可見顯示visible
,如果不可見顯示committed
。txnId
為這個(gè)insert對(duì)應(yīng)的導(dǎo)入事務(wù)的id。err
字段會(huì)顯示一些其他非預(yù)期錯(cuò)誤。當(dāng)需要查看被過(guò)濾的行時(shí),您可以通過(guò)如下語(yǔ)句:
SHOW LOAD WHERE label="xxx";
返回結(jié)果中的 URL 可以用于查詢錯(cuò)誤的數(shù)據(jù),具體見后面查看錯(cuò)誤行小結(jié)。數(shù)據(jù)不可見是一個(gè)臨時(shí)狀態(tài),這批數(shù)據(jù)最終是一定可見的。可以通過(guò)如下語(yǔ)句查看這批數(shù)據(jù)的可見狀態(tài):
SHOW TRANSACTION WHERE id=4005;
返回結(jié)果中的
TransactionStatus
列如果為visible
,則表述數(shù)據(jù)可見。執(zhí)行失敗。
執(zhí)行失敗表示沒有任何數(shù)據(jù)被成功導(dǎo)入,并返回如下:
INSERT INTO tbl1 SELECT * FROM tbl2 WHERE k1 = "a"; ERROR 1064 (HY000): all partitions have no load data. url: http://10.74.167.16:8042/api/_load_error_log?file=__shard_2/error_log_insert_stmt_ba8bb9e158e4879-ae8de8507c0bf8a2_ba8bb9e158e4879_ae8de8507c0bf8a2
其中
ERROR 1064 (HY000): all partitions have no load data
顯示失敗原因。通過(guò)其中的 URL 可以用于查詢錯(cuò)誤的數(shù)據(jù):SHOW LOAD WARNINGS ON "url";
超時(shí)時(shí)間。
Insert Into操作的超時(shí)時(shí)間由會(huì)話變量
query_timeout
控制,默認(rèn)為5分鐘。超時(shí)則作業(yè)會(huì)被取消。Label和原子性。
Insert Into操作同樣能夠保證導(dǎo)入的原子性。當(dāng)需要使用
CTE(Common Table Expressions)
作為 Insert Into操作中的查詢部分時(shí),必須指定WITH LABEL
和column
部分。過(guò)濾閾值。
與其他導(dǎo)入方式不同,Insert Into操作不能指定過(guò)濾閾值(
max_filter_ratio
)。默認(rèn)的過(guò)濾閾值為 1,即有錯(cuò)誤行都可以被忽略。對(duì)于有要求數(shù)據(jù)不能夠被過(guò)濾的業(yè)務(wù)場(chǎng)景,可以通過(guò)設(shè)置會(huì)話變量
enable_insert_strict
為true
來(lái)確保當(dāng)有數(shù)據(jù)被過(guò)濾掉的時(shí)候,Insert Into
不會(huì)被執(zhí)行成功。性能問(wèn)題。
不建議使用
Insert Into Values
方式進(jìn)行數(shù)據(jù)導(dǎo)入,尤其是大數(shù)據(jù)的線上生產(chǎn)環(huán)境。如果必須這樣使用,請(qǐng)將多行數(shù)據(jù)合并到一個(gè)Insert Into語(yǔ)句中進(jìn)行批量提交,單個(gè)批次建議1000~1000000條數(shù)據(jù)。部分列更新。
Insert Into的默認(rèn)行為是整行寫入。在Unique數(shù)據(jù)模型MOW實(shí)現(xiàn)方式中,客戶可按需開啟部分列更新功能,需要設(shè)置如下會(huì)話變量:
set enable_unique_key_partial_update=true
需要注意的是,控制Insert Into語(yǔ)句是否開啟嚴(yán)格模式的會(huì)話變量
enable_insert_strict
的默認(rèn)值為true,意味著不允許更新不存在的key。所以,在使用Insert Into語(yǔ)句進(jìn)行部分列更新時(shí),如果希望能插入不存在的key,需要同時(shí)將enable_insert_strict
設(shè)置為false。