基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

什么是 Doris 的前缀索引(Short Key Index)?如何合理排列建表语句中的 Key 列以最大化前缀索引的过滤效果?

在 Apache Doris 中,前缀索引(Short Key Index) 是其核心的查询加速机制之一。理解它的原理并合理设计建表语句,能够极大地提升查询性能。

以下是关于 Doris 前缀索引的详细解释以及如何优化 Key 列排列的指南:


一、 什么是 Doris 的前缀索引?

1. 基本概念
Doris 的数据在底层存储时,是严格按照建表语句中指定的 Key 列(Aggregate Key、Unique Key 或 Duplicate Key)进行字典序排序存储的。
前缀索引是一种稀疏索引。Doris 会在排好序的数据上,每隔一定的数据行(默认 1024 行构成一个 Data Block)提取该 Block 第一行的前几个列的值作为索引项。查询时,Doris 会先通过二分查找匹配前缀索引,快速定位到数据可能存在的 Block,从而跳过大量无关数据(Block 级别裁剪)。

2. 前缀索引的生成规则(核心!)
Doris 并不会把所有的 Key 列都放入前缀索引,而是有严格的截断规则:

  • 36 字节限制:前缀索引最多包含前 36 个字节的数据。
  • VARCHAR 截断规则:当遇到 VARCHARSTRING 类型的列时,前缀索引会包含该列,但会立即在该列截断。即排在 VARCHAR 之后的列,无论是否达到 36 字节,都不会进入前缀索引。

举个例子:
假设建表 Key 列如下:

  1. user_id (INT, 4 字节)
  2. age (SMALLINT, 2 字节)
  3. message (VARCHAR(50))
  4. join_date (DATE, 3 字节)

前缀索引包含的内容是user_id + age + message(前30个字节)
原因:4 + 2 + 30 = 36 字节。遇到 message 是 VARCHAR,索引在此截断。后面的 join_date 即使是 Key 列,也无法享受前缀索引加速。


二、 如何合理排列 Key 列以最大化过滤效果?

为了让查询尽可能多地命中前缀索引,你需要遵循以下几个核心原则:

1. 遵循“最左前缀匹配”原则

前缀索引的查询类似于 MySQL 的联合索引,必须从左到右连续匹配。

  • 原则:将查询中最频繁作为过滤条件(WHERE 子句)的列放在最前面。
  • 场景:如果你的表大部分查询都是基于 dateshop_id,那么这两个列必须放在前面。如果查询条件跳过了第一列(例如 WHERE shop_id = 1,但表的第一列是 date),则无法使用前缀索引。

2. 规避 VARCHAR 截断陷阱

由于 VARCHAR 会截断前缀索引,错误的放置会导致后续列的索引失效。

  • 原则将定长类型(INT, BIGINT, DATE, DATETIME 等)放在前面,将 VARCHAR 放到 Key 列的最后面。
  • 反面教材CREATE TABLE ... DUPLICATE KEY (user_name VARCHAR, age INT, date DATE)。这里前缀索引只有 user_name,后面的 agedate 完全没用上。
  • 正面教材CREATE TABLE ... DUPLICATE KEY (date DATE, age INT, user_name VARCHAR)。这样三个列都能进入前缀索引(只要总大小不超过 36 字节)。

3. 区分度(基数)与业务逻辑的平衡

  • 原则:在满足前两点的基础上,将区分度高(Cardinality 大)且经常用于等值查询的列尽量靠前。
  • 特例(时间列):在日志分析、事实表场景中,DATEDATETIME 列虽然区分度不如 user_id 高,但几乎所有的查询都会带上时间范围(过滤掉 90% 的历史数据)。因此,通常建议把时间列放在第一列,接着放区分度高的维度列(如 user_id, device_id)。

4. 充分利用 36 字节

  • 原则:不要在前面放过大的定长字段(比如 DECIMAL 或长 CHAR),尽量让 36 字节能包含更多的维度列。
  • 常见类型字节数TINYINT (1), SMALLINT (2), INT (4), BIGINT (8), DATE (3), DATETIME (8)。
  • 如果你把 INT, INT, DATE 放前面,总共才 11 字节,前缀索引可以轻松覆盖这三个列。

三、 综合实战案例

假设我们要建一张用户行为日志表,字段包括:log_time (DATETIME), user_id (BIGINT), event_type (INT), device_model (VARCHAR), ip (VARCHAR)。

❌ 糟糕的排列(反例):

sql
CREATE TABLE log_table (
    device_model VARCHAR(50), 
    user_id BIGINT,
    log_time DATETIME,
    event_type INT,
    ip VARCHAR(20)
)
DUPLICATE KEY(device_model, user_id, log_time, event_type)
-- 结果:前缀索引只包含 device_model。
-- 灾难:查询 WHERE log_time > '2023-01-01' 时,必须全表扫描。

✅ 优秀的排列(正例):

sql
CREATE TABLE log_table (
    log_time DATETIME,      -- 8 字节,几乎所有查询必带
    event_type INT,         -- 4 字节,经常用于过滤事件
    user_id BIGINT,         -- 8 字节,精确查找
    device_model VARCHAR(50), -- 截断点,放最后
    ip VARCHAR(20)
)
DUPLICATE KEY(log_time, event_type, user_id, device_model)
-- 结果:前缀索引包含了 log_time(8) + event_type(4) + user_id(8) = 20 字节。
-- 剩余 16 字节分配给 device_model 的前缀。
-- 优势:基于时间范围、事件类型、用户的查询,都能完美命中前缀索引,实现极速响应。

四、 补充优化手段(当一个前缀索引不够用时)

在实际业务中,查询条件可能是多变的。如果查询条件无法命中当前的前缀索引,可以通过以下方式弥补:

  1. Rollup / 倒排索引:如果除了按时间+用户查询,还需要频繁按 device_model 查询,且不带时间条件,可以创建一个以 device_model 为第一列的 Rollup 表,或者在 device_model 上建立倒排索引(Inverted Index)
  2. Bloom Filter 索引:对于高基数(高区分度)且经常进行等值查询(=IN)的列(如 user_id),如果它排在前缀索引后面,可以为其单独添加 Bloom Filter 索引以加速。
00:00
00:00