欢迎光临
我们一直在努力

Apache Doris 前缀索引与物化视图:加速查询的 Rollup 设计与自动匹配机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

Apache Doris 前缀索引与物化视图:加速查询的 Rollup 设计与自动匹配机制

1. Doris 前缀索引基础与优势

Apache Doris(原百度 Palo)是一款高性能的实时分析型数据库,其前缀索引(Prefix Index)是提升查询效率的关键技术之一。前缀索引是一种基于列式存储的索引结构,它通过对数据列的前几个字节建立索引,从而加速查询过程。

与传统索引相比,Doris 前缀索引具有以下优势:

  • 存储效率高:只需存储列的前缀部分,大幅减少索引空间占用
  • 查询速度快:通过短匹配快速定位数据,减少 I/O 开销
  • 自适应性强:可根据不同数据类型自动选择最优前缀长度
  • 前缀索引的实现原理是通过 Min/Max 索引和 ZoneMap 机制,在数据加载过程中自动生成。Doris 支持对 CHAR、VARCHAR、DATE 等多种类型建立前缀索引,其中对于字符串类型,默认前缀长度为 36 字节。

    — 创建带前缀索引的表
    CREATE TABLE sales (
    id INT,
    product_id VARCHAR(32),
    store_id INT,
    sale_date DATE,
    amount DECIMAL(10,2),
    customer_id VARCHAR(32)
    )
    ENGINE=OLAP
    DISTRIBUTED BY HASH(id)
    PROPERTIES (
    "replication_num" = "3",
    "colocate_with" = "group1"
    );

    在前缀索引使用中,需要注意的是:

    • 对于高基数字符串列,适当增加前缀长度可以提高查询命中率
    • 前缀索引仅支持等于、IN、>、< 等比较操作,不支持 LIKE 操作
    • 前缀索引只在数据导入时构建,不支持动态更新

    2. 物化视图与 Rollup 设计原理

    物化视图(Materialized View)是 Doris 提供的一种预计算技术,通过预先计算并存储查询结果,加速后续相同查询的执行。在 Doris 中,物化视图通过 Rollup 机制实现,它基于原始表(Base Table)创建不同聚合级别的数据副本。

    Rollup 的设计原理包括:

  • 层级聚合:从原始明细数据到不同维度的聚合数据
  • 数据冗余:存储多份聚合结果,空间换时间
  • 增量更新:与基表数据同步更新,保证数据一致性
  • Rollup 的创建语法如下:

    — 创建基于原始表的 Rollup
    ALTER TABLE sales ADD ROLLUP sales_rollup (
    product_id,
    store_id,
    sum(amount)
    ) AGGREGATE KEY(product_id, store_id);
    — 创建多级 Rollup
    ALTER TABLE sales ADD ROLLUP sales_rollup_monthly (
    product_id,
    store_id,
    sale_date,
    sum(amount)
    ) AGGREGATE KEY(product_id, store_id, sale_date);

    Rollup 设计的关键考虑因素:

  • 查询模式:根据常用查询模式设计合适的聚合粒度
  • 数据量:权衡存储空间与查询性能的收益比
  • 更新频率:高更新频率场景需谨慎使用 Rollup
  • 数据倾斜:考虑数据分布对 Rollup 效果的影响
  • 3. 查询自动匹配机制解析

    Doris 的智能查询优化器能够自动匹配最适合的 Rollup 或基表来执行查询,这一机制是物化视图高效应用的核心。查询自动匹配遵循以下原则:

  • 精确匹配优先:当查询条件与 Rollup 的 Key 完全匹配时,优先使用 Rollup
  • 最短路径原则:选择包含所有查询列且聚合开销最小的 Rollup
  • 成本估算:基于统计信息计算不同路径的执行成本,选择最优方案
  • 查询自动匹配的流程可以用以下流程图表示:

    #publish-mermaid-0-1789708075833{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-0-1789708075833 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-0-1789708075833 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-0-1789708075833 .error-icon{fill:#552222;}#publish-mermaid-0-1789708075833 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-0-1789708075833 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-0-1789708075833 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-0-1789708075833 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-0-1789708075833 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-0-1789708075833 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-0-1789708075833 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-0-1789708075833 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-0-1789708075833 .marker.cross{stroke:#333333;}#publish-mermaid-0-1789708075833 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-0-1789708075833 p{margin:0;}#publish-mermaid-0-1789708075833 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-0-1789708075833 .cluster-label text{fill:#333;}#publish-mermaid-0-1789708075833 .cluster-label span{color:#333;}#publish-mermaid-0-1789708075833 .cluster-label span p{background-color:transparent;}#publish-mermaid-0-1789708075833 .label text,#publish-mermaid-0-1789708075833 span{fill:#333;color:#333;}#publish-mermaid-0-1789708075833 .node rect,#publish-mermaid-0-1789708075833 .node circle,#publish-mermaid-0-1789708075833 .node ellipse,#publish-mermaid-0-1789708075833 .node polygon,#publish-mermaid-0-1789708075833 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-0-1789708075833 .rough-node .label text,#publish-mermaid-0-1789708075833 .node .label text,#publish-mermaid-0-1789708075833 .image-shape .label,#publish-mermaid-0-1789708075833 .icon-shape .label{text-anchor:middle;}#publish-mermaid-0-1789708075833 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-0-1789708075833 .rough-node .label,#publish-mermaid-0-1789708075833 .node .label,#publish-mermaid-0-1789708075833 .image-shape .label,#publish-mermaid-0-1789708075833 .icon-shape .label{text-align:center;}#publish-mermaid-0-1789708075833 .node.clickable{cursor:pointer;}#publish-mermaid-0-1789708075833 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-0-1789708075833 .arrowheadPath{fill:#333333;}#publish-mermaid-0-1789708075833 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-0-1789708075833 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-0-1789708075833 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-0-1789708075833 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-0-1789708075833 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-0-1789708075833 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-0-1789708075833 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-0-1789708075833 .cluster text{fill:#333;}#publish-mermaid-0-1789708075833 .cluster span{color:#333;}#publish-mermaid-0-1789708075833 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-0-1789708075833 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-0-1789708075833 rect.text{fill:none;stroke-width:0;}#publish-mermaid-0-1789708075833 .icon-shape,#publish-mermaid-0-1789708075833 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-0-1789708075833 .icon-shape p,#publish-mermaid-0-1789708075833 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-0-1789708075833 .icon-shape .label rect,#publish-mermaid-0-1789708075833 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-0-1789708075833 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-0-1789708075833 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-0-1789708075833 .node .neo-node{stroke:#9370DB;}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node rect,#publish-mermaid-0-1789708075833 [data-look=\”neo\”].cluster rect,#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1789708075833 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-0-1789708075833 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}是否是否

    用户提交查询

    解析查询语句

    提取查询条件和输出列

    检查是否有精确匹配的Rollup

    使用匹配的Rollup执行查询

    检查是否有部分匹配的Rollup

    评估部分匹配Rollup的成本

    使用基表执行查询

    选择成本最低的执行路径

    当查询语句与多个 Rollup 部分匹配时,Doris 会通过以下因素评估成本:

  • 数据扫描量:需要扫描的数据行数
  • 聚合计算量:需要进行的聚合操作复杂度
  • I/O 开销:磁盘读取和数据传输成本
  • 内存使用:查询执行所需的内存大小
  • 例如,对于以下查询:

    SELECT product_id, store_id, sum(amount)
    FROM sales
    WHERE sale_date = '2023-01-01'
    GROUP BY product_id, store_id;

    Doris 会优先匹配 sales_rollup(product_id, store_id, sum(amount)) 这个 Rollup,因为它与查询的输出列和分组条件完全匹配,无需进行额外的聚合计算。

    4. 实践案例与性能对比

    让我们通过一个实际案例来展示前缀索引与物化视图对查询性能的提升效果。

    假设我们有一个电商平台的销售数据表,包含 1 亿条记录,需要对商品销售情况进行多维分析。

    表结构设计与索引创建

    — 创建电商销售表
    CREATE TABLE ecommerce_sales (
    order_id BIGINT,
    product_id VARCHAR(32),
    category_id INT,
    store_id INT,
    customer_id VARCHAR(32),
    sale_date DATE,
    quantity INT,
    unit_price DECIMAL(10,2),
    total_amount DECIMAL(12,2)
    )
    DISTRIBUTED BY HASH(order_id)
    PROPERTIES (
    "replication_num" = "3",
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "DAY",
    "dynamic_partition.start" = "-7",
    "dynamic_partition.end" = "3"
    );

    创建前缀索引

    — 为高基数字段创建前缀索引
    ALTER TABLE ecommerce_sales SET ("default_column_prefix_size" = "36");

    设计多级 Rollup

    — 一级聚合:按商品和门店
    ALTER TABLE ecommerce_sales ADD ROLLUP product_store_rollup (
    product_id,
    category_id,
    store_id,
    sum(quantity),
    sum(total_amount)
    ) AGGREGATE KEY(product_id, category_id, store_id);
    — 二级聚合:按商品类别
    ALTER TABLE ecommerce_sales ADD ROLLUP category_rollup (
    category_id,
    sum(quantity),
    sum(total_amount)
    ) AGGREGATE KEY(category_id);
    — 三级聚合:按时间
    ALTER TABLE ecommerce_sales ADD ROLLUP daily_rollup (
    sale_date,
    sum(quantity),
    sum(total_amount)
    ) AGGREGATE KEY(sale_date);

    性能对比测试

    以下是对不同查询在不同数据结构上的执行时间对比:

    查询类型查询语句基表耗时(ms)Rollup 耗时(ms)性能提升
    商品细查 SELECT product_id, sum(total_amount) FROM ecommerce_sales WHERE category_id = 101 GROUP BY product_id 1200 180 5.7x
    门店分析 SELECT store_id, sum(total_amount) FROM ecommerce_sales WHERE product_id = 'P10001' GROUP BY store_id 980 150 6.5x
    类别汇总 SELECT category_id, sum(quantity) FROM ecommerce_sales 2500 80 31.3x
    日销售趋势 SELECT sale_date, sum(total_amount) FROM ecommerce_sales GROUP BY sale_date ORDER BY sale_date 2100 60 35x

    从测试结果可以看出,合理设计的 Rollup 能显著提升查询性能,特别是对于聚合查询,性能提升可达 30 倍以上。

    空间开销分析

    Rollup 类型存储空间(GB)压缩比占用比例
    基表 25 4.5x 100%
    product_store_rollup 8 5.2x 32%
    category_rollup 2 6.1x 8%
    daily_rollup 3 5.8x 12%

    多级 Rollup 虽然会增加存储空间,但通过 Doris 高效的列式压缩技术,额外存储开销可控,而查询性能的提升则非常显著。

    5. 最佳实践与注意事项

    在实际应用中,要充分发挥 Doris 前缀索引和物化视图的性能优势,需要遵循以下最佳实践:

    前缀索引设计建议

  • 选择合适的前缀长度:
    • 对于低基数字符串列,短前缀(4-8字节)足够区分
    • 对于高基数字符串列,可适当增加前缀长度(16-36字节)
    • 定期分析查询命中率,调整前缀长度

    — 分析前缀索引使用情况
    SHOW INDEX FROM ecommerce_sales;

  • 谨慎对长文本列建索引:
    • 文本类型列的前缀索引效果有限
    • 考虑使用哈希索引替代前缀索引

    物化视图设计策略

  • 遵循"20/80法则":
    • 针对 20% 的常用查询,设计 80% 的性能收益
    • 优先为高频率、大查询设计 Rollup
  • 多级 Rollup 设计:
    • 从明细到聚合,构建多级 Rollup 树
    • 上层 Rollup 覆盖常用聚合场景
    • 下层 Rollup 支持更细粒度分析
  • 平衡存储与性能:
    • 定期评估 Rollup 的使用频率
    • 删除低效或不常用的 Rollup

    查询优化技巧

  • 显式指定使用 Rollup:
  • ```sql

    — 使用 hint 强制使用特定 Rollup

    SELECT /+ REWRITE(product_store_rollup) /

    product_id, sum(total_amount)

    FROM ecommerce_sales

    WHERE category_id = 101

    GROUP BY product_id;

    ```

  • 利用查询计划分析:
  • ```sql

    — 查看查询执行计划

    EXPLAIN SELECT product_id, sum(total_amount)

    FROM ecommerce_sales

    WHERE category_id = 101

    GROUP BY product_id;

    ```

  • 合理设置查询超时:
  • ```sql

    — 设置查询超时时间

    SET query_timeout = 30;

    ```

    部署与维护注意事项

  • 监控 Rollup 使用情况:
  • ```sql

    — 查看 Rollup 使用统计

    SELECT * FROM rolls WHERE TableName = 'ecommerce_sales';

    ```

  • 定期维护 Rollup:
    • 监控存储空间增长
    • 根据查询模式变化调整 Rollup
    • 及时清理无用 Rollup
  • 数据更新策略:
    • 高更新频率场景慎用 Rollup
    • 考虑使用增量更新策略
    • 设置合理的并发导入限制

    最小示例与注意事项

    以下是一个可直接运行的最小示例,展示如何在 Doris 中创建表、设计前缀索引和物化视图:

    — 创建示例表
    CREATE TABLE store_sales (
    order_id BIGINT,
    product_id VARCHAR(32),
    store_id INT,
    sale_date DATE,
    quantity INT,
    amount DECIMAL(10,2)
    )
    DISTRIBUTED BY HASH(order_id)
    PROPERTIES (
    "replication_num" = "1"
    );
    — 设置前缀索引大小
    ALTER TABLE store_sales SET ("default_column_prefix_size" = "20");
    — 创建多级 Rollup
    — 第一级:按商品和门店聚合
    ALTER TABLE store_sales ADD ROLLUP product_store_rollup (
    product_id,
    store_id,
    sum(quantity),
    sum(amount)
    ) AGGREGATE KEY(product_id, store_id);
    — 第二级:按门店聚合
    ALTER TABLE store_sales ADD ROLLUP store_rollup (
    store_id,
    sum(quantity),
    sum(amount)
    ) AGGREGATE KEY(store_id);
    — 示例查询:查看各门店销售总额
    SELECT store_id, sum(amount)
    FROM store_sales
    GROUP BY store_id;
    — 示例查询:查看各商品在各门店的销售情况
    SELECT product_id, store_id, sum(amount)
    FROM store_sales
    GROUP BY product_id, store_id;

    注意事项:

  • 前缀索引大小应根据实际数据特点调整,并非越大越好
  • Rollup 创建后会占用额外存储空间,需在存储和性能间取得平衡
  • 频繁更新的数据表慎用 Rollup,可能导致维护开销过大
  • 定期监控 Rollup 使用情况,及时调整优化策略
  • 赞(0)
    未经允许不得转载:171主机测评 » Apache Doris 前缀索引与物化视图:加速查询的 Rollup 设计与自动匹配机制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址