Apache Doris 入门教程09:数据导入之导入场景

支持的数据源​

Doris 提供多种数据导入方案,可以针对不同的数据源进行选择不同的数据导入方式。

按场景划分​

数据源 导入方式
对象存储(s3),HDFS 使用Broker导入数据
本地文件 导入本地数据
Kafka 订阅Kafka数据
Mysql、PostgreSQL,Oracle,SQLServer 通过外部表同步数据
通过JDBC导入 使用JDBC同步数据
导入JSON格式数据 JSON格式数据导入

按导入方式划分​

导入方式名称 使用方式
Spark Load 通过Spark导入外部数据
Broker Load 通过Broker导入外部存储数据
Stream Load 流式导入数据(本地文件及内存数据)
Routine Load 导入Kafka数据
Insert Into 外部表通过INSERT方式导入数据
S3 Load S3协议的对象存储数据导入
MySQL Load MySQL客户端导入本地数据

支持的数据格式​

不同的导入方式支持的数据格式略有不同。

导入方式 支持的格式
Broker Load parquet、orc、csv、gzip
Stream Load csv、json、parquet、orc
Routine Load csv、json
MySQL Load csv

导入说明​

Apache Doris 的数据导入实现有以下共性特征,这里分别介绍,以帮助大家更好的使用数据导入功能

导入的原子性保证​

Doris 的每一个导入作业,不论是使用 Broker Load 进行批量导入,还是使用 INSERT 语句进行单条导入,都是一个完整的事务操作。导入事务可以保证一批次内的数据原子生效,不会出现部分数据写入的情况。

同时,一个导入作业都会有一个 Label。这个 Label 是在一个数据库(Database)下唯一的,用于唯一标识一个导入作业。Label 可以由用户指定,部分导入功能也会由系统自动生成。

Label 是用于保证对应的导入作业,仅能成功导入一次。一个被成功导入的 Label,再次使用时,会被拒绝并报错 Label already used。通过这个机制,可以在 Doris 侧做到 At-Most-Once 语义。如果结合上游系统的 At-Least-Once 语义,则可以实现导入数据的 Exactly-Once 语义。

关于原子性保证的最佳实践,可以参阅 导入事务和原子性。

同步及异步导入​

导入方式分为同步和异步。对于同步导入方式,返回结果即表示导入成功还是失败。而对于异步导入方式,返回成功仅代表作业提交成功,不代表数据导入成功,需要使用对应的命令查看导入作业的运行状态。

导入array类型​

向量化场景才能支持array函数,非向量化场景不支持。

如果想要应用array函数导入数据,则应先启用向量化功能;然后需要根据array函数的参数类型将输入参数列转换为array类型;最后,就可以继续使用array函数了。

例如以下导入,需要先将列b14和列a13先cast成array类型,再运用array_union函数。

LOAD LABEL label_03_14_49_34_898986_19090452100 ( 
  DATA INFILE("hdfs://test.hdfs.com:9000/user/test/data/sys/load/array_test.data") 
  INTO TABLE `test_array_table` 
  COLUMNS TERMINATED BY "|" (`k1`, `a1`, `a2`, `a3`, `a4`, `a5`, `a6`, `a7`, `a8`, `a9`, `a10`, `a11`, `a12`, `a13`, `b14`) 
  SET(a14=array_union(cast(b14 as array), cast(a13 as array))) WHERE size(a2) > 270) 
  WITH BROKER "hdfs" ("username"="test_array", "password"="") 
  PROPERTIES( "max_filter_ratio"="0.8" );

 

导入本地数据

本文档主要介绍如何从客户端导入本地的数据。

目前Doris支持两种从本地导入数据的模式:

  1. Stream Load
  2. MySql Load

Stream Load​

Stream Load 用于将本地文件导入到 Doris 中。

不同于其他命令的提交方式,Stream Load 是通过 HTTP 协议与 Doris 进行连接交互的。

该方式中涉及 HOST:PORT 应为 HTTP 协议端口。

  • BE 的 HTTP 协议端口,默认为 8040。
  • FE 的 HTTP 协议端口,默认为 8030。但须保证客户端所在机器网络能够联通 BE 所在机器。

本文文档我们以 curl 命令为例演示如何进行数据导入。

文档最后,我们给出一个使用 Java 导入数据的代码示例

导入数据​

Stream Load 的请求体如下:

PUT /api/{db}/{table}/_stream_load

  1. 创建一张表

    通过 CREATE TABLE 命令在demo创建一张表用于存储待导入的数据。具体的导入方式请查阅 CREATE TABLE 命令手册。示例如下:

    CREATE TABLE IF NOT EXISTS load_local_file_test
    (
        id INT,
        age TINYINT,
        name VARCHAR(50)
    )
    unique key(id)
    DISTRIBUTED BY HASH(id) BUCKETS 3;
    

  2. 导入数据

    执行以下 curl 命令导入本地文件:

     curl -u user:passwd -H "label:load_local_file_test" -T /path/to/local/demo.txt http://host:port/api/demo/load_local_file_test/_stream_load
    

    • user:passwd 为在 Doris 中创建的用户。初始用户为 admin / root,密码初始状态下为空。
    • host:port 为 BE 的 HTTP 协议端口,默认是 8040,可以在 Doris 集群 WEB UI页面查看。
    • label: 可以在 Header 中指定 Label 唯一标识这个导入任务。

    关于 Stream Load 命令的更多高级操作,请参阅 Stream Load 命令文档。

  3. 等待导入结果

    Stream Load 命令是同步命令,返回成功即表示导入成功。如果导入数据较大,可能需要较长的等待时间。示例如下:

    {
        "TxnId": 1003,
        "Label": "load_local_file_test",
        "Status": "Success",
        "Message": "OK",
        "NumberTotalRows": 1000000,
        "NumberLoadedRows": 1000000,
        "NumberFilteredRows": 1,
        "NumberUnselectedRows": 0,
        "LoadBytes": 40888898,
        "LoadTimeMs": 2144,
        "BeginTxnTimeMs": 1,
        "StreamLoadPutTimeMs": 2,
        "ReadDataTimeMs": 325,
        "WriteDataTimeMs": 1933,
        "CommitAndPublishTimeMs": 106,
        "ErrorURL": "http://192.168.1.1:8042/api/_load_error_log?file=__shard_0/error_log_insert_stmt_db18266d4d9b4ee5-abb00ddd64bdf005_db18266d4d9b4ee5_abb00ddd64bdf005"
    }
    

    • Status 字段状态为 Success 即表示导入成功。
    • 其他字段的详细介绍,请参阅 Stream Load 命令文档。

