三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Apache Doris建表实战:从数据模型到分区分桶的完整指南

Apache Doris建表实战:从数据模型到分区分桶的完整指南

在数据仓库项目中,数据表是承载业务数据的核心载体,其设计质量直接决定了后续查询分析的效率与便捷性。Apache Doris 作为一款高性能的实时分析数据库,其建表过程融合了MPP数据库的分布式特性和数据仓库的建模思想,理解其表创建逻辑是高效使用Doris的第一步。本文将系统性地拆解Doris数据表的创建过程,从核心概念、表类型选择、到详细的建表示例与最佳实践,手把手带你掌握从零到一构建Doris数据表的完整技能树。

1. 理解Doris表的核心概念与设计哲学

在动手创建表之前,我们需要先理解Doris表设计的几个核心思想,这能帮助我们在后续做出更合理的选择。

1.1 数据模型:决定数据分布与聚合方式

Doris主要支持三种数据模型,这是建表时第一个也是最重要的选择。

  • Duplicate 明细模型:表中存在重复的Key列。数据完全按照导入文件中的原始数据存储,不会进行任何聚合。适用于需要保留原始明细日志、分析用户行为序列等场景。例如,用户点击流数据,每一行都是一次独立的点击事件。
  • Aggregate 聚合模型:表中相同的Key列,其Value列会按照指定的聚合函数(如SUM、MAX、MIN、REPLACE)进行聚合。这大大减少了数据量,提升了查询性能。适用于报表类、统计类场景。例如,每日的商品销售总额表,相同商品ID的销售额会被累加。
  • Unique 主键模型:同样是聚合模型的一种特化,它保证了Key的唯一性,对于相同Key的数据,后导入的数据会替换先导入的数据(REPLACE)。适用于有数据更新需求的场景,如用户画像表、订单状态表。

1.2 数据分布:影响查询并行度与存储均衡

Doris是分布式系统,数据表会被切分并存储在不同的节点上。数据分布策略决定了数据如何被切分和放置。

  • 分区(Partition):通常按时间进行分区(如按天、按月),类似于将一张大表在物理上划分为多个独立管理的子表。分区可以有效地进行数据生命周期管理(如删除旧分区),并能大幅提升针对时间范围的查询效率。
  • 分桶(Bucket/Distribution):在分区内,数据会进一步被划分为多个分桶。分桶规则通常是指定一个或多个列,通过Hash算法将数据行映射到不同的分桶中。合理的分桶能保证数据在各个节点上均匀分布,避免数据倾斜,同时相同分桶键的查询可以快速定位到数据所在机器。

1.3 索引与物化视图:加速查询的利器

  • 前缀索引:Doris会对表中前36个字节的数据自动生成前缀索引。在查询时,这些索引能帮助快速定位数据块。因此,将查询条件中高频使用的列放在表定义的前部,能有效利用前缀索引。
  • RollUp 物化视图:这是一种预计算的加速手段。你可以基于一张基表,创建若干RollUp表,它们以不同的维度、度量或排序方式存储数据。查询时,Doris的优化器会自动选择最合适的RollUp来响应,对于聚合查询的加速效果尤为显著。

2. 环境准备与基础操作

在开始建表前,请确保你有一个可用的Doris环境。你可以通过Doris Manager进行可视化部署,或参照官方文档进行单机或集群部署。

2.1 连接Doris数据库

我们通常通过MySQL客户端连接Doris进行SQL操作。确保你已安装mysql客户端。

# 连接Doris FE(前端)节点,默认端口9030 mysql -h FE_HOST -P 9030 -u root -p # 输入密码后,进入Doris SQL命令行

2.2 创建数据库

表必须存在于某个数据库下。首先创建一个用于测试的数据库。

-- 查看已有数据库 SHOW DATABASES; -- 创建新的数据库,并指定默认的副本数(单机测试可设为1) CREATE DATABASE IF NOT EXISTS demo_db; USE demo_db;

3. 三种数据模型的建表示例与详解

下面我们通过具体的业务场景,来创建三种不同模型的数据表。

3.1 创建明细模型(Duplicate)表

