Doris2.x连载文章(2)

4、表模型

https://doris.apache.org/zh-CN/docs/table-design/data-model/overview

在 Doris 中建表时需要指定表模型,以定义数据存储与管理方式。在 Doris 中提供了明细模型、聚合模型以及主键模型三种表模型,可以应对不同的应用场景需求。不同的表模型具有相应的数据去重、聚合及更新机制。选择合适的表模型有助于实现业务目标,同时保证数据处理的灵活性和高效性。

表模型分类

在 Doris 中支持三种表模型:

  • 明细模型(Duplicate Key Model):允许指定的Key 列重复,Doirs 存储层保留所有写入的数据,适用于必须保留所有原始数据记录的情况;
  • 主键模型(Unique Key Model):每一行的 Key 值唯一 ,可确保给定的 Key 列不会存在重复行,Doris 存储层对每个 key 只保留最新写入的数据,适用于数据更新的情况;
  • 聚合模型(Aggregate Key Model):可根据 Key 列聚合数据,Doris 存储层保留聚合后的数据,从而可以减少存储空间和提升查询性能;通常用于需要汇总或聚合信息(如总数或平均值)的情况。

在建表后,表模型的属性已经确认,无法修改。针对业务选择合适的模型至关重要:

  • Duplicate Key:适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。
  • Unique Key:针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势。
  • Aggregate Key :可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。

排序键

在 Doris 中,数据以列的形式存储,一张表可以分为 key 列与 value 列。其中,key 列用于分组与排序,value 列用于参与聚合。Key 列可以是一个或多个字段,在建表时,按照各种表模型中,Aggregate Key、Unique Key 和 Duplicate Key 的列进行数据排序存储。

不同的表模型都需要在建表时指定 Key 列,分别有不同的意义:对于 Duplicate Key 模型,Key 列表示排序,没有唯一键的约束。在 Aggregate Key 与 Unique Key 模型中,会基于 Key 列进行聚合,Key 列既有排序的能力,又有唯一键的约束。

选择排序键时,可以遵循以下建议:

  • Key 列必须在所有 Value 列之前。
  • 尽量选择整型类型。因为整型类型的计算和查找效率远高于字符串。
  • 对于不同长度的整型类型的选择原则,遵循够用即可。
  • 对于 VARCHARSTRING 类型的长度,遵循够用即可原则

明细模型

明细模型是 Doris 中的默认建表模型 ,用于保存每条原始数据记录。在建表时,通过 DUPLICATE KEY 指定数据存储的排序列,以优化常用查询。一般建议选择三列或更少的列作为排序键,具体选择方式参考排序键

使用场景

一般明细模型中的数据只进行追加,旧数据不会更新。明细模型适用于需要存储全量原始数据的场景:

  • 日志存储
  • 用户行为数据
  • 交易数据

建表说明

在建表时,可以通过 DUPLICATE KEY 关键字指定明细模型。明细表必须指定数据的 Key 列 ,用于在存储时对数据进行排序。下例的明细表中存储了日志信息,并针对于 log_timelog_typeerror_code 三列进行了排序:

复制代码
CREATE TABLE IF NOT EXISTS example_tbl_duplicate
(
    log_time        DATETIME       NOT NULL,
    log_type        INT            NOT NULL,
    error_code      INT,
    error_msg       VARCHAR(1024),
    op_id           BIGINT,
    op_time         DATETIME
)
DUPLICATE KEY(log_time, log_type, error_code)
DISTRIBUTED BY HASH(log_type) BUCKETS 10;

数据插入与存储

在明细表中,数据不进行去重与聚合,插入数据即存储数据。明细模型中Key 列只作为排序

在上例中,表中原有 4 行数据,插入 2 行数据后,采用追加(APPEND)方式存储,共计 6 行数据:

复制代码
-- 4 rows raw data
INSERT INTO example_tbl_duplicate VALUES
('2024-11-01 00:00:00', 2, 2, 'timeout', 12, '2024-11-01 01:00:00'),
('2024-11-02 00:00:00', 1, 2, 'success', 13, '2024-11-02 01:00:00'),
('2024-11-03 00:00:00', 2, 2, 'unknown', 13, '2024-11-03 01:00:00'),
('2024-11-04 00:00:00', 2, 2, 'unknown', 12, '2024-11-04 01:00:00');

-- insert into 2 rows
INSERT INTO example_tbl_duplicate VALUES
('2024-11-01 00:00:00', 2, 2, 'timeout', 12, '2024-11-01 01:00:00'),
('2024-11-01 00:00:00', 2, 2, 'unknown', 13, '2024-11-01 01:00:00');

-- check the rows of table
SELECT * FROM example_tbl_duplicate;
+---------------------+----------+------------+-----------+-------+---------------------+
| log_time            | log_type | error_code | error_msg | op_id | op_time             |
+---------------------+----------+------------+-----------+-------+---------------------+
| 2024-11-02 00:00:00 |        1 |          2 | success   |    13 | 2024-11-02 01:00:00 |
| 2024-11-01 00:00:00 |        2 |          2 | timeout   |    12 | 2024-11-01 01:00:00 |
| 2024-11-03 00:00:00 |        2 |          2 | unknown   |    13 | 2024-11-03 01:00:00 |
| 2024-11-04 00:00:00 |        2 |          2 | unknown   |    12 | 2024-11-04 01:00:00 |
| 2024-11-01 00:00:00 |        2 |          2 | unknown   |    13 | 2024-11-01 01:00:00 |
| 2024-11-01 00:00:00 |        2 |          2 | timeout   |    12 | 2024-11-01 01:00:00 |
+---------------------+----------+------------+-----------+-------+----------------------