导入建议​

  • Stream Load 只能导入本地文件。
  • 建议一个导入请求的数据量控制在 1 - 2 GB 以内。如果有大量本地文件,可以分批并发提交。

Java 代码示例​

这里通过一个简单的 JAVA 示例来执行 Stream Load:

package demo.doris;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/*
这是一个 Doris Stream Load 示例,需要依赖

    org.apache.httpcomponents
    httpclient
    4.5.13

 */
public class DorisStreamLoader {
    //可以选择填写 FE 地址以及 FE 的 http_port,但须保证客户端和 BE 节点的连通性。
    private final static String HOST = "your_host";
    private final static int PORT = 8040;
    private final static String DATABASE = "db1";   // 要导入的数据库
    private final static String TABLE = "tbl1";     // 要导入的表
    private final static String USER = "root";      // Doris 用户名
    private final static String PASSWD = "";        // Doris 密码
    private final static String LOAD_FILE_NAME = "/path/to/1.txt"; // 要导入的本地文件路径

    private final static String loadUrl = String.format("http://%s:%s/api/%s/%s/_stream_load",
            HOST, PORT, DATABASE, TABLE);

    private final static HttpClientBuilder httpClientBuilder = HttpClients
            .custom()
            .setRedirectStrategy(new DefaultRedirectStrategy() {
                @Override
                protected boolean isRedirectable(String method) {
                    // 如果连接目标是 FE,则需要处理 307 redirect。
                    return true;
                }
            });

    public void load(File file) throws Exception {
        try (CloseableHttpClient client = httpClientBuilder.build()) {
            HttpPut put = new HttpPut(loadUrl);
            put.setHeader(HttpHeaders.EXPECT, "100-continue");
            put.setHeader(HttpHeaders.AUTHORIZATION, basicAuthHeader(USER, PASSWD));

            // 可以在 Header 中设置 stream load 相关属性,这里我们设置 label 和 column_separator。
            put.setHeader("label","label1");
            put.setHeader("column_separator",",");

            // 设置导入文件。
            // 这里也可以使用 StringEntity 来传输任意数据。
            FileEntity entity = new FileEntity(file);
            put.setEntity(entity);

            try (CloseableHttpResponse response = client.execute(put)) {
                String loadResult = "";
                if (response.getEntity() != null) {
                    loadResult = EntityUtils.toString(response.getEntity());
                }

                final int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200) {
                    throw new IOException(
                            String.format("Stream load failed. status: %s load result: %s", statusCode, loadResult));
                }

                System.out.println("Get load result: " + loadResult);
            }
        }
    }

    private String basicAuthHeader(String username, String password) {
        final String tobeEncode = username + ":" + password;
        byte[] encoded = Base64.encodeBase64(tobeEncode.getBytes(StandardCharsets.UTF_8));
        return "Basic " + new String(encoded);
    }

    public static void main(String[] args) throws Exception{
        DorisStreamLoader loader = new DorisStreamLoader();
        File file = new File(LOAD_FILE_NAME);
        loader.load(file);
    }
}

注意:这里 http client 的版本要是4.5.13


   org.apache.httpcomponents
   httpclient
   4.5.13

MySql LOAD​

SinceVersion devMySql LOAD样例

导入数据​

  1. 创建一张表

    通过 CREATE TABLE 命令在demo创建一张表用于存储待导入的数据

    CREATE TABLE IF NOT EXISTS load_local_file_test
    (
    id INT,
    age TINYINT,
    name VARCHAR(50)
    )
    unique key(id)
    DISTRIBUTED BY HASH(id) BUCKETS 3;
    

  2. 导入数据 在MySql客户端下执行以下 SQL 命令导入本地文件:

    LOAD DATA
    LOCAL
    INFILE '/path/to/local/demo.txt'
    INTO TABLE demo.load_local_file_test
    

    关于 MySQL Load 命令的更多高级操作,请参阅 MySQL Load 命令文档。

  3. 等待导入结果

    MySql Load 命令是同步命令,返回成功即表示导入成功。如果导入数据较大,可能需要较长的等待时间。示例如下:

    Query OK, 1 row affected (0.17 sec)
    Records: 1  Deleted: 0  Skipped: 0  Warnings: 0
    

    • 如果出现上述结果, 则表示导入成功。导入失败, 会抛出错误,并在客户端显示错误原因
    • 其他字段的详细介绍,请参阅 MySQL Load 命令文档。

导入建议​

  • MySql Load 只能导入本地文件(可以是客户端本地或者连接的FE节点本地), 而且支持CSV格式。
  • 建议一个导入请求的数据量控制在 1 - 2 GB 以内。如果有大量本地文件,可以分批并发提交。

外部存储数据导入

本文档主要介绍如何导入外部系统中存储的数据。例如(HDFS,所有支持S3协议的对象存储)

