1. Doris数据表创建基础准备
在开始创建Doris数据表之前,我们需要先完成一些基础准备工作。这些步骤虽然简单,但对于后续的数据管理和权限控制至关重要。
1.1 用户创建与权限管理
Doris作为企业级MPP数据库,完善的用户权限体系是保障数据安全的第一道防线。我们先从最基本的用户创建开始:
sql复制-- 创建新用户并设置密码
CREATE USER 'hello' IDENTIFIED BY 'hello';
这个命令创建了一个名为'hello'的用户,密码也是'hello'。在实际生产环境中,建议使用更复杂的密码策略,比如包含大小写字母、数字和特殊字符的组合。
安全提示:永远不要使用像示例中这样简单的用户名和密码组合。建议采用密码管理工具生成强密码,并定期更换。
1.2 数据库Schema创建
创建完用户后,我们需要为其准备一个工作空间——数据库Schema:
sql复制-- 创建测试数据库
CREATE DATABASE test_db;
这个命令创建了一个名为test_db的数据库。在Doris中,数据库是表的逻辑容器,类似于MySQL中的database概念。一个Doris集群可以包含多个数据库,每个数据库相互隔离。
1.3 权限分配
有了用户和数据库,接下来需要将两者关联起来:
sql复制-- 为用户分配数据库权限
GRANT ALL ON test_db TO hello;
这条命令给'hello'用户授予了test_db数据库的所有权限,包括创建表、插入数据、查询、删除等操作权限。在实际项目中,应根据最小权限原则,只授予必要的权限。
权限管理最佳实践:
- 为不同角色创建不同用户
- 遵循最小权限原则
- 定期审计权限分配
- 生产环境避免使用ALL权限
2. Doris表的核心概念解析
在正式创建表之前,我们需要深入理解Doris表的核心概念和设计哲学。这些知识将帮助我们设计出高性能的数据表。
2.1 行与列的设计
Doris表由行(Row)和列(Column)组成,这一点与传统关系型数据库类似,但在存储和处理上有其独特之处。
排序列与非排序列:
- 排序列:作为表的Key列,数据会按照这些列排序存储
- 非排序列:普通列,不参与排序
聚合模型中的Key和Value:
- Key列:相当于维度列,相同Key的行会被聚合
- Value列:相当于指标列,需要指定聚合函数(SUM、MAX等)
sql复制-- Key列示例
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`date` DATE NOT NULL COMMENT "数据灌入日期时间"
-- Value列示例
`cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间"
2.2 分区与分桶机制
Doris采用两级数据分片策略,这是其高性能查询的基石。
Partition(分区):
- 第一级数据划分
- 支持RANGE和LIST两种分区方式
- 通常按时间或枚举值划分
- 是数据管理的最小逻辑单元
Tablet(分桶):
- 第二级数据划分
- 采用Hash分桶方式
- 是数据移动和复制的最小物理单元
- 影响查询并行度和数据均衡
sql复制-- 分区示例
PARTITION BY RANGE(`date`)
(
PARTITION `p201701` VALUES LESS THAN ("2017-02-01")
)
-- 分桶示例
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
3. Doris建表语法详解
掌握了基本概念后,我们来深入解析Doris的建表语法,这是创建高效数据表的关键。
3.1 基础建表语句
Doris的建表语法丰富而灵活,以下是完整的语法结构:
sql复制CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [database.]table_name
(column_definition1[, column_definition2, ...]
[, index_definition1[, index_definition12,]])
[ENGINE = [olap|mysql|broker|hive]]
[key_desc]
[COMMENT "table comment"];
[partition_desc]
[distribution_desc]
[rollup_index]
[PROPERTIES ("key"="value", ...)]
[BROKER PROPERTIES ("key"="value", ...)];
关键参数说明:
EXTERNAL:创建外部表IF NOT EXISTS:表不存在时才创建ENGINE:指定存储引擎,默认为olapkey_desc:指定数据模型(AGGREGATE/UNIQUE/DUPLICATE)partition_desc:分区定义distribution_desc:分桶定义PROPERTIES:表属性配置
3.2 数据模型选择
Doris支持三种数据模型,适应不同业务场景:
-
Aggregate模型:
- 相同Key的行会自动聚合
- Value列需要指定聚合函数(SUM,MAX等)
- 适合报表、分析场景
-
Unique模型:
- 相同Key的行后写入的覆盖先写入的
- 实现行级更新
- 适合需要实时更新的维表
-
Duplicate模型:
- 保留所有数据行
- 不进行聚合
- 适合原始日志存储
sql复制-- Aggregate模型示例
AGGREGATE KEY(`user_id`, `date`)
-- Unique模型示例
UNIQUE KEY(`user_id`, `date`)
-- Duplicate模型示例
DUPLICATE KEY(`user_id`, `date`)
3.3 字段类型选择
Doris支持丰富的字段类型,合理选择类型对存储和性能至关重要:
| 类型分类 | 推荐类型 | 适用场景 | 注意事项 |
|---|---|---|---|
| 整型 | TINYINT | 状态标记、枚举值 | 范围-128~127 |
| INT | 常规ID、计数 | 平衡存储和范围 | |
| BIGINT | 大数值ID | 避免过度使用 | |
| 浮点型 | DOUBLE | 常规小数运算 | 有精度损失 |
| DECIMAL | 财务精确计算 | 指定精度和小数位 | |
| 字符串 | VARCHAR | 变长字符串 | 按需指定长度 |
| TEXT | 大文本 | 避免过度使用 | |
| 时间 | DATE | 仅日期 | 格式'YYYY-MM-DD' |
| DATETIME | 日期时间 | 精确到秒 |
类型选择建议:
- 尽量使用数值类型而非字符串
- 选择够用的最小类型
- 时间类型优先于字符串存储时间
- 避免使用过大的VARCHAR长度
4. 高级表设计技巧
掌握了基础语法后,我们来看一些高级表设计技巧,这些技巧能显著提升表的使用性能。
4.1 分区策略设计
合理的分区设计能极大提升查询效率和管理便利性。
RANGE分区:
- 按连续范围划分
- 适合时间序列数据
- 支持历史分区冷存储
sql复制PARTITION BY RANGE(`date`)
(
PARTITION `p202301` VALUES LESS THAN ("2023-02-01"),
PARTITION `p202302` VALUES LESS THAN ("2023-03-01")
)
LIST分区:
- 按离散值划分
- 适合枚举类型数据
- 查询时能有效裁剪分区
sql复制PARTITION BY LIST(`city`)
(
PARTITION `p_east` VALUES IN ("Beijing","Shanghai"),
PARTITION `p_west` VALUES IN ("Chengdu","Chongqing")
)
分区管理技巧:
- 时间分区通常按天/周/月划分
- 单个分区数据量建议在1-10GB
- 热数据分区可以设置更小的粒度
- 定期归档冷数据分区
4.2 分桶设计优化
分桶设计直接影响数据分布的均衡性和查询效率。
分桶列选择原则:
- 选择高基数列(不同值多的列)
- 选择经常作为查询条件的列
- 避免选择可能倾斜的列
分桶数量确定:
- 建议分桶数量为BE节点数的整数倍
- 单个Tablet大小建议在1-10GB
- 考虑并发查询需求
sql复制-- 好的分桶示例
DISTRIBUTED BY HASH(`user_id`) BUCKETS 32
-- 可能有问题分桶示例
DISTRIBUTED BY HASH(`gender`) BUCKETS 10 -- 基数太低
4.3 高级属性配置
通过PROPERTIES可以配置表的各种高级属性。
副本配置:
sql复制"replication_num" = "3" -- 每个Tablet的副本数
存储介质配置:
sql复制"storage_medium" = "SSD", -- 初始存储在SSD
"storage_cooldown_time" = "2023-12-31 23:59:59" -- 冷却时间
其他重要配置:
sql复制"enable_persistent_index" = "true", -- 启用持久化索引
"bloom_filter_columns" = "user_id,order_id" -- 布隆过滤列
5. 完整建表示例与最佳实践
现在,我们通过几个完整的建表示例,来展示不同场景下的最佳实践。
5.1 用户行为分析表示例
这是一个典型的用户行为分析表,采用Aggregate模型:
sql复制CREATE TABLE IF NOT EXISTS test_db.user_behavior
(
`user_id` LARGEINT NOT NULL COMMENT "用户ID",
`date` DATE NOT NULL COMMENT "数据日期",
`city` VARCHAR(50) COMMENT "城市",
`device` VARCHAR(50) COMMENT "设备类型",
`pv` BIGINT SUM DEFAULT "0" COMMENT "页面浏览量",
`uv` BIGINT REPLACE DEFAULT "0" COMMENT "独立访客",
`duration` BIGINT SUM DEFAULT "0" COMMENT "停留时长(秒)",
`bounce_rate` DOUBLE MAX DEFAULT "0.0" COMMENT "跳出率"
)
ENGINE=olap
AGGREGATE KEY(`user_id`, `date`, `city`, `device`)
PARTITION BY RANGE(`date`)
(
PARTITION `p202301` VALUES LESS THAN ("2023-02-01"),
PARTITION `p202302` VALUES LESS THAN ("2023-03-01"),
PARTITION `p202303` VALUES LESS THAN ("2023-04-01")
)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 32
PROPERTIES
(
"replication_num" = "3",
"storage_medium" = "SSD",
"storage_cooldown_time" = "2023-12-31 23:59:59",
"enable_persistent_index" = "true"
);
设计要点:
- 按日期范围分区,便于管理时间序列数据
- 使用user_id哈希分桶,保证数据均匀分布
- 为不同指标选择合适的聚合函数
- 配置SSD存储和持久化索引提升性能
5.2 电商订单表示例
这是一个电商订单表,采用Unique模型支持行级更新:
sql复制CREATE TABLE IF NOT EXISTS test_db.ecommerce_orders
(
`order_id` VARCHAR(50) NOT NULL COMMENT "订单ID",
`user_id` LARGEINT NOT NULL COMMENT "用户ID",
`order_time` DATETIME NOT NULL COMMENT "下单时间",
`total_amount` DECIMAL(12,2) COMMENT "订单金额",
`payment_method` VARCHAR(20) COMMENT "支付方式",
`order_status` TINYINT COMMENT "订单状态",
`update_time` DATETIME REPLACE DEFAULT CURRENT_TIMESTAMP COMMENT "更新时间"
)
ENGINE=olap
UNIQUE KEY(`order_id`)
PARTITION BY RANGE(`order_time`)
(
PARTITION `p2023q1` VALUES LESS THAN ("2023-04-01"),
PARTITION `p2023q2` VALUES LESS THAN ("2023-07-01"),
PARTITION `p2023q3` VALUES LESS THAN ("2023-10-01"),
PARTITION `p2023q4` VALUES LESS THAN ("2024-01-01")
)
DISTRIBUTED BY HASH(`order_id`) BUCKETS 64
PROPERTIES
(
"replication_num" = "3",
"enable_persistent_index" = "true",
"light_schema_change" = "true"
);
设计要点:
- 使用Unique模型支持订单状态更新
- 按季度分区管理订单数据
- 使用order_id分桶避免热点
- 启用light_schema_change支持快速schema变更
5.3 日志存储表示例
这是一个日志存储表,采用Duplicate模型保留原始日志:
sql复制CREATE TABLE IF NOT EXISTS test_db.app_logs
(
`log_time` DATETIME NOT NULL COMMENT "日志时间",
`log_level` VARCHAR(10) COMMENT "日志级别",
`service_name` VARCHAR(50) COMMENT "服务名称",
`host_ip` VARCHAR(15) COMMENT "主机IP",
`trace_id` VARCHAR(36) COMMENT "追踪ID",
`log_content` TEXT COMMENT "日志内容"
)
ENGINE=olap
DUPLICATE KEY(`log_time`, `service_name`, `host_ip`)
PARTITION BY RANGE(`log_time`)
(
PARTITION `p202301` VALUES LESS THAN ("2023-02-01"),
PARTITION `p202302` VALUES LESS THAN ("2023-03-01")
)
DISTRIBUTED BY HASH(`host_ip`) BUCKETS 48
PROPERTIES
(
"replication_num" = "2",
"storage_medium" = "HDD",
"disable_auto_compaction" = "false"
);
设计要点:
- 使用Duplicate模型保留完整日志
- 按主机IP分桶,便于按主机查询
- 使用HDD存储降低成本
- 保持自动压缩启用节省空间
6. 常见问题与性能优化
在实际使用中,我们会遇到各种问题和性能挑战。这里总结一些常见问题和优化建议。
6.1 常见错误与解决方法
问题1:字段顺序错误
错误信息:Key columns should be listed before value columns
解决方案:确保所有Key列(维度列)都定义在Value列(指标列)之前。
问题2:分区值格式错误
错误信息:Invalid partition value format
解决方案:分区值必须用双引号括起来,即使分区列是数值类型。
问题3:分桶数不合理
错误信息:Bucket number is too small or too large
解决方案:分桶数建议在10-100之间,具体取决于数据量和BE节点数。
6.2 性能优化建议
1. 查询性能优化:
- 合理设计分区键和分桶键
- 为常用查询条件创建物化视图
- 使用Colocate Group将相关表放在一起
2. 导入性能优化:
- 批量导入优于单条插入
- 合理设置导入并行度
- 避免高频小批量导入
3. 存储优化:
- 热数据放在SSD,冷数据放在HDD
- 定期执行COMPACT操作合并小文件
- 对不常查询的历史数据设置TTL
6.3 监控与维护
关键监控指标:
- Tablet健康状态
- 副本分布均衡性
- 查询延迟和QPS
- 导入速度和成功率
日常维护操作:
sql复制-- 查看表结构
DESC test_db.user_behavior;
-- 查看分区信息
SHOW PARTITIONS FROM test_db.user_behavior;
-- 手动触发Compaction
ALTER TABLE test_db.user_behavior COMPACT;
-- 修改副本数
ALTER TABLE test_db.user_behavior SET ("replication_num" = "2");
在实际使用Doris的过程中,我发现合理设计分区和分桶策略对性能影响最大。一个常见的误区是过度分区,导致大量小文件影响性能。建议根据数据量和查询模式找到平衡点,通常单个分区在1-10GB比较合适。另外,定期监控和调整表结构也是保持系统高效运行的关键。