主键模型

当需要更新数据时,可以选择主键模型(Unique Key Model)。该模型保证 Key 列的唯一性,插入或更新数据时,新数据会覆盖具有相同 Key 的旧数据,确保数据记录为最新。与其他数据模型相比,主键模型适用于数据的更新场景,在插入过程中进行主键级别的更新覆盖。

主键模型有以下特点:

  • 基于主键进行 UPSERT(update insert):在插入数据时,主键重复的数据会更新,主键不存在的记录会插入;
  • 基于主键进行去重:主键模型中的 Key 列具有唯一性,会对根据主键列对数据进行去重操作;
  • 高频数据更新:支持高频数据更新场景,同时平衡数据更新性能与查询性能。

实现方式

在 Doris 中主键模型有两种实现方式:

  • 写时合并(merge-on-write):自 1.2 版本起,Doris 默认使用写时合并模式,数据在写入时立即合并相同 Key 的记录,确保存储的始终是最新数据。写时合并兼顾查询和写入性能,避免多个版本的数据合并,大多数场景推荐使用此模式;
  • 读时合并(merge-on-read):在 1.2 版本前,Doris 中的主键模型默认使用读时合并模式,数据在写入时并不进行合并,以增量的方式被追加存储,在 Doris 内保留多个版本。查询 时,会对数据进行相同 Key 的版本合并。读时合并适合写多读少的场景,在查询是需要进行多个版本合并。

在 Doris 中基于主键模型更新有两种语义:

  • 整行更新 :Unique Key 模型默认的更新语义为整行UPSERT,即 UPDATE OR INSERT,该行数据的 Key 如果存在,则进行更新,如果不存在,则进行新数据插入。
  • 部分列更新:如果用户希望更新部分字段,需要使用写时合并实现,并通过特定的参数来开启部分列更新的支持。

写时合并

在建表时,使用 UNIQUE KEY 关键字可以指定主键表。通过显示开启 enable_unique_key_merge_on_write 属性可以指定写时合并模式。自 Doris 2.1 版本以后,默认开启写时合并

复制代码
CREATE TABLE IF NOT EXISTS example_tbl_unique
(
    user_id         LARGEINT        NOT NULL,
    user_name       VARCHAR(50)     NOT NULL,
    city            VARCHAR(20),
    age             SMALLINT,
    sex             TINYINT
)
UNIQUE KEY(user_id, user_name)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES (
    "enable_unique_key_merge_on_write" = "true"
);

读时合并

在建表时,使用 UNIQUE KEY 关键字可以指定主键表。通过显示关闭 enable_unique_key_merge_on_write 属性可以指定读时合并模式。在 Doris 2.1 版本之前,默认开启读时合并:

复制代码
CREATE TABLE IF NOT EXISTS example_tbl_unique
(
    user_id         LARGEINT        NOT NULL,
    username        VARCHAR(50)     NOT NULL,
    city            VARCHAR(20),
    age             SMALLINT,
    sex             TINYINT
)
UNIQUE KEY(user_id, username)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES (
    "enable_unique_key_merge_on_write" = "false"
);

数据插入与存储

在主键表中,Key 列不仅用于排序,还用于去重,插入数据时,相同 Key 的记录会被覆盖。

如上例所示,原表中有 4 行数据,插入 2 行后,新插入的数据基于主键进行了更新:

复制代码
-- insert into raw data
INSERT INTO example_tbl_unique VALUES
(101, 'Tom', 'BJ', 26, 1),
(102, 'Jason', 'BJ', 27, 1),
(103, 'Juice', 'SH', 20, 2),
(104, 'Olivia', 'SZ', 22, 2);

-- insert into data to update by key
INSERT INTO example_tbl_unique VALUES
(101, 'Tom', 'BJ', 27, 1),
(102, 'Jason', 'SH', 28, 1);

-- check updated data
SELECT * FROM example_tbl_unique;
+---------+----------+------+------+------+
| user_id | username | city | age  | sex  |
+---------+----------+------+------+------+
| 101     | Tom      | BJ   |   27 |    1 |
| 102     | Jason    | SH   |   28 |    1 |
| 104     | Olivia   | SZ   |   22 |    2 |
| 103     | Juice    | SH   |   20 |    2 |
+---------+----------+------+------+------+

注意事项

  • Unique 表的实现方式只能在建表时确定,无法通过 schema change 进行修改;
  • 在整行 UPSERT 语义下,即使用户使用 insert into 指定部分列进行写入,Doris 也会在 Planner 中将未提供的列使用 NULL 值或者默认值进行填充;
  • 部分列更新。如果用户希望更新部分字段,需要使用写时合并实现,并通过特定的参数来开启部分列更新的支持。请查阅文档部分列更新获取相关使用建议;
  • 使用 Unique 表时,为了保证数据的唯一性,分区键必须包含在 Key 列内。

聚合模型

Doris 的聚合模型专为高效处理大规模数据查询中的聚合操作设计。它通过预聚合数据,减少重复计算,提升查询性能。聚合模型只存储聚合后的数据,节省存储空间并加速查询。