场景:存储用户行为日志,需要记录每一次点击事件的完整信息,包括用户ID、时间、页面、动作等。

CREATE TABLE IF NOT EXISTS user_behavior_duplicate ( `user_id` BIGINT NOT NULL COMMENT "用户ID", `date` DATE NOT NULL COMMENT "数据灌入日期", `timestamp` DATETIME NOT NULL COMMENT "行为发生时间戳", `page` VARCHAR(20) NOT NULL COMMENT "所在页面", `action` VARCHAR(10) NOT NULL COMMENT "用户行为,如click, view" ) -- 指定数据模型为Duplicate,并列出所有的Key列(这里所有列都是Key,因为不聚合) DUPLICATE KEY(`user_id`, `date`, `timestamp`, `page`, `action`) -- 数据分布策略 COMMENT "用户行为明细表" DISTRIBUTED BY HASH(`user_id`) BUCKETS 10 PROPERTIES ( "replication_num" = "1" -- 副本数,单机设为1 );

关键点解析

  1. DUPLICATE KEY:虽然叫“Key”,但在明细模型中它仅用于指定数据排序和存储的顺序,不保证唯一性。将查询条件中最常使用的列放在前面,能利用前缀索引。
  2. DISTRIBUTED BY HASH(user_id) BUCKETS 10:指定按user_id的Hash值将数据分布到10个分桶中。同一个用户的日志有很大概率会分布在同一个桶内,便于用户维度的查询。

3.2 创建聚合模型(Aggregate)表

场景:存储每日商品销售聚合数据,需要按商品和日期统计销售总额和订单量。

CREATE TABLE IF NOT EXISTS daily_sales_agg ( `date` DATE NOT NULL COMMENT "销售日期", `product_id` INT NOT NULL COMMENT "商品ID", `category` VARCHAR(50) COMMENT "商品类别", `total_sales_amount` BIGINT SUM COMMENT "当日该商品销售总额", `total_order_count` BIGINT SUM COMMENT "当日该商品订单总数", `latest_order_time` DATETIME MAX COMMENT "当日最晚订单时间" ) -- 指定聚合模型,并定义聚合的Key列 AGGREGATE KEY(`date`, `product_id`, `category`) COMMENT "每日商品销售聚合表" -- 按日期进行分区,便于管理历史数据 PARTITION BY RANGE(`date`) ( PARTITION `p202401` VALUES LESS THAN ("2024-02-01"), PARTITION `p202402` VALUES LESS THAN ("2024-03-01") ) -- 在分区内,按商品ID分桶 DISTRIBUTED BY HASH(`product_id`) BUCKETS 8 PROPERTIES ( "replication_num" = "1", "storage_medium" = "SSD" -- 存储介质,默认为HDD );

关键点解析

  1. AGGREGATE KEY:定义了聚合的维度列。只有Key列相同的行才会被聚合。
  2. 聚合函数:在定义Value列时直接指定了聚合方式,如SUMMAX。导入数据时,Doris会自动对相同Key行的这些列进行聚合。
  3. PARTITION BY RANGE:这是一个非常重要的分区示例。我们按date字段进行了范围分区,将不同月份的数据物理分开。对于时间序列数据,这能极大优化按时间范围的查询,并且可以方便地删除过期分区(ALTER TABLE ... DROP PARTITION ...)。

3.3 创建主键模型(Unique)表

场景:存储用户最新信息表,用户信息会随着业务更新,需要保证每个用户只有一条最新记录。

CREATE TABLE IF NOT EXISTS user_profile_unique ( `user_id` BIGINT NOT NULL COMMENT "用户ID", `username` VARCHAR(50) NOT NULL COMMENT "用户名", `city` VARCHAR(100) COMMENT "所在城市", `age` SMALLINT COMMENT "年龄", `last_login` DATETIME COMMENT "最后登录时间", `last_update` DATETIME REPLACE COMMENT "记录最后更新时间" ) -- 指定主键模型,并定义唯一键(主键) UNIQUE KEY(`user_id`) COMMENT "用户画像表(主键模型)" DISTRIBUTED BY HASH(`user_id`) BUCKETS 6 PROPERTIES ( "replication_num" = "1", "enable_persistent_index" = "true" -- 启用持久化索引,优化主键查询性能 );

