Insert Into

Insert Into语句的使用方式和MySQL等数据库中Insert Into语句的使用方式类似。在Doris中,所有的数据写入都是一个独立的导入作业。因此,文本将Insert Into作为一种导入方式,介绍Insert Into的使用方法和最佳实践。

背景信息

说明

本文部分内容来源于Apache Doris,详情请参见Doris介绍

主要的Insert Into命令包含以下两种:

  • NSERT INTO tbl SELECT ...

  • INSERT INTO tbl (col1, col2, ...) VALUES (1, 2, ...), (1,3, ...);

    重要

    该命令仅用于Demo,请勿使用在测试或生产环境中。

导入操作及返回结果

Insert Into命令需要通过MySQL协议提交,创建导入请求会同步返回导入结果。

导入操作

Insert Into的使用示例如下:

INSERT INTO tbl2 WITH LABEL label1 SELECT * FROM tbl3;
INSERT INTO tbl1 VALUES ("qweasdzxcqweasdzxc"), ("a");
重要

当需要使用CTE(Common Table Expressions) 作为insert操作中的查询部分时,必须指定WITH LABEL和column list部分或者对CTE进行包装,示例如下。

INSERT INTO tbl1 WITH LABEL label1
WITH cte1 AS (SELECT * FROM tbl1), cte2 AS (SELECT * FROM tbl2)
SELECT k1 FROM cte1 JOIN cte2 WHERE cte1.k1 = 1;

INSERT INTO tbl1 (k1)
WITH cte1 AS (SELECT * FROM tbl1), cte2 AS (SELECT * FROM tbl2)
SELECT k1 FROM cte1 JOIN cte2 WHERE cte1.k1 = 1;

INSERT INTO tbl1 (k1)
select * from (
WITH cte1 AS (SELECT * FROM tbl1), cte2 AS (SELECT * FROM tbl2)
SELECT k1 FROM cte1 JOIN cte2 WHERE cte1.k1 = 1) as ret

示例中的参数说明,详情请参见INSERT INTO命令或者执行HELP INSERT来查看。

返回结果

Insert Into本身是一个SQL命令,其返回结果会根据执行结果的不同,分为以下几种:

  • 结果集为空

    如果Insert对应Select语句的结果集为空,返回示例如下。

    mysql> insert into tbl1 select * from empty_tbl;
    Query OK, 0 rows affected (0.02 sec)

    Query OK表示执行成功,0 rows affected表示没有数据被导入。

  • 结果集不为空

    在结果集不为空的情况下,返回结果分为如下几种情况:

    • Insert执行成功并可见。

      mysql> insert into tbl1 select * from tbl2;
      Query OK, 4 rows affected (0.38 sec)
      {'label':'insert_8510c568-9eda-4173-9e36-6adc7d35****', 'status':'visible', 'txnId':'4005'}
      
      mysql> 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'}
      
      mysql> insert into tbl1 select * from tbl2;
      Query OK, 2 rows affected, 2 warnings (0.31 sec)
      {'label':'insert_f0747f0e-7a35-46e2-affa-13a235f4****', 'status':'visible', 'txnId':'4005'}
      
      mysql> insert into tbl1 select * from tbl2;
      Query OK, 2 rows affected, 2 warnings (0.31 sec)
      {'label':'insert_f0747f0e-7a35-46e2-affa-13a235f4****', 'status':'committed', 'txnId':'4005'}

      Query OK表示执行成功,4 rows affected表示总共有4行数据被导入,2 warnings表示被过滤的行数。

      同时会返回一个JSON串,示例如下:

      {'label':'my_label1', 'status':'visible', 'txnId':'4005'}
      {'label':'insert_f0747f0e-7a35-46e2-affa-13a235f4****', 'status':'committed', 'txnId':'4005'}
      {'label':'my_label1', 'status':'visible', 'txnId':'4005', 'err':'some other error'}
      • label:您指定的Label或自动生成的Label。Label是该Insert Into导入作业的标识,每个导入作业都有一个在单database内部唯一的Label。

      • status:表示导入数据是否可见。如果可见,显示visible,如果不可见,显示committed。

      • txnId:该Insert对应的导入事务的id。

      • err:显示一些其他非预期错误。

      您可以使用SHOW LOAD语句查看被过滤的行,示例如下。返回结果中的URL可以用于查询错误的数据。

      show load where label="xxx";

      数据不可见是一个临时状态,数据最终是一定可见的。可以通过SHOW TRANSACTION语句查看这批数据的可见状态,示例如下。返回结果中的TransactionStatus列如果为visible,则表述数据可见。

      show transaction where id=4005;
    • Insert执行失败。

      执行失败表示没有任何数据被成功导入,返回示例如下。

      mysql> insert into tbl1 select * from tbl2 where k1 = "a";
      ERROR 1064 (HY000): all partitions have no load data. url: http://10.74.xx.xx:8042/api/_load_error_log?file=__shard_2/error_log_insert_stmt_ba8bb9e158e4879-ae8de8507c0b****_ba8bb9e158e4879_ae8de8507c0b****

      其中ERROR 1064 (HY000): all partitions have no load data显示失败原因。后面的url可以用于查询错误的数据。