使用场景

  • 明细数据进行汇总:用于电商平台的月销售业绩、金融风控的客户交易总额、广告投放的点击量等业务场景中,进行多维度汇总;
  • 不需要查询原始明细数据:如驾驶舱报表、用户交易行为分析等,原始数据存储在数据湖中,仅需存储汇总后的数据。

原理

每一次数据导入会在聚合模型内形成一个版本,在 Compaction 阶段进行版本合并,在查询时会按照主键进行数据聚合:

  1. 数据导入阶段:数据按批次导入,每批次生成一个版本,并对相同聚合键的数据进行初步聚合(如求和、计数);
  2. 后台文件合并阶段(Compaction):多个版本文件会定期合并,减少冗余并优化存储;
  3. 查询阶段:查询时,系统会聚合同一聚合键的数据,确保查询结果准确。

建表说明

使用 AGGREGATE KEY 关键字在建表时指定聚合模型,并指定 Key 列用于聚合 Value 列。

复制代码
CREATE TABLE IF NOT EXISTS example_tbl_agg
(
    user_id             LARGEINT    NOT NULL,
    load_dt             DATE        NOT NULL,
    city                VARCHAR(20),
    last_visit_dt       DATETIME    REPLACE DEFAULT "1970-01-01 00:00:00",
    cost                BIGINT      SUM DEFAULT "0",
    max_dwell           INT         MAX DEFAULT "0",
)
AGGREGATE KEY(user_id, load_dt, city)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;

上例中定义了用户信息和访问行为表,将 user_idload_datecityage 作为 Key 列进行聚合。数据导入时,Key 列会聚合成一行,Value 列会按照指定的聚合类型进行维度聚合。

在聚合表中支持以下类型的维度聚合:

|---------------------|---------------------------------------|
| 聚合方式 | 描述 |
| SUM | 求和,多行的 Value 进行累加。 |
| REPLACE | 替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。 |
| MAX | 保留最大值。 |
| MIN | 保留最小值。 |
| REPLACE_IF_NOT_NULL | 非空值替换。与 REPLACE 的区别在于对 null 值,不做替换。 |
| HLL_UNION | HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。 |
| BITMAP_UNION | BITMAP 类型的列的聚合方式,进行位图的并集聚合。 |

提示:

如果以上的聚合方式无法满足业务需求,可以选择使用 agg_state 类型。

数据插入与存储

在聚合表中,数据基于主键进行聚合操作。数据插入后及完成聚合操作。

在上例中,表中原有 4 行数据,在插入 2 行数据后,基于 Key 列进行维度列的聚合操作:

复制代码
-- 4 rows raw data
INSERT INTO example_tbl_agg VALUES
(101, '2024-11-01', 'BJ', '2024-10-29', 10, 20),
(102, '2024-10-30', 'BJ', '2024-10-29', 20, 20),
(101, '2024-10-30', 'BJ', '2024-10-28', 5, 40),
(101, '2024-10-30', 'SH', '2024-10-29', 10, 20);

-- insert into 2 rows
INSERT INTO example_tbl_agg VALUES
(101, '2024-11-01', 'BJ', '2024-10-30', 20, 10),
(102, '2024-11-01', 'BJ', '2024-10-30', 10, 30);

-- check the rows of table
SELECT * FROM example_tbl_agg;
+---------+------------+------+---------------------+------+----------------+
| user_id | load_date  | city | last_visit_date     | cost | max_dwell_time |
+---------+------------+------+---------------------+------+----------------+
| 102     | 2024-10-30 | BJ   | 2024-10-29 00:00:00 |   20 |             20 |
| 102     | 2024-11-01 | BJ   | 2024-10-30 00:00:00 |   10 |             30 |
| 101     | 2024-10-30 | BJ   | 2024-10-28 00:00:00 |    5 |             40 |
| 101     | 2024-10-30 | SH   | 2024-10-29 00:00:00 |   10 |             20 |
| 101     | 2024-11-01 | BJ   | 2024-10-30 00:00:00 |   30 |             20 |
+---------+------------+------+---------------------+------+----------------+

案例实操

Bitmap(位图)是一种使用位数组(bit array)来表示一组整数(通常是用户 ID、商品 ID 等离散值)是否存在的数据结构。

例如,如果我们要记录哪些用户访问了网站,可以用一个 Bitmap,其中:

  • 每一个 bit 代表一个用户 ID(通常是经过编码后的整数,如 0, 1, 2, ...)
  • 如果某位是 1,表示该用户存在(访问过/购买过等)
  • 如果是 0,则表示不存在

假如我有一个位数组 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

user_id=1001 TO_BITMAP(1001) --> 下标 10 --> [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0]

user_id=1001 TO_BITMAP(1001) --> 下标 10 -->[0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0]

user_id=1002 TO_BITMAP(1002) --> 下标 11 -->[0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0]

将来根据bitmap_count 统计有多少个 1 就表示有几个用户。

复制代码
-- 统计每天每个页面的pv和uv
-- pv: 一个页面的总访问次数就是pv 
-- uv: 一个页面去重之后的访问用户数
CREATE TABLE `pv_bitmap` (
  `dt` 		varchar(10)  NULL COMMENT "",
  `page` 	varchar(10)  NULL COMMENT "",
  `pv`      BIGINT       SUM  DEFAULT "0",
  `user_id` bitmap 	     BITMAP_UNION NULL COMMENT ""
) ENGINE=OLAP
AGGREGATE KEY(`dt`, `page`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`dt`) BUCKETS 2
properties('replication_num' = '1');