关键点解析

  1. UNIQUE KEY:指定了主键列user_id,保证了该列的唯一性。
  2. REPLACE聚合函数:对于非主键列last_update,我们指定了REPLACE函数。当导入两条user_id相同的数据时,所有未指定聚合函数的列(如username,city)会被后一条数据整体替换,而last_update列则会取后一条数据的值。这完美实现了“ Upsert ”(更新插入)语义。

4. 进阶:使用分区与分桶优化大型表

对于数据量巨大的表,合理使用分区和分桶是性能的关键。

4.1 动态分区管理

手动管理每天的分区非常繁琐。Doris支持动态分区,可以自动创建和删除分区。

CREATE TABLE IF NOT EXISTS dynamic_sales_log ( `dt` DATE NOT NULL COMMENT "日志日期", `order_id` VARCHAR(50) NOT NULL COMMENT "订单号", `amount` BIGINT COMMENT "金额" ) DUPLICATE KEY(`dt`, `order_id`) COMMENT "动态分区表示例" PARTITION BY RANGE(`dt`)() -- 使用动态分区属性 DISTRIBUTED BY HASH(`order_id`) BUCKETS 10 PROPERTIES ( "replication_num" = "1", "dynamic_partition.enable" = "true", -- 开启动态分区 "dynamic_partition.time_unit" = "DAY", -- 按天分区 "dynamic_partition.start" = "-7", -- 保留最近7天的分区 "dynamic_partition.end" = "3", -- 提前创建未来3天的分区 "dynamic_partition.prefix" = "p", -- 分区名前缀 "dynamic_partition.buckets" = "10" -- 动态创建的分区的分桶数 );

配置后,Doris会自动管理以p20240401格式命名的分区,始终保持最近7天到未来3天的分区存在,过期分区会自动删除。

4.2 分桶数优化建议

分桶数直接影响查询的并行度和单个分桶的数据量。

  • 数量:建议在10-100个之间。分桶数应略小于集群节点数 * CPU核数,以充分利用集群资源。
  • 数据量:单个分桶的数据量建议在100MB到1GB之间。可以通过总数据量 / 分桶数来估算。
  • 分桶键选择:选择高基数列(值不重复或重复少的列),如用户ID、订单号,以保证数据均匀分布。避免使用低基数列如“性别”、“状态”,这会导致严重的数据倾斜。

5. 表管理常用操作

创建表后,我们经常需要进行一些管理操作。

5.1 查看与修改表结构

-- 查看表创建语句 SHOW CREATE TABLE daily_sales_agg; -- 查看表结构 DESC daily_sales_agg; -- 增加一个列(聚合模型表增加Value列) ALTER TABLE daily_sales_agg ADD COLUMN avg_price DOUBLE SUM COMMENT "平均价格"; -- 修改分桶数(这是一个异步操作) ALTER TABLE daily_sales_agg DISTRIBUTED BY HASH(`product_id`) BUCKETS 12;

5.2 删除与清空表

-- 清空表数据(保留表结构) TRUNCATE TABLE user_behavior_duplicate; -- 删除表(谨慎操作!) DROP TABLE IF EXISTS user_behavior_duplicate;

6. 数据导入与验证

表创建好后,我们需要导入数据并验证表的设计是否合理。

6.1 使用Stream Load导入测试数据

准备一个简单的CSV文件test_data.csv

2024-01-15,1001,Electronics,5000,50,2024-01-15 23:59:59 2024-01-15,1001,Electronics,3000,30,2024-01-15 22:30:00 2024-01-16,1002,Books,200,5,2024-01-16 10:00:00

使用curl命令通过Stream Load方式导入到聚合表daily_sales_agg

curl --location-trusted -u root: -H “label:label1” -H “column_separator:,” -T test_data.csv http://FE_HOST:8030/api/demo_db/daily_sales_agg/_stream_load