HDFS LOAD​

准备工作​

上传需要导入的文件到HDFS上,具体命令可参阅HDFS上传命令

开始导入​

Hdfs load 创建导入语句,导入方式和Broker Load 基本相同,只需要将 WITH BROKER broker_name () 语句替换成如下部分

  LOAD LABEL db_name.label_name 
  (data_desc, ...)
  WITH HDFS
  [PROPERTIES (key1=value1, ... )]

  1. 创建一张表

    通过 CREATE TABLE 命令在demo创建一张表用于存储待导入的数据。具体的导入方式请查阅 CREATE TABLE 命令手册。示例如下:

    CREATE TABLE IF NOT EXISTS load_hdfs_file_test
    (
        id INT,
        age TINYINT,
        name VARCHAR(50)
    )
    unique key(id)
    DISTRIBUTED BY HASH(id) BUCKETS 3;
    

  2. 导入数据执行以下命令导入HDFS文件:

    LOAD LABEL demo.label_20220402
        (
        DATA INFILE("hdfs://host:port/tmp/test_hdfs.txt")
        INTO TABLE `load_hdfs_file_test`
        COLUMNS TERMINATED BY "\t"            
        (id,age,name)
        )
        with HDFS (
        "fs.defaultFS"="hdfs://testFs",
        "hdfs_user"="user"
        )
        PROPERTIES
        (
        "timeout"="1200",
        "max_filter_ratio"="0.1"
        );
    

    关于参数介绍,请参阅Broker Load,HA集群的创建语法,通过HELP BROKER LOAD查看

  3. 查看导入状态

    Broker load 是一个异步的导入方式,具体导入结果可以通过SHOW LOAD命令查看

    mysql> show load order by createtime desc limit 1\G;
    *************************** 1. row ***************************
             JobId: 41326624
             Label: broker_load_2022_04_15
             State: FINISHED
          Progress: ETL:100%; LOAD:100%
              Type: BROKER
           EtlInfo: unselected.rows=0; dpp.abnorm.ALL=0; dpp.norm.ALL=27
          TaskInfo: cluster:N/A; timeout(s):1200; max_filter_ratio:0.1
          ErrorMsg: NULL
        CreateTime: 2022-04-01 18:59:06
      EtlStartTime: 2022-04-01 18:59:11
     EtlFinishTime: 2022-04-01 18:59:11
     LoadStartTime: 2022-04-01 18:59:11
    LoadFinishTime: 2022-04-01 18:59:11
               URL: NULL
        JobDetails: {"Unfinished backends":{"5072bde59b74b65-8d2c0ee5b029adc0":[]},"ScannedRows":27,"TaskNumber":1,"All backends":{"5072bde59b74b65-8d2c0ee5b029adc0":[36728051]},"FileNumber":1,"FileSize":5540}
    1 row in set (0.01 sec)
    

S3 LOAD​

从0.14 版本开始,Doris 支持通过S3协议直接从支持S3协议的在线存储系统导入数据。

下面主要介绍如何导入 AWS S3 中存储的数据。也支持导入其他支持S3协议的对象存储系统导入。

适用场景​

  • 源数据在 支持S3协议的存储系统中,如 S3 等。
  • 数据量在 几十到百GB 级别。

准备工作​

  1. 准本AK 和 SK 首先需要找到或者重新生成 AWS Access keys,可以在 AWS console 的 My Security Credentials 找到生成方式, 如下图所示: AK_SK 选择 Create New Access Key 注意保存生成 AK和SK.
  2. 准备 REGION 和 ENDPOINT REGION 可以在创建桶的时候选择也可以在桶列表中查看到。ENDPOINT 可以通过如下页面通过 REGION 查到 AWS 文档

其他云存储系统可以相应的文档找到与S3兼容的相关信息

开始导入​

导入方式和 Broker Load 基本相同,只需要将 WITH BROKER broker_name () 语句替换成如下部分

    WITH S3
    (
        "AWS_ENDPOINT" = "AWS_ENDPOINT",
        "AWS_ACCESS_KEY" = "AWS_ACCESS_KEY",
        "AWS_SECRET_KEY"="AWS_SECRET_KEY",
        "AWS_REGION" = "AWS_REGION"
    )

完整示例如下

    LOAD LABEL example_db.exmpale_label_1
    (
        DATA INFILE("s3://your_bucket_name/your_file.txt")
        INTO TABLE load_test
        COLUMNS TERMINATED BY ","
    )
    WITH S3
    (
        "AWS_ENDPOINT" = "AWS_ENDPOINT",
        "AWS_ACCESS_KEY" = "AWS_ACCESS_KEY",
        "AWS_SECRET_KEY"="AWS_SECRET_KEY",
        "AWS_REGION" = "AWS_REGION"
    )
    PROPERTIES
    (
        "timeout" = "3600"
    );

常见问题​

  1. S3 SDK 默认使用 virtual-hosted style 方式。但某些对象存储系统可能没开启或没支持 virtual-hosted style 方式的访问,此时我们可以添加 use_path_style 参数来强制使用 path style 方式:
  WITH S3
  (
        "AWS_ENDPOINT" = "AWS_ENDPOINT",
        "AWS_ACCESS_KEY" = "AWS_ACCESS_KEY",
        "AWS_SECRET_KEY"="AWS_SECRET_KEY",
        "AWS_REGION" = "AWS_REGION",
        "use_path_style" = "true"
  )