bitmap可以理解为一维数组,里面要么是1,要么是0   [0,0,0,0,0,0,0,0,0....]
跟布隆过滤器有一定的关系。

INSERT INTO pv_bitmap VALUES
('2025-07-04','a.html',1,TO_BITMAP(101)),
('2025-07-04','a.html',1,TO_BITMAP(101)),
('2025-07-04','a.html',1,TO_BITMAP(102))
;

select * from pv_bitmap;

-- bitmap_count 统计bitmap中1的个数
select dt,page,pv,bitmap_count(user_id)  from pv_bitmap;

4.数据的导入

Apache Doris 提供的数据导入方式可以分为四类:

  • 实时写入:应用程序通过 HTTP 或者 JDBC 实时写入数据到 Doris 表中,适用于需要实时分析和查询的场景。
    • 极少量数据(5 分钟一次)时可以使用 JDBC INSERT 写入数据。
    • 吞吐较高时推荐使用 Stream Load 通过 HTTP 写入数据。
  • 流式同步 :通过实时数据流(如 Flink、Kafka、事务数据库)将数据实时导入到 Doris 表中,适用于需要实时分析和查询的场景。

  • 批量导入:将数据从外部存储系统(如对象存储、HDFS、本地文件、NAS)批量加载到 Doris 表中,适用于非实时数据导入的需求。

    • 可以使用 Broker Load 将对象存储和 HDFS 中的文件写入到 Doris 中。
    • 可以使用 INSERT INTO SELECT 将对象存储、HDFS 和 NAS 中的文件同步写入到 Doris 中,配合 JOB 可以异步写入。
    • 可以使用 Stream Load 或者 Doris Streamloader 将本地文件写入 Doris 中。
  • 外部数据源集成:通过与外部数据源(如 Hive、JDBC、Iceberg 等)的集成,实现对外部数据的查询和部分数据导入到 Doris 表中。

4.1 本地文件

Doris 提供多种方式从本地数据导入:

  • Stream Load

Stream Load 是通过 HTTP 协议将本地文件或数据流导入到 Doris 中。Stream Load 是一个同步导入方式,执行导入后返回导入结果,可以通过请求的返回判断导入是否成功。支持导入 CSV、JSON、Parquet 与 ORC 格式的数据。更多文档参考stream load

  • streamloader

Streamloader 工具是一款用于将数据导入 Doris 数据库的专用客户端工具,底层基于 Stream Load 实现,可以提供多文件,多并发导入的功能,降低大数据量导入的耗时。更多文档参考Streamloader

  • MySQL Load

Doris 兼容 MySQL 协议,可以使用 MySQL 标准的 LOAD DATA 语法导入本地文件。MySQL Load 是一种同步导入方式,执行导入后即返回导入结果,主要适用于导入客户端本地 CSV 文件。更多文档参考MySQL Load

使用 Stream Load 导入

第 1 步:准备数据

创建 CSV 文件 streamload_example.csv,内容如下:

cd /home

mkdir doris_data

复制代码
1,Emily,25
2,Benjamin,35
3,Olivia,28
4,Alexander,60
5,Ava,17
6,William,69
7,Sophia,32
8,James,64
9,Emma,37
10,Liam,64

第 2 步:在库中创建表

在 Doris 中创建表,语法如下:

复制代码
CREATE TABLE testdb.test_streamload(
    user_id            BIGINT       NOT NULL COMMENT "用户 ID",
    name               VARCHAR(20)           COMMENT "用户姓名",
    age                INT                   COMMENT "用户年龄"
)
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;

第 3 步:使用 Stream Load 导入数据

使用 curl 提交 Stream Load 导入作业:

复制代码
curl --location-trusted -u root:123456 \
    -H "column_separator:," \
    -H "columns:user_id,name,age" \
    -T /home/doris_data/streamload_example.csv \
    -XPUT http://bigdata01:8030/api/testdb/test_streamload/_stream_load

Stream Load 是一种同步导入方式,导入结果会直接返回给用户。

复制代码
{
    "TxnId": 3,
    "Label": "123",
    "Comment": "",
    "TwoPhaseCommit": "false",
    "Status": "Success",
    "Message": "OK",
    "NumberTotalRows": 10,
    "NumberLoadedRows": 10,
    "NumberFilteredRows": 0,
    "NumberUnselectedRows": 0,
    "LoadBytes": 118,
    "LoadTimeMs": 173,
    "BeginTxnTimeMs": 1,
    "StreamLoadPutTimeMs": 70,
    "ReadDataTimeMs": 2,
    "WriteDataTimeMs": 48,
    "CommitAndPublishTimeMs": 52
}

第 4 步:检查导入数据

复制代码
select count(*) from testdb.test_streamload;
+----------+
| count(*) |
+----------+
|       10 |
+----------+

该方式中涉及 HOST:PORT 都是对应的HTTP 协议端口。

  • BE 的 HTTP 协议端口,默认为 8040。
  • FE 的 HTTP 协议端口,默认为 8030。

但须保证客户端所在机器网络能够联通FE, BE 所在机器。

curl的一些可配置的参数:

  1. label: 导入任务的标签,相同标签的数据无法多次导入。(标签默认保留30分钟)
  2. column_separator:用于指定导入文件中的列分隔符,默认为\t。
  3. line_delimiter:用于指定导入文件中的换行符,默认为\n。
  4. columns:用于指定文件中的列和table中列的对应关系,默认一一对应