注意替换FE_HOST为你的Doris FE节点地址,root:后为你的密码(若无密码则留空)。

6.2 查询验证数据

导入后,查询表数据,观察聚合模型的效果:

SELECT * FROM daily_sales_agg ORDER BY date, product_id;

预期结果:商品1001在2024-01-15的两条记录会被聚合成一条,total_sales_amount变为8000,total_order_count变为80,latest_order_time取最大值。

7. 常见问题与排查思路

问题现象常见原因解决思路
建表失败,报错Failed to create partition分区键的值不符合分区范围或动态分区配置错误。检查PARTITION BY RANGE语句中的范围定义,或动态分区属性start/end的值是否合理。
数据导入后,查询结果未聚合1. 使用了明细模型表。
2. 导入数据的分区/分桶键与表定义不一致,导致未被认为是相同Key。
1. 确认表模型是否为AGGREGATE
2. 检查导入数据的列顺序、类型是否与表定义完全匹配。
查询速度慢,尤其是全表扫描1. 未有效利用分区进行剪枝。
2. 分桶数不合理,导致数据倾斜或并行度不足。
3. 缺乏有效的RollUp物化视图。
1. 在查询条件中带上分区键。
2. 使用SHOW DATA查看数据分布,调整分桶键和分桶数。
3. 分析慢查询,创建针对性的RollUp。
ALTER TABLE修改分桶数长时间不生效修改分桶是异步操作,需要后台数据重分布。使用SHOW ALTER TABLE COLUMN查看任务进度。对于大表,此操作耗时较长,建议在业务低峰期进行。
内存不足错误(Out of memory)单次导入数据量过大,或查询涉及的分区/数据量过大。1. 对于Stream Load,调小单个导入文件大小,增加-H “exec_mem_limit:xxxx”参数。
2. 优化查询,避免SELECT *,增加过滤条件。
3. 检查BE节点内存配置。

8. 最佳实践与工程建议

  1. 设计先行,模型为王:在建表前,务必明确数据的使用场景。是点查更新(主键模型)?是聚合报表(聚合模型)?还是原始日志分析(明细模型)?选错模型会事倍功半。
  2. 分区策略紧跟业务:绝大多数分析场景都与时间相关。强烈建议按时间字段(天、周、月)进行分区。这不仅能加速时间范围查询,更是数据生命周期管理(TTL)的基础。
  3. 分桶是均匀分布的关键:选择高基数的、经常作为查询条件的列作为分桶键。分桶数需要根据数据总量和集群规模仔细测算,并在建表初期就尽量规划好,因为后续调整成本较高。
  4. 善用物化视图(RollUp):对于频繁出现的聚合查询、固定维度的上卷查询,创建对应的RollUp是性价比最高的优化手段。Doris的查询优化器会自动路由。
  5. 列类型选择要精准:在满足业务需求的前提下,选择尽可能小的数据类型。例如,能用INT就不要用BIGINT,能用VARCHAR(20)就不要用VARCHAR(255)。这能节省存储空间,提升内存计算效率。
  6. 规范注释:为每个数据库、表、列添加清晰的COMMENT。这在团队协作和后期维护中价值巨大。
  7. 测试环境验证:在生产环境执行建表、改表、删分区等DDL操作前,务必在测试环境进行完整验证,特别是当表数据量很大时。
  8. 监控与调整:表创建并运行一段时间后,通过SHOW DATASHOW PROC ‘/dbs’等命令监控数据分布和存储情况,根据实际情况进行优化调整。

掌握Doris建表,就相当于掌握了这座高性能数据仓库的“地基”建造技术。从理解三种数据模型的本质差异开始,到熟练运用分区分桶进行物理设计,再到通过索引和物化视图进行查询加速,每一步都需要结合具体的业务需求和数据特性来权衡。建议你在自己的测试环境中,将本文的示例逐一运行一遍,并尝试导入自己的数据,通过实践来加深对每个参数和配置项的理解。当你能为你的业务场景设计出最合适的Doris表结构时,你就已经为后续的实时数据分析打下了最坚实的基础。

← 返回列表