SinceVersion 1.2

  1. 支持使用临时秘钥(TOKEN) 访问所有支持 S3 协议的对象存储,用法如下:
  WITH S3
  (
        "AWS_ENDPOINT" = "AWS_ENDPOINT",
        "AWS_ACCESS_KEY" = "AWS_TEMP_ACCESS_KEY",
        "AWS_SECRET_KEY" = "AWS_TEMP_SECRET_KEY",
        "AWS_TOKEN" = "AWS_TEMP_TOKEN",
        "AWS_REGION" = "AWS_REGION"
  )

 

订阅Kafka日志

用户可以通过提交例行导入作业,直接订阅Kafka中的消息数据,以近实时的方式进行数据同步。

Doris 自身能够保证不丢不重的订阅 Kafka 中的消息,即 Exactly-Once 消费语义。

订阅 Kafka 消息​

订阅 Kafka 消息使用了 Doris 中的例行导入(Routine Load)功能。

用户首先需要创建一个例行导入作业。作业会通过例行调度,不断地发送一系列的任务,每个任务会消费一定数量 Kafka 中的消息。

请注意以下使用限制:

  1. 支持无认证的 Kafka 访问,以及通过 SSL 方式认证的 Kafka 集群。
  2. 支持的消息格式如下:
    • csv 文本格式。每一个 message 为一行,且行尾不包含换行符。
    • Json 格式,详见 导入 Json 格式数据。
  3. 仅支持 Kafka 0.10.0.0(含) 以上版本。

访问 SSL 认证的 Kafka 集群​

例行导入功能支持无认证的 Kafka 集群,以及通过 SSL 认证的 Kafka 集群。

访问 SSL 认证的 Kafka 集群需要用户提供用于认证 Kafka Broker 公钥的证书文件(ca.pem)。如果 Kafka 集群同时开启了客户端认证,则还需提供客户端的公钥(client.pem)、密钥文件(client.key),以及密钥密码。这里所需的文件需要先通过 CREAE FILE 命令上传到 Doris 中,并且 catalog 名称为 kafkaCREATE FILE 命令的具体帮助可以参见 CREATE FILE 命令手册。这里给出示例:

  • 上传文件

    CREATE FILE "ca.pem" PROPERTIES("url" = "https://example_url/kafka-key/ca.pem", "catalog" = "kafka");
    CREATE FILE "client.key" PROPERTIES("url" = "https://example_urlkafka-key/client.key", "catalog" = "kafka");
    CREATE FILE "client.pem" PROPERTIES("url" = "https://example_url/kafka-key/client.pem", "catalog" = "kafka");
    

上传完成后,可以通过 SHOW FILES 命令查看已上传的文件。

创建例行导入作业​

创建例行导入任务的具体命令,请参阅 ROUTINE LOAD 命令手册。这里给出示例:

  1. 访问无认证的 Kafka 集群

    CREATE ROUTINE LOAD demo.my_first_routine_load_job ON test_1
    COLUMNS TERMINATED BY ","
    PROPERTIES
    (
        "max_batch_interval" = "20",
        "max_batch_rows" = "300000",
        "max_batch_size" = "209715200",
    )
    FROM KAFKA
    (
        "kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
        "kafka_topic" = "my_topic",
        "property.group.id" = "xxx",
        "property.client.id" = "xxx",
        "property.kafka_default_offsets" = "OFFSET_BEGINNING"
    );
    

    • max_batch_interval/max_batch_rows/max_batch_size 用于控制一个子任务的运行周期。一个子任务的运行周期由最长运行时间、最多消费行数和最大消费数据量共同决定。
  2. 访问 SSL 认证的 Kafka 集群

    CREATE ROUTINE LOAD demo.my_first_routine_load_job ON test_1
    COLUMNS TERMINATED BY ",",
    PROPERTIES
    (
        "max_batch_interval" = "20",
        "max_batch_rows" = "300000",
        "max_batch_size" = "209715200",
    )
    FROM KAFKA
    (
       "kafka_broker_list"= "broker1:9091,broker2:9091",
       "kafka_topic" = "my_topic",
       "property.security.protocol" = "ssl",
       "property.ssl.ca.location" = "FILE:ca.pem",
       "property.ssl.certificate.location" = "FILE:client.pem",
       "property.ssl.key.location" = "FILE:client.key",
       "property.ssl.key.password" = "abcdefg"
    );
    

查看导入作业状态​

查看作业状态的具体命令和示例请参阅 SHOW ROUTINE LOAD 命令文档。

查看某个作业的任务运行状态的具体命令和示例请参阅 SHOW ROUTINE LOAD TASK 命令文档。

只能查看当前正在运行中的任务,已结束和未开始的任务无法查看。

修改作业属性​

用户可以修改已经创建的作业的部分属性。具体说明请参阅 ALTER ROUTINE LOAD 命令手册。

作业控制​

用户可以通过 STOP/PAUSE/RESUME 三个命令来控制作业的停止,暂停和重启。

具体命令请参阅 STOP ROUTINE LOAD,PAUSE ROUTINE LOAD,RESUME ROUTINE LOAD 命令文档。

更多帮助​

关于 ROUTINE LOAD 的更多详细语法和最佳实践,请参阅 ROUTINE LOAD 命令手册。

通过外部表同步数据

Doris 可以创建外部表。创建完成后,可以通过 SELECT 语句直接查询外部表的数据,也可以通过 INSERT INTO SELECT 的方式导入外部表的数据。

Doris 外部表目前支持的数据源包括:

  • MySQL
  • Oracle
  • PostgreSQL
  • SQLServer
  • Hive
  • Iceberg
  • ElasticSearch

本文档主要介绍如何创建通过 ODBC 协议访问的外部表,以及如何导入这些外部表的数据。

创建外部表​

创建 ODBC 外部表的详细介绍请参阅 CREATE EXTERNAL TABLE 语法帮助手册。

这里仅通过示例说明使用方式。

  1. 创建 ODBC Resource

    ODBC Resource 的目的是用于统一管理外部表的连接信息。

    CREATE EXTERNAL RESOURCE `oracle_test_odbc`
    PROPERTIES (
        "type" = "odbc_catalog",
        "host" = "192.168.0.10",
        "port" = "8086",
        "user" = "oracle",
        "password" = "oracle",
        "database" = "oracle",
        "odbc_type" = "oracle",
        "driver" = "Oracle"
    );
    

这里我们创建了一个名为 oracle_test_odbc 的 Resource,其类型为 odbc_catalog,表示这是一个用于存储 ODBC 信息的 Resource。odbc_type 为 oracle,表示这个 OBDC Resource 是用于连接 Oracle 数据库的。关于其他类型的资源,具体可参阅 资源管理 文档。

  1. 创建外部表
CREATE EXTERNAL TABLE `ext_oracle_demo` (
  `k1` decimal(9, 3) NOT NULL COMMENT "",
  `k2` char(10) NOT NULL COMMENT "",
  `k3` datetime NOT NULL COMMENT "",
  `k5` varchar(20) NOT NULL COMMENT "",
  `k6` double NOT NULL COMMENT ""
) ENGINE=ODBC
COMMENT "ODBC"
PROPERTIES (
    "odbc_catalog_resource" = "oracle_test_odbc",
    "database" = "oracle",
    "table" = "baseall"
);

这里我们创建一个 ext_oracle_demo 外部表,并引用了之前创建的 oracle_test_odbc Resource

导入数据​

  1. 创建 Doris 表

    这里我们创建一张 Doris 的表,列信息和上一步创建的外部表 ext_oracle_demo 一样:

    CREATE TABLE `doris_oralce_tbl` (
      `k1` decimal(9, 3) NOT NULL COMMENT "",
      `k2` char(10) NOT NULL COMMENT "",
      `k3` datetime NOT NULL COMMENT "",
      `k5` varchar(20) NOT NULL COMMENT "",
      `k6` double NOT NULL COMMENT ""
    )
    COMMENT "Doris Table"
    DISTRIBUTED BY HASH(k1) BUCKETS 2
    PROPERTIES (
        "replication_num" = "1"
    );
    

    关于创建 Doris 表的详细说明,请参阅 CREATE-TABLE 语法帮助。

  2. 导入数据 (从 ext_oracle_demo表 导入到 doris_oralce_tbl 表)

INSERT INTO doris_oralce_tbl SELECT k1,k2,k3 FROM ext_oracle_demo limit 100;

INSERT 命令是同步命令,返回成功,即表示导入成功。

注意事项​

  • 必须保证外部数据源与 Doris 集群是可以互通,包括BE节点和外部数据源的网络是互通的。
  • ODBC 外部表本质上是通过单一 ODBC 客户端访问数据源,因此并不合适一次性导入大量的数据,建议分批多次导入。

更多帮助​

关于 CREATE EXTERNAL TABLE 的更多详细语法和最佳实践,请参阅 CREATE EXTERNAL TABLE 命令手册

使用 Insert 方式同步数据

用户可以通过 MySQL 协议,使用 INSERT 语句进行数据导入。

INSERT 语句的使用方式和 MySQL 等数据库中 INSERT 语句的使用方式类似。 INSERT 语句支持以下两种语法:

* INSERT INTO table SELECT ...
* INSERT INTO table VALUES(...)

这里我们仅介绍第二种方式。关于 INSERT 命令的详细说明,请参阅 INSERT 命令文档。

单次写入​

单次写入是指用户直接执行一个 INSERT 命令。示例如下:

INSERT INTO example_tbl (col1, col2, col3) VALUES (1000, "test", 3.25);

对于 Doris 来说,一个 INSERT 命令就是一个完整的导入事务。

因此不论是导入一条数据,还是多条数据,我们都不建议在生产环境使用这种方式进行数据导入。高频次的 INSERT 操作会导致在存储层产生大量的小文件,会严重影响系统性能。

该方式仅用于线下简单测试或低频少量的操作。

或者可以使用以下方式进行批量的插入操作:

INSERT INTO example_tbl VALUES
(1000, "baidu1", 3.25)
(2000, "baidu2", 4.25)
(3000, "baidu3", 5.25);

我们建议一批次插入条数在尽量大,比如几千甚至一万条一次。或者可以通过下面的程序的方式,使用 PreparedStatement 来进行批量插入。

JDBC 示例​

这里我们给出一个简单的 JDBC 批量 INSERT 代码示例:

package demo.doris;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DorisJDBCDemo {

    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL_PATTERN = "jdbc:mysql://%s:%d/%s?rewriteBatchedStatements=true";
    private static final String HOST = "127.0.0.1"; // Leader Node host
    private static final int PORT = 9030;   // query_port of Leader Node
    private static final String DB = "demo";
    private static final String TBL = "test_1";
    private static final String USER = "admin";
    private static final String PASSWD = "my_pass";

    private static final int INSERT_BATCH_SIZE = 10000;

    public static void main(String[] args) {
        insert();
    }

    private static void insert() {
        // 注意末尾不要加 分号 ";"
        String query = "insert into " + TBL + " values(?, ?)";
        // 设置 Label 以做到幂等。
        // String query = "insert into " + TBL + " WITH LABEL my_label values(?, ?)";

        Connection conn = null;
        PreparedStatement stmt = null;
        String dbUrl = String.format(DB_URL_PATTERN, HOST, PORT, DB);
        try {
            Class.forName(JDBC_DRIVER);
            conn = DriverManager.getConnection(dbUrl, USER, PASSWD);
            stmt = conn.prepareStatement(query);

            for (int i =0; i < INSERT_BATCH_SIZE; i++) {
                stmt.setInt(1, i);
                stmt.setInt(2, i * 100);
                stmt.addBatch();
            }

            int[] res = stmt.executeBatch();
            System.out.println(res);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException se2) {
                se2.printStackTrace();
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
    }
}

请注意以下几点:

  1. JDBC 连接串需添加 rewriteBatchedStatements=true 参数,并使用 PreparedStatement 方式。

    目前 Doris 暂不支持服务器端的 PrepareStatemnt,所以 JDBC Driver 会在客户端进行批量 Prepare。

    rewriteBatchedStatements=true 会确保 Driver 执行批处理。并最终形成如下形式的 INSERT 语句发往 Doris:

    INSERT INTO example_tbl VALUES
    (1000, "baidu1", 3.25)
    (2000, "baidu2", 4.25)
    (3000, "baidu3", 5.25);
    

  2. 批次大小

    因为是在客户端进行批量处理,所以一批次过大的话,会占用客户端的内存资源,需关注。

    Doris 后续会支持服务端的 PrepareStatemnt,敬请期待。

  3. 导入原子性

    和其他到导入方式一样,INSERT 操作本身也支持原子性。每一个 INSERT 操作都是一个导入事务,能够保证一个 INSERT 中的所有数据原子性的写入。

    前面提到,我们建议在使用 INSERT 导入数据时,采用 ”批“ 的方式进行导入,而不是单条插入。

    同时,我们可以为每次 INSERT 操作设置一个 Label。通过 Label 机制 可以保证操作的幂等性和原子性,最终做到数据的不丢不重。关于 INSERT 中 Label 的具体用法,可以参阅 INSERT 文档。

数据导入事务及原子性

Doris 中的所有导入操作都有原子性保证,即一个导入作业中的数据要么全部成功,要么全部失败。不会出现仅部分数据导入成功的情况。

在 BROKER LOAD 中我们也可以实现多表的原子性导入。

对于表所附属的 物化视图,也同时保证和基表的原子性和一致性。

Label 机制​

Doris 的导入作业都可以设置一个 Label。这个 Label 通常是用户自定义的、具有一定业务逻辑属性的字符串。

Label 的主要作用是唯一标识一个导入任务,并且能够保证相同的 Label 仅会被成功导入一次。

Label 机制可以保证导入数据的不丢不重。如果上游数据源能够保证 At-Least-Once 语义,则配合 Doris 的 Label 机制,能够保证 Exactly-Once 语义。

Label 在一个数据库下具有唯一性。Label 的保留期限默认是 3 天。即 3 天后,已完成的 Label 会被自动清理,之后 Label 可以被重复使用。

最佳实践​

Label 通常被设置为 业务逻辑+时间 的格式。如 my_business1_20220330_125000

这个 Label 通常用于表示:业务 my_business1 这个业务在 2022-03-30 12:50:00 产生的一批数据。通过这种 Label 设定,业务上可以通过 Label 查询导入任务状态,来明确的获知该时间点批次的数据是否已经导入成功。如果没有成功,则可以使用这个 Label 继续重试导入。

导入的数据转换、列映射及过滤

支持的导入方式​

  • BROKER LOAD

    LOAD LABEL example_db.label1
    (
        DATA INFILE("bos://bucket/input/file")
        INTO TABLE `my_table`
        (k1, k2, tmpk3)
        PRECEDING FILTER k1 = 1
        SET (
            k3 = tmpk3 + 1
        )
        WHERE k1 > k2
    )
    WITH BROKER bos
    (
        ...
    );
    

  • STREAM LOAD

    curl
    --location-trusted
    -u user:passwd
    -H "columns: k1, k2, tmpk3, k3 = tmpk3 + 1"
    -H "where: k1 > k2"
    -T file.txt
    http://host:port/api/testDb/testTbl/_stream_load
    

  • ROUTINE LOAD

    CREATE ROUTINE LOAD example_db.label1 ON my_table
    COLUMNS(k1, k2, tmpk3, k3 = tmpk3 + 1),
    PRECEDING FILTER k1 = 1,
    WHERE k1 > k2
    ...
    

以上导入方式都支持对源数据进行列映射、转换和过滤操作:

  • 前置过滤:对读取到的原始数据进行一次过滤。

    PRECEDING FILTER k1 = 1
    

  • 映射:定义源数据中的列。如果定义的列名和表中的列相同,则直接映射为表中的列。如果不同,则这个被定义的列可以用于之后的转换操作。如上面示例中的:

    (k1, k2, tmpk3)
    

  • 转换:将第一步中经过映射的列进行转换,可以使用内置表达式、函数、自定义函数进行转化,并重新映射到表中对应的列上。如上面示例中的:

    k3 = tmpk3 + 1
    

  • 后置过滤:对经过映射和转换后的列,通过表达式进行过滤。被过滤的数据行不会导入到系统中。如上面示例中的:

    WHERE k1 > k2
    

列映射​

列映射的目的主要是描述导入文件中各个列的信息,相当于为源数据中的列定义名称。通过描述列映射关系,我们可以将于表中列顺序不同、列数量不同的源文件导入到 Doris 中。下面我们通过示例说明:

假设源文件有4列,内容如下(表头列名仅为方便表述,实际并无表头):

列1 列2 列3 列4
1 100 beijing 1.1
2 200 shanghai 1.2
3 300 guangzhou 1.3
4 \N chongqing 1.4

注:\N 在源文件中表示 null。

  1. 调整映射顺序

    假设表中有 k1,k2,k3,k4 4列。我们希望的导入映射关系如下:

    列1 -> k1
    列2 -> k3
    列3 -> k2
    列4 -> k4
    

    则列映射的书写顺序应如下:

    (k1, k3, k2, k4)
    

  2. 源文件中的列数量多于表中的列

    假设表中有 k1,k2,k3 3列。我们希望的导入映射关系如下:

    列1 -> k1
    列2 -> k3
    列3 -> k2
    

    则列映射的书写顺序应如下:

    (k1, k3, k2, tmpk4)
    

    其中 tmpk4 为一个自定义的、表中不存在的列名。Doris 会忽略这个不存在的列名。

  3. 源文件中的列数量少于表中的列,使用默认值填充

    假设表中有 k1,k2,k3,k4,k5 5列。我们希望的导入映射关系如下:

    列1 -> k1
    列2 -> k3
    列3 -> k2
    

    这里我们仅使用源文件中的前3列。k4,k5 两列希望使用默认值填充。

    则列映射的书写顺序应如下:

    (k1, k3, k2)
    

    如果 k4,k5 列有默认值,则会填充默认值。否则如果是 nullable 的列,则会填充 null 值。否则,导入作业会报错。

列前置过滤​

前置过滤是对读取到的原始数据进行一次过滤。目前仅支持 BROKER LOAD 和 ROUTINE LOAD。

前置过滤有以下应用场景:

  1. 转换前做过滤

    希望在列映射和转换前做过滤的场景。能够先行过滤掉部分不需要的数据。

  2. 过滤列不存在于表中,仅作为过滤标识

    比如源数据中存储了多张表的数据(或者多张表的数据写入了同一个 Kafka 消息队列)。数据中每行有一列表名来标识该行数据属于哪个表。用户可以通过前置过滤条件来筛选对应的表数据进行导入。

列转换​

列转换功能允许用户对源文件中列值进行变换。目前 Doris 支持使用绝大部分内置函数、用户自定义函数进行转换。

注:自定义函数隶属于某一数据库下,在使用自定义函数进行转换时,需要用户对这个数据库有读权限。

转换操作通常是和列映射一起定义的。即先对列进行映射,再进行转换。下面我们通过示例说明:

假设源文件有4列,内容如下(表头列名仅为方便表述,实际并无表头):

列1 列2 列3 列4
1 100 beijing 1.1
2 200 shanghai 1.2
3 300 guangzhou 1.3
\N 400 chongqing 1.4
  1. 将源文件中的列值经转换后导入表中

    假设表中有 k1,k2,k3,k4 4列。我们希望的导入映射和转换关系如下:

    列1       -> k1
    列2 * 100 -> k3
    列3       -> k2
    列4       -> k4
    

    则列映射的书写顺序应如下:

    (k1, tmpk3, k2, k4, k3 = tmpk3 * 100)
    

    这里相当于我们将源文件中的第2列命名为 tmpk3,同时指定表中 k3 列的值为 tmpk3 * 100。最终表中的数据如下:

    k1 k2 k3 k4
    1 beijing 10000 1.1
    2 shanghai 20000 1.2
    3 guangzhou 30000 1.3
    null chongqing 40000 1.4
  2. 通过 case when 函数,有条件的进行列转换。

    假设表中有 k1,k2,k3,k4 4列。我们希望对于源数据中的 beijing, shanghai, guangzhou, chongqing 分别转换为对应的地区id后导入:

    列1                  -> k1
    列2                  -> k2
    列3 进行地区id转换后    -> k3
    列4                  -> k4
    

    则列映射的书写顺序应如下:

    (k1, k2, tmpk3, k4, k3 = case tmpk3 when "beijing" then 1 when "shanghai" then 2 when "guangzhou" then 3 when "chongqing" then 4 else null end)
    

    最终表中的数据如下:

    k1 k2 k3 k4
    1 100 1 1.1
    2 200 2 1.2
    3 300 3 1.3
    null 400 4 1.4
  3. 将源文件中的 null 值转换成 0 导入。同时也进行示例2中的地区id转换。

    假设表中有 k1,k2,k3,k4 4列。在对地区id转换的同时,我们也希望对于源数据中 k1 列的 null 值转换成 0 导入:

    列1 如果为null 则转换成0   -> k1
    列2                      -> k2
    列3                      -> k3
    列4                      -> k4
    

    则列映射的书写顺序应如下:

    (tmpk1, k2, tmpk3, k4, k1 = ifnull(tmpk1, 0), k3 = case tmpk3 when "beijing" then 1 when "shanghai" then 2 when "guangzhou" then 3 when "chongqing" then 4 else null end)
    

    最终表中的数据如下:

    k1 k2 k3 k4
    1 100 1 1.1
    2 200 2 1.2
    3 300 3 1.3
    0 400 4 1.4

列过滤​

经过列映射和转换后,我们可以通过过滤条件将不希望导入到Doris中的数据进行过滤。下面我们通过示例说明:

假设源文件有4列,内容如下(表头列名仅为方便表述,实际并无表头):

列1 列2 列3 列4
1 100 beijing 1.1
2 200 shanghai 1.2
3 300 guangzhou 1.3
\N 400 chongqing 1.4
  1. 在列映射和转换缺省的情况下,直接过滤

    假设表中有 k1,k2,k3,k4 4列。我们可以在缺省列映射和转换的情况下,直接定义过滤条件。如我们希望只导入源文件中第4列为大于 1.2 的数据行,则过滤条件如下:

    where k4 > 1.2
    

    最终表中的数据如下:

    k1 k2 k3 k4
    3 300 guangzhou 1.3
    null 400 chongqing 1.4

    缺省情况下,Doris 会按照顺序进行列映射,因此源文件中的第4列自动被映射到表中的 k4 列。

  2. 对经过列转换的数据进行过滤

    假设表中有 k1,k2,k3,k4 4列。在 列转换 示例中,我们将省份名称转换成了id。这里我们想过滤掉 id 为 3 的数据。则转换、过滤条件如下:

    (k1, k2, tmpk3, k4, k3 = case tmpk3 when "beijing" then 1 when "shanghai" then 2 when "guangzhou" then 3 when "chongqing" then 4 else null end)
    where k3 != 3
    

    最终表中的数据如下:

    k1 k2 k3 k4
    1 100 1 1.1
    2 200 2 1.2
    null 400 4 1.4

    这里我们看到,执行过滤时的列值,为经过映射和转换后的最终列值,而不是原始数据。

  3. 多条件过滤

    假设表中有 k1,k2,k3,k4 4列。我们想过滤掉 k1 列为 null 的数据,同时过滤掉 k4 列小于 1.2 的数据,则过滤条件如下:

    where k1 is not null and k4 >= 1.2
    

    最终表中的数据如下:

    k1 k2 k3 k4
    2 200 2 1.2
    3 300 3 1.3

数据质量问题和过滤阈值​

导入作业中被处理的数据行可以分为如下三种:

  1. Filtered Rows

    因数据质量不合格而被过滤掉的数据。数据质量不合格包括类型错误、精度错误、字符串长度超长、文件列数不匹配等数据格式问题,以及因没有对应的分区而被过滤掉的数据行。

  2. Unselected Rows

    这部分为因 preceding filter 或 where 列过滤条件而被过滤掉的数据行。

  3. Loaded Rows

    被正确导入的数据行。

Doris 的导入任务允许用户设置最大错误率(max_filter_ratio)。如果导入的数据的错误率低于阈值,则这些错误行将被忽略,其他正确的数据将被导入。

错误率的计算方式为:

#Filtered Rows / (#Filtered Rows + #Loaded Rows)

也就是说 Unselected Rows 不会参与错误率的计算。

导入严格模式

严格模式(strict_mode)为导入操作中的一个参数配置。该参数会影响某些数值的导入行为和最终导入的数据。

本文档主要说明如何设置严格模式,以及严格模式产生的影响。

如何设置​

严格模式默认情况下都为 False,即关闭状态。

不同的导入方式设置严格模式的方式不尽相同。

  1. BROKER LOAD

    LOAD LABEL example_db.label1
    (
        DATA INFILE("bos://my_bucket/input/file.txt")
        INTO TABLE `my_table`
        COLUMNS TERMINATED BY ","
    )
    WITH BROKER bos
    (
        "bos_endpoint" = "http://bj.bcebos.com",
        "bos_accesskey" = "xxxxxxxxxxxxxxxxxxxxxxxxxx",
        "bos_secret_accesskey"="yyyyyyyyyyyyyyyyyyyyyyyyyy"
    )
    PROPERTIES
    (
        "strict_mode" = "true"
    )
    

  2. STREAM LOAD

    curl --location-trusted -u user:passwd \
    -H "strict_mode: true" \
    -T 1.txt \
    http://host:port/api/example_db/my_table/_stream_load
    

  3. ROUTINE LOAD

    CREATE ROUTINE LOAD example_db.test_job ON my_table
    PROPERTIES
    (
        "strict_mode" = "true"
    ) 
    FROM KAFKA
    (
        "kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
        "kafka_topic" = "my_topic"
    );
    

  4. INSERT

    通过会话变量设置:

    SET enable_insert_strict = true;
    INSERT INTO my_table ...;
    

严格模式的作用​

严格模式的意思是,对于导入过程中的列类型转换进行严格过滤。

严格过滤的策略如下:

对于列类型转换来说,如果开启严格模式,则错误的数据将被过滤。这里的错误数据是指:原始数据并不为 null,而在进行列类型转换后结果为 null 的这一类数据。

这里说指的 列类型转换,并不包括用函数计算得出的 null 值。

对于导入的某列类型包含范围限制的,如果原始数据能正常通过类型转换,但无法通过范围限制的,严格模式对其也不产生影响。例如:如果类型是 decimal(1,0), 原始数据为 10,则属于可以通过类型转换但不在列声明的范围内。这种数据 strict 对其不产生影响。

  1. 以列类型为 TinyInt 来举例:

    原始数据类型 原始数据举例 转换为 TinyInt 后的值 严格模式 结果
    空值 \N NULL 开启或关闭 NULL
    非空值 "abc" or 2000 NULL 开启 非法值(被过滤)
    非空值 "abc" NULL 关闭 NULL
    非空值 1 1 开启或关闭 正确导入

    说明:

    1. 表中的列允许导入空值
    2. abc 及 2000 在转换为 TinyInt 后,会因类型或精度问题变为 NULL。在严格模式开启的情况下,这类数据将会被过滤。而如果是关闭状态,则会导入 null
  2. 以列类型为 Decimal(1,0) 举例

    原始数据类型 原始数据举例 转换为 Decimal 后的值 严格模式 结果
    空值 \N null 开启或关闭 NULL
    非空值 aaa NULL 开启 非法值(被过滤)
    非空值 aaa NULL 关闭 NULL
    非空值 1 or 10 1 or 10 开启或关闭 正确导入

    说明:

    1. 表中的列允许导入空值
    2. abc 在转换为 Decimal 后,会因类型问题变为 NULL。在严格模式开启的情况下,这类数据将会被过滤。而如果是关闭状态,则会导入 null
    3. 10 虽然是一个超过范围的值,但是因为其类型符合 decimal 的要求,所以严格模式对其不产生影响。10 最后会在其他导入处理流程中被过滤。但不会被严格模式过滤。

 

 

你可能感兴趣的:(apache,数据仓库,大数据,kafka,mysql)