例1: 表中有3个列"c1, c2, c3",源文件中的三个列一次对应的是"c3,c2,c1"; 那么需要指定-H "columns: c3, c2, c1"

例2: 表中有3个列"c1, c2, c3", 源文件中前三列依次对应,但是有多余1列;那么需要指定-H "columns: c1, c2, c3, xxx";最后一个列随意指定个名称占位即可

例3: 表中有3个列"year, month, day"三个列,源文件中只有一个时间列,为"2018-06-01 01:02:03"格式;那么可以指定

-H "columns: col, year = year(col), month=month(col), day=day(col)"完成导入

  1. where: 用来过滤导入文件中的数据

例1: 只导入大于k1列等于20180601的数据,那么可以在导入时候指定-H "where: k1 = 20180601"

  1. max_filter_ratio:最大容忍可过滤(数据不规范等原因)的数据比例。默认零容忍。数据不规范不包括通过 where 条件过滤掉的行。
  2. partitions: 用于指定这次导入所 涉及 的partition。如果用户能够确定数据对应的partition,推荐指定该项。不满足这些分区的数据将被过滤掉。

比如指定导入到p1, p2分区,

-H "partitions: p1, p2"

  1. timeout: 指定导入的超时时间。单位秒。默认是 600 秒。可设置范围为 1 秒 ~ 259200 秒。
  2. timezone: 指定本次导入所使用的时区。默认为东八区。该参数会影响所有导入涉及的和时区有关的函数结果
  3. exec_mem_limit: 导入内存限制。默认为 2GB。单位为字节。
  4. format: 指定导入数据格式,默认是csv,支持json格式。
  5. read_json_by_line: 布尔类型,为true表示支持每行读取一个json对象,默认值为false。
  6. merge_type: 数据的合并类型,一共支持三种类型APPEND、DELETE、MERGE 其中,APPEND是默认值,表示这批数据全部需要追加到现有数据中,DELETE 表示删除与这批数据key相同的所有行,MERGE 语义 需要与delete 条件联合使用,表示满足delete 条件的数据按照DELETE 语义处理其余的按照APPEND 语义处理, 示例:-H "merge_type: MERGE" -H "delete: flag=1"
  7. delete: 仅在 MERGE下有意义, 表示数据的删除条件 function_column.sequence_col: 只适用于UNIQUE_KEYS,相同key列下,保证value列按照source_sequence列进行REPLACE, source_sequence可以是数据源中的列,也可以是表结构中的一列。

导入建议

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

使用 Streamloader 工具导入(略)

该工具不是 doris 自带的,需要现在源码,并编译,才能使用,源码地址:
https://github.com/apache/doris-streamloader

使用 stream loader 工具导入数据

复制代码
doris-streamloader --source_file="/root/doris_data/streamloader_example.csv" --url="http://node01:8330" --header="column_separator:," --db="testdb" --table="test_streamloader"

使用 MySQL Load 从本地数据导入

第 1 步:准备数据

创建名为 client_local.csv 的文件,样例数据如下:

复制代码
1,10
2,20
3,30
4,40
5,50
6,60

第 2 步:在库中创建表

在执行 LOAD DATA 命令前,需要先链接 mysql 客户端。记得使用命令的方式,datagrip 不行。

复制代码
mysql --local-infile  -h bigdata01 -P 9030 -u root -D testdb -p123456

执行 MySQL Load,在连接时需要使用指定参数选项:

  1. 在链接 mysql 客户端时,必须使用 --local-infile 选项,否则可能会报错。
  2. 通过 JDBC 链接,需要在 URL 中指定配置 allowLoadLocalInfile=true

在 Doris 中创建以下表:

复制代码
CREATE TABLE testdb.t1 (
    pk     INT, 
    v1     INT SUM
) AGGREGATE KEY (pk) 
DISTRIBUTED BY hash (pk);

第 3 步:使用 Mysql Load 导入数据

链接 MySQL Client 后,创建导入作业,命令如下:

复制代码
LOAD DATA LOCAL
INFILE '/home/doris_data/client_local.csv'
INTO TABLE testdb.t1
COLUMNS TERMINATED BY ','
LINES TERMINATED BY '\n';

第 4 步:检查导入数据

MySQL Load 是一种同步的导入方式,导入后结果会在命令行中返回给用户。如果导入执行失败,会展示具体的报错信息。

如下是导入成功的结果显示,会返回导入的行数:

复制代码
Query OK, 6 row affected (0.17 sec)
Records: 6  Deleted: 0  Skipped: 0  Warnings: 0

4.2 HDFS

Doris 提供两种方式从 HDFS 导入文件:

  • 使用 HDFS Load 将 HDFS 文件导入到 Doris 中,这是一个异步的导入方式。
  • 使用 TVF 将 HDFS 文件导入到 Doris 中,这是一个同步的导入方式。

使用 HDFS Load 导入

使用 HDFS Load 导入 HDFS 上的文件,详细步骤可以参考 Broker Load 手册

第 1 步:准备数据

复制代码
hdfs dfs -put streamload_example.csv /home

第 2 步:在 Doris 中创建表

复制代码
CREATE TABLE test_hdfsload(
    user_id            BIGINT       NOT NULL COMMENT "user id",
    name               VARCHAR(20)           COMMENT "name",
    age                INT                   COMMENT "age"
)
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;