综上,对于Insert操作返回结果的正确处理逻辑为:

  • 如果返回结果为ERROR 1064 (HY000),则表示导入失败。

  • 如果返回结果为Query OK,则表示执行成功。

    • 如果rows affected为0,表示结果集为空,没有数据被导入。

    • 如果rows affected大于0:

      • 如果status为committed,表示数据不可见,需要通过SHOW TRANSACTION语句查看状态直到visible。

      • 如果status为visible,表示数据导入成功。

      • 如果warnings大于0,表示有数据被过滤,可以通过SHOW LOAD语句获取URL查看被过滤的行。

SHOW LAST INSERT

上面介绍了如何根据Insert操作的返回结果进行后续处理。但一些语言的MySQL类库中很难获取返回结果中的JSON字符串。因此,Doris还提供了SHOW LAST INSERT命令来显式的获取最近一次Insert操作的结果。当执行完一个Insert操作后,可以在同一Session连接中执行SHOW LAST INSERT,该命令会返回最近一次Insert操作的结果。例如:

mysql> show last insert\G
*************************** 1. row ***************************
    TransactionId: 640**
            Label: insert_ba8f33aea9544866-8ed77e2844d0****
         Database: default_cluster:db1
            Table: t1
TransactionStatus: VISIBLE
       LoadedRows: 2
     FilteredRows: 0

该命令会返回Insert以及对应事务的详细信息。因此,您可以在每次执行完Insert操作后,继续执行show last insert命令来获取Insert的结果。

重要

该命令只会返回在同一Session连接中,最近一次Insert操作的结果。如果连接断开或更换了新的连接,则将返回空集。

相关系统配置

FE配置

timeout:导入任务的超时时间(以秒为单位)。导入任务在设定的timeout时间内未完成则会被系统取消,变成CANCELLED。目前Insert Into暂不支持自定义导入的timeout时间,所有Insert Into导入的超时时间是统一的,默认的timeout 时间为1小时。如果导入的源文件无法在规定时间内完成导入,则需要调整FE的参数insert_load_default_timeout_second。同时Insert Into语句受到Session变量query_timeout的限制,可以通过SET query_timeout = xxx; 来增加超时时间,单位是秒。

Session变量

  • enable_insert_strict:Insert Into导入本身不能控制导入可容忍的错误率。您只能通过Session参数enable_insert_strict控制。当该参数设置为false时,表示至少有一条数据被正确导入,则返回成功;如果有失败数据,则还会返回一个Label。当该参数设置为true时,表示如果有一条数据错误,则导入失败。默认为false。可通过SET enable_insert_strict = true;来设置。

  • query_timeout:Insert Into本身也是一个SQL命令,因此也受到Session变量query_timeout的限制。可以通过SET query_timeout = xxx;来增加超时时间,单位是秒。

最佳实践

应用场景

  • 仅导入几条测试数据,验证Doris系统的功能,适合使用INSERT INTO VALUES语法,其与MySql语法相同。

  • 将已经在Doris表中的数据进行ETL转换并导入到一个新的Doris表中,适合使用INSERT INTO SELECT语法。

  • 创建一种外部表,如MySQL外部表映射一张MySQL系统中的表,或者创建Broker外部表来映射HDFS上的数据文件。然后通过INSERT INTO SELECT语法将外部表中的数据导入到Doris表中存储。

数据量

Insert Into对数据量没有限制,也支持大数据量导入。但Insert Into有默认的超时时间,如果您预估的导入数据量过大,需要修改系统的Insert Into导入超时时间。

例如,当导入数据量为36 GB时,导入时间约小于等于3600s*10 M/s。其中10 M/s是最大导入限速,您需要根据当前集群情况计算出平均的导入速度来替换公式中的10 M/s。

完整示例

在数据库sales中有表store_sales,又新建表bj_store_sales,希望将store_sales中销售记录在bj的数据导入到表bj_store_sales中,导入的数据量约为10 GB,当前集群的平均导入速度约为5 M/s。

store_sales schema:
(id, total, user_id, sale_timestamp, region)

bj_store_sales schema:
(id, total, user_id, sale_timestamp)
  1. 判断是否要修改Insert Into的默认超时时间。

    计算导入的大概时间
    10 GB / 5 M/s = 2000s
    
    修改FE配置
    insert_load_default_timeout_second = 2000
  2. 创建导入任务。

    由于希望将一张表中的数据做ETL并导入到目标表中,所以应该使用Insert into query_stmt方式导入。

    INSERT INTO bj_store_sales WITH LABEL `label` SELECT id, total, user_id, sale_timestamp FROM store_sales where region = "bj";