第 3 步:使用 HDFS Load 导入数据

复制代码
LOAD LABEL hdfs_load_2025_12_19
(
    DATA INFILE("hdfs://bigdata01:9820/home/streamload_example.csv")
    INTO TABLE test_hdfsload
    COLUMNS TERMINATED BY ","
    FORMAT AS "CSV"
    (user_id, name, age)
)
with HDFS
(
    "fs.defaultFS" = "hdfs://bigdata01:9820",
    "hadoop.username" = "root"
)
PROPERTIES
(
    "timeout" = "3600"
);

第 4 步:检查导入数据

复制代码
SELECT * FROM test_hdfsload;

结果:

复制代码
mysql> select * from test_hdfsload;
+---------+-----------+------+
| user_id | name      | age  |
+---------+-----------+------+
|       5 | Ava       |   17 |
|      10 | Liam      |   64 |
|       7 | Sophia    |   32 |
|       9 | Emma      |   37 |
|       1 | Emily     |   25 |
|       4 | Alexander |   60 |
|       2 | Benjamin  |   35 |
|       3 | Olivia    |   28 |
|       6 | William   |   69 |
|       8 | James     |   64 |
+---------+-----------+------+
10 rows in set (0.04 sec)

Stream Load 用于将本地文件导入到doris中。Stream Load 是通过 HTTP 协议与 Doris 进行连接交互的。

语法 导入格式:

复制代码
语法示例:
LOAD LABEL test.label_202204(
[MERGE|APPEND|DELETE]  -- 不写就是append 
DATA INFILE
(
"file_path1"[, file_path2, ...]  -- 描述数据的路径   这边可以写多个 ,以逗号分割
)
[NEGATIVE]               -- 负增长
INTO TABLE `table_name`  -- 导入的表名字
[PARTITION (p1, p2, ...)] -- 导入到哪些分区,不符合这些分区的就会被过滤掉
[COLUMNS TERMINATED BY "column_separator"]  -- 指定分隔符
[FORMAT AS "file_type"] -- 指定存储的文件类型
[(column_list)] -- 指定导入哪些列 

[COLUMNS FROM PATH AS (c1, c2, ...)]  -- 从路劲中抽取的部分列
[SET (column_mapping)] -- 对于列可以做一些映射,写一些函数
-- 这个参数要写在要写在set的后面
[PRECEDING FILTER predicate]  -- 在mapping前做过滤做一些过滤
[WHERE predicate]  -- 在mapping后做一些过滤  比如id>10 

[DELETE ON expr] --根据字段去做一些抵消消除的策略  需要配合MERGE
[ORDER BY source_sequence] -- 导入数据的时候保证数据顺序
[PROPERTIES ("key1"="value1", ...)]  -- 一些配置参数

这是一个异步 操作,所以需要去查看下执行的状态.

复制代码
show load order by createtime desc limit 1\G;

参数的说明

  1. load_label:导入任务的唯一 Label
  2. MERGE\|APPEND\|DELETE\]:数据合并类型。默认为 APPEND,表示本次导入是普通的追加写操作。MERGE 和 DELETE 类型仅适用于 Unique Key 模型表。其中 MERGE 类型需要配合 \[DELETE ON\] 语句使用,以标注 Delete Flag 列。而 DELETE 类型则表示本次导入的所有数据皆为删除数据

  3. NEGTIVE:该关键词用于表示本次导入为一批"负"导入。这种方式仅针对具有整型 SUM 聚合类型的聚合数据表。该方式会将导入数据中,SUM 聚合列对应的整型数值取反。主要用于冲抵之前导入错误的数据。
  4. PARTITION(p1, p2, ...):可以指定仅导入表的某些分区。不再分区范围内的数据将被忽略。
  5. COLUMNS TERMINATED BY:指定列分隔符
  6. FORMAT AS:指定要导入文件的类型,支持 CSV、PARQUET 和 ORC 格式。默认为 CSV。
  7. Column list:用于指定原始文件中的列顺序。
  8. COLUMNS FROM PATH AS:指定从导入文件路径中抽取的列。
  9. PRECEDING FILTER:前置过滤条件。数据首先根据 column list 和 COLUMNS FROM PATH AS 按顺序拼接成原始数据行。然后按照前置过滤条件进行过滤。
  10. SET (column_mapping):指定列的转换函数。
  11. WHERE predicate:根据条件对导入的数据进行过滤。
  12. DELETE ON expr:需配合 MEREGE 导入模式一起使用,仅针对 Unique Key 模型的表。用于指定导入数据中表示 Delete Flag 的列和计算关系。
  13. load_properties:指定导入的相关参数。目前支持以下参数:
    1. timeout:导入超时时间。默认为 4 小时。单位秒。
    2. max_filter_ratio:最大容忍可过滤(数据不规范等原因)的数据比例。默认零容忍。取值范围为0到1。
    3. exec_mem_limit:导入内存限制。默认为 2GB。单位为字节。
    4. strict_mode:是否对数据进行严格限制。默认为 false。
    5. timezone:指定某些受时区影响的函数的时区,如 strftime/alignment_timestamp/from_unixtime 等等,具体请查阅 时区 文档。如果不指定,则使用 "Asia/Shanghai" 时区

记录一个问题:

复制代码
type:LOAD_RUN_FAIL; msg:errCode = 2, detailMessage = (bigdata02)[MEM_ALLOC_FAILED]Create Expr failed because [E11] Allocator sys memory check failed: Cannot alloc:64, consuming tracker:<Load#Id=6f5cbea1a558446a-af9cdbd2b4d0d386>, peak used 4096, current used 4096, exec node:<>, process memory used 682.63 MB exceed limit 1.60 GB or sys available memory 41.84 MB less than low water mark 91.08 MB.

	0#  doris::Exception::Exception(int, std::basic_string_view<char, std::char_traits<char> > const&) at /var/local/ldb-toolchain/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:173
	1#  Allocator<false, false, false, DefaultMemoryAllocator>::sys_memory_check(unsigned long) const at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/allocator.cpp:129
	2#  Allocator<false, false, false, DefaultMemoryAllocator>::alloc_impl(unsigned long, unsigned long) at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/allocator.cpp:187
	3#  doris::vectorized::ColumnStr<unsigned int>::reserve(unsigned long) at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/pod_array.h:143
	4#  doris::vectorized::ColumnNullable::reserve(unsigned long) at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/cow.h:198
	5#  doris::vectorized::IDataType::create_column_const(unsigned long, doris::vectorized::Field const&) const at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/cow.h:198
	6#  doris::vectorized::VLiteral::init(doris::TExprNode const&) at /home/zcp/repo_center/doris_release/doris/be/src/vec/common/cow.h:143
	7#  std::_Sp_counted_ptr_inplace<doris::vectorized::VLiteral, std::allocator<doris::vectorized::VLiteral>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<doris::TExprNode const&>(std::allocator<doris::vectorized::VLiteral>, doris::TExprNode const&) at /var/local/ldb-toolchain/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/shared_ptr_base.h:521
	8#  doris::vectorized::VExpr::create_expr(doris::TExprNode const&, std::shared_ptr<doris::vectorized::VExpr>&) at /var/local/ldb-toolchain/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/shared_ptr_base.h:565

以上错误原因是 bigdata02 和 03 的内存已经所剩不多了,调大即可。

进阶参数示例

从 HDFS 导入数据,使用通配符匹配两批文件。分别导入到两个表中

复制代码
LOAD LABEL example_db.label2
(
    DATA INFILE("hdfs://hdfs_host:hdfs_port/input/file-10*")
    INTO TABLE `my_table1`
    PARTITION (p1)
    COLUMNS TERMINATED BY ","
    FORMAT AS "parquet"  
    (id, tmp_salary, tmp_score) 
    SET (
        salary= tmp_salary + 1000,
        score = tmp_score + 10
    ),
    DATA INFILE("hdfs://hdfs_host:hdfs_port/input/file-20*")
    INTO TABLE `my_table2`
    COLUMNS TERMINATED BY ","
    (k1, k2, k3)
)
with HDFS (
"fs.defaultFS"="hdfs://hadoop11:8020",
"hadoop.username"="root"
)

导入数据,并提取文件路径中的分区字段

复制代码
LOAD LABEL example_db.label10
(
    DATA INFILE("hdfs://hdfs_host:hdfs_port/user/hive/warehouse/table_name/dt=20221125/*")
    INTO TABLE `my_table`
    FORMAT AS "csv"
    (k1, k2, k3)
    COLUMNS FROM PATH AS (dt)
)
WITH BROKER hdfs
(
    "username"="hdfs_user",
    "password"="hdfs_password"
);

对导入数据进行过滤。

复制代码
LOAD LABEL example_db.label6
(
    DATA INFILE("hdfs://host:port/input/file")
    INTO TABLE `my_table`
    (k1, k2, k3)
    SET (
        k2 = k2 + 1
    )
        PRECEDING FILTER k1 = 1
    WHERE k1 > k2
)
WITH BROKER hdfs
(
    "username"="user",
    "password"="pass"
);

只有原始数据中,k1 = 1,并且转换后,k1 > k2 的行才会被导入。

取消导入任务

当 Broker load 作业状态不为 CANCELLED 或 FINISHED 时,可以被用户手动取消。

取消时需要指定待取消导入任务的 Label 。取消导入命令语法可执行 HELP CANCEL LOAD 查看。

复制代码
CANCEL LOAD [FROM db_name] WHERE LABEL="load_label";

4.3 使用 Insert 方式同步数据

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

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

复制代码
* INSERT INTO table SELECT ...

* INSERT INTO table VALUES(...)

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

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

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

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

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

5 导出数据

Apache Doris 提供以下三种不同的数据导出方式:

  • SELECT INTO OUTFILE:支持任意 SQL 结果集的导出。
  • EXPORT:支持表级别的部分或全部数据导出。
  • MySQL DUMP:兼容 MySQL Dump 指令的数据导出。

5.1 Export

本文档将介绍如何使用EXPORT命令导出 Doris 中存储的数据。

Export 是 Doris 提供的一种将数据异步导出的功能。该功能可以将用户指定的表或分区的数据,以指定的文件格式,导出到目标存储系统中,包括对象存储、HDFS 或本地文件系统。

Export 是一个异步执行的命令,命令执行成功后,立即返回结果,用户可以通过Show Export 命令查看该 Export 任务的详细信息。

快速上手

建表与导入数据

复制代码
CREATE TABLE IF NOT EXISTS tbl (
    `c1` int(11) NULL,
    `c2` string NULL,
    `c3` bigint NULL
)
DISTRIBUTED BY HASH(c1) BUCKETS 20
PROPERTIES("replication_num" = "1");


insert into tbl values
    (1, 'doris', 18),
    (2, 'nereids', 20),
    (3, 'pipelibe', 99999),
    (4, 'Apache', 122123455),
    (5, null, null);

创建导出作业

导出到 HDFS

将 tbl 表的所有数据导出到 HDFS 上,设置导出作业的文件格式为 csv(默认格式),并设置列分割符为 ,

复制代码
EXPORT TABLE tbl
TO "hdfs://bigdata01:9820/home/tbl/export_" 
PROPERTIES
(
    "line_delimiter" = ","
)
with HDFS (
    "fs.defaultFS"="hdfs://bigdata01:9820",
    "hadoop.username" = "root"
);

查看导出作业

提交作业后,可以通过 SHOW EXPORT 命令查询导出作业状态,结果举例如下:

复制代码
mysql> show export\G
*************************** 1. row ***************************
      JobId: 143265
      Label: export_0aa6c944-5a09-4d0b-80e1-cb09ea223f65
      State: FINISHED
   Progress: 100%
   TaskInfo: {"partitions":[],"parallelism":5,"data_consistency":"partition","format":"csv","broker":"S3","column_separator":"\t","line_delimiter":"\n","max_file_size":"2048MB","delete_existing_files":"","with_bom":"false","db":"tpch1","tbl":"lineitem"}
       Path: s3://bucket/export/export_
 CreateTime: 2024-06-11 18:01:18
  StartTime: 2024-06-11 18:01:18
 FinishTime: 2024-06-11 18:01:31
    Timeout: 7200
   ErrorMsg: NULL
OutfileInfo: [
  [
    {
      "fileNumber": "1",
      "totalRows": "6001215",
      "fileSize": "747503989",
      "url": "s3://bucket/export/export_6555cd33e7447c1-baa9568b5c4eb0ac_*"
    }
  ]
]
1 row in set (0.00 sec)

5.2 SELECT INTO OUTFILE

本文档将介绍如何使用 SELECT INTO OUTFILE 命令进行查询结果的导出操作。

SELECT INTO OUTFILE 命令将 SELECT 部分的结果数据,以指定的文件格式导出到目标存储系统中,包括对象存储或 HDFS。

SELECT INTO OUTFILE 是一个同步命令,命令返回即表示导出结束。若导出成功,会返回导出的文件数量、大小、路径等信息。若导出失败,会返回错误信息

导出到 HDFS

将查询结果导出到文件 hdfs://path/to/ 目录下,指定导出格式为 Parquet:

复制代码
SELECT c1, c2, c3 FROM tbl
INTO OUTFILE "hdfs://bigdata01:9820/home/tbl/result_"
FORMAT AS PARQUET
PROPERTIES
(
    "fs.defaultFS" = "hdfs://bigdata01:9820",
    "hadoop.username" = "root"
);

导出到本地文件系统

导出到本地文件系统功能默认是关闭的。这个功能仅用于本地调试和开发,请勿用于生产环境。

如要开启这个功能请在 fe.conf 中添加 enable_outfile_to_local=true 并且重启 FE

5.3 MySQL Dump

Doris 在 0.15 之后的版本已经支持通过 mysqldump 工具导出数据或者表结构

使用示例

导出

  1. 导出 test 数据库中的 table1 表:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test --tables table1
  2. 导出 test 数据库中的 table1 表结构:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test --tables table1 --no-data
  3. 导出 test1, test2 数据库中所有表:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test1 test2
  4. 导出所有数据库和表 mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --all-databases

更多的使用参数可以参考mysqldump 的使用手册

导入

mysqldump导出的结果可以重定向到文件中,之后可以通过 source 命令导入到 Doris 中 source filenamme.sql

注意

  1. 由于 Doris 中没有 MySQL 里的 tablespace 概念,因此在使用 MySQL Dump 时要加上 --no-tablespaces 参数
  2. 使用 MySQL Dump 导出数据和表结构仅用于开发测试或者数据量很小的情况,请勿用于大数据量的生产环境
相关推荐
玩转数据库管理工具FOR DBLENS2 小时前
企业数据架构选型指南:关系型与非关系型数据库的实战抉择
数据库·测试工具·mysql·oracle·架构·nosql
共享家95272 小时前
Redis背景知识
数据库·redis·缓存
盐焗西兰花2 小时前
鸿蒙学习实战之路-数据持久化键值型数据库KV-Store全攻略
数据库·学习·harmonyos
青春不流名2 小时前
通过geoip自动更新GeoLite2-ASN GeoLite2-City GeoLite2-Country
数据库
Rysxt_2 小时前
IDEA中Git隐藏更改(Stash)功能详解教程
数据库·git·intellij-idea·stash
gugugu.3 小时前
Redis持久化机制详解(二):AOF持久化全解析
数据库·redis·缓存
Hello.Reader3 小时前
Flink SQL 的 RESET 语句一键回到默认配置(SQL CLI 实战)
数据库·sql·flink
摇滚侠3 小时前
Redis 零基础到进阶,Redis 事务,Redis 管道,Redis 发布订阅,笔记47-54
数据库·redis·笔记
UVM_ERROR3 小时前
UVM实战:RDMA Host侧激励开发全流程问题排查与解决
服务器·网络·数据库