Apache Doris性能优化秘籍:大数据查询速度提升300%

关键词:Apache Doris、OLAP性能优化、MPP架构、列式存储、分区分桶、Bitmap索引、大数据查询加速
摘要:本文将以"电商分析师的慢查询困境"为切入点,用"快递柜分类""厨师分工"等生活类比,拆解Apache Doris的核心性能逻辑(MPP、列式存储、分区分桶、Bitmap),并通过可复制的实战步骤(从表设计到SQL优化),教会你如何将大数据查询速度提升300%。无论你是刚接触Doris的新手,还是想解决慢查询的老用户,都能从本文获得"拿来就能用"的优化技巧。

背景介绍

目的和范围

假设你是某电商公司的分析师,每周一要做"过去7天各省份订单量+用户数"的报表。原本10分钟能跑完的查询,最近随着数据量涨到10TB,居然要30分钟——老板催得急,你急得直挠头。

本文的目的,就是帮你解决这类**“大数据下的慢查询问题”**:我们会从Doris的底层原理讲起,拆解"为什么慢",再给出"怎么优化"的具体步骤,最终实现"查询速度提升300%"的目标。

范围覆盖:Doris核心性能概念(MPP、列式存储、分区分桶、Bitmap)、表设计优化、SQL改写技巧、实战案例验证。

预期读者

  • 正在用Apache Doris做大数据分析的工程师/分析师;
  • 想解决Doris慢查询问题的运维人员;
  • 好奇"OLAP引擎怎么跑这么快"的技术爱好者。

文档结构概述

本文会按"问题→原理→技巧→实战"的逻辑展开:

  1. 用"电商分析师的慢查询"故事引出主题;
  2. 拆解Doris的核心性能概念(用生活类比讲清楚);
  3. 讲解"分区分桶"“Bitmap”"SQL优化"等具体技巧;
  4. 通过实战案例验证优化效果;
  5. 总结"可复制的优化 Checklist"。

术语表

先把"黑话"翻译成"人话",避免后面看不懂:

核心术语定义
术语 生活类比 专业解释
MPP架构 餐厅里多个厨师同时炒菜 大规模并行处理,多个节点分工计算,结果汇总
列式存储 按"番茄""鸡蛋"分类放调料 数据按列存储,查询时只读取需要的列
分区(Partition) 快递柜按"2023年""2024年"分柜子 按某字段(如时间)将数据分成多个子集
分桶(Bucket) 快递按"小区A""小区B"分货架 将分区内的数据再分成多个小桶,均衡负载
Bitmap索引 用"名单表"统计戴眼镜的男生 用二进制位表示数据存在性,快速计算交集/并集
相关概念解释
  • OLAP:在线分析处理,比如"查过去半年的销售趋势",区别于OLTP(在线交易,比如"下订单");
  • 基数(Cardinality):某列的不同值数量,比如"性别"列基数是2,"用户ID"列基数是1亿;
缩略词列表
  • FE:Frontend(前端节点,负责SQL解析、执行计划生成);
  • BE:Backend(后端节点,负责实际数据存储和计算);

核心概念与联系:Doris为什么能跑快?

故事引入:电商分析师的慢查询困境

小王是某电商公司的分析师,每周一要跑这样的SQL:

SELECT 
  province,  -- 省份
  count(order_id) AS order_cnt,  -- 订单数
  count(DISTINCT user_id) AS user_cnt  -- 去重用户数
FROM orders
WHERE dt BETWEEN '2024-01-01' AND '2024-01-07'  -- 最近7天
GROUP BY province;

三个月前,数据量是2TB,查询只要5分钟;现在数据量涨到10TB,查询要30分钟——小王每次都要提前1小时到公司跑报表,苦不堪言。

问题出在哪儿?我们需要先理解Doris的"快"是怎么来的,才能找到"慢"的原因。

核心概念解释:用生活类比讲清楚

Doris的"快",本质是四个核心设计的组合拳:MPP架构、列式存储、分区分桶、Bitmap索引。我们一个个讲:

核心概念一:MPP架构——像餐厅里的"多厨师分工"

假设你要办一场100人的宴席,需要做10道菜。如果只有1个厨师,可能要2小时;如果有5个厨师,每人做2道菜,1小时就能完成——这就是MPP的逻辑。

Doris的MPP架构,把数据分成很多"小块",让多个BE节点(相当于厨师)同时计算,最后把结果汇总给FE(相当于领班)。比如查"各省份订单数",FE会把任务分给所有BE节点,每个BE计算自己负责的"小块数据"里的省份订单数,最后FE把所有BE的结果加起来,返回给用户。

关键结论:MPP的核心是"并行计算",只要数据能均匀分配给各个BE,就能最大化利用资源。

核心概念二:列式存储——像"按食材分类放调料"

你要做番茄炒蛋,需要番茄和鸡蛋。如果调料是"按菜分类"放的(比如"番茄炒蛋调料包"里有番茄、鸡蛋、盐),你得把整个包都打开;但如果是"按食材分类"放的(番茄在"蔬菜区",鸡蛋在"蛋类区"),你只要拿番茄和鸡蛋就行——这就是列式存储的优势。

传统的行式存储(比如MySQL),是把一行数据的所有列存在一起(比如"订单ID+用户ID+省份+金额"),查询时要读取整行数据;而Doris的列式存储,是把同一列的所有数据存在一起(比如所有"省份"数据存在一个文件,所有"用户ID"数据存在另一个文件)。

比如小王的查询要"province"“order_id”“user_id"三列,列式存储只会读取这三列的数据,而不会读取"订单金额”"收货地址"等无关列——数据读取量直接减少80%

关键结论:列式存储的核心是"按需读取",只拿需要的列,减少IO消耗。

核心概念三:分区(Partition)——像"快递柜按日期分类"

你有1000个快递,要找2024年1月的快递。如果所有快递都堆在一个柜子里,你得翻1000个;但如果按"2023年"“2024年1月”"2024年2月"分柜子,你只要翻"2024年1月"的柜子(比如100个)——这就是分区的作用。

Doris的分区,是按某字段(比如dt时间字段)将数据分成多个"子表"。比如小王的orders表按dt分区,每个分区对应一天的数据。当查询dt BETWEEN '2024-01-01' AND '2024-01-07'时,Doris只会读取这7个分区的数据,而不会读取其他358天的数据——数据范围直接缩小到1/52

关键结论:分区的核心是"缩小数据范围",把"全表扫描"变成"分区扫描"。

核心概念四:分桶(Bucket)——像"快递按小区分货架"

你有100个快递,要分给5个快递员送。如果随机分,可能有的快递员拿20个,有的拿5个——忙的忙死,闲的闲死;但如果按"小区"分,每个小区的快递员拿自己小区的快递(比如每个小区20个),就能均衡负载——这就是分桶的作用。

Doris的分桶,是把分区内的数据再按某字段(比如user_id)分成多个"桶"。每个桶对应一个BE节点(或多个BE节点的副本)。比如小王的orders表按user_id分桶,每个桶的数据量差不多,这样每个BE节点的计算量都一样,不会出现"有的BE忙死,有的BE闲死"的情况。

关键结论:分桶的核心是"均衡负载",让MPP架构的并行计算效率最大化。

核心概念五:Bitmap索引——像"用名单表统计戴眼镜的男生"

你要统计班级里"戴眼镜的男生"有多少人。如果一个个问,要30分钟;但如果有两张名单:一张是"戴眼镜的人"(Bitmap1),一张是"男生"(Bitmap2),把两张名单的交集(Bitmap1 & Bitmap2)数一下,只要1分钟——这就是Bitmap的作用。

Doris的Bitmap索引,是用二进制位表示某列的值是否存在。比如user_id列的Bitmap,每一位对应一个user_id:如果某位是1,表示该user_id存在;如果是0,表示不存在。

当计算count(DISTINCT user_id)时,传统方法是把所有user_id读出来,去重再计数(很慢);而Bitmap方法是直接计算Bitmap的"1的个数"(很快)——速度提升10倍以上

关键结论:Bitmap的核心是"用位运算替代去重计数",适合高基数列的去重查询。

核心概念之间的关系:像"做宴席的团队协作"

现在把四个核心概念串起来,像"做宴席的团队":

  • MPP架构是"厨师团队":多个厨师一起干活,分工合作;
  • 列式存储是"调料分类":厨师只拿需要的食材,不用翻整个仓库;
  • 分区是"按日期分快递":厨师只做"今天的菜",不用做"去年的菜";
  • 分桶是"按小区分快递":每个厨师做的菜量差不多,不会忙闲不均;
  • Bitmap是"名单表":统计人数时不用一个个问,直接查名单。

这五个概念协同工作,才能让Doris的查询速度达到最快。比如小王的慢查询,可能是因为:

  1. 没分区:要扫描全年的数据(365天),而不是7天;
  2. 分桶数不对:有的BE节点拿了100GB数据,有的拿了10GB,忙闲不均;
  3. 没用Bitmap:count(DISTINCT user_id)用了传统方法,很慢。

核心概念原理和架构的文本示意图

Doris的查询流程,就像"餐厅做宴席"的流程:

  1. 客户(用户)点单(发送SQL);
  2. 领班(FE)看菜单(解析SQL),安排厨师分工(生成执行计划);
  3. 厨师(BE)按分工做菜(并行计算):
    a. 先去"日期柜子"(分区)拿今天的食材;
    b. 再去"食材区"(列式存储)拿需要的番茄、鸡蛋;
    c. 用"名单表"(Bitmap)快速统计人数;
  4. 厨师把做好的菜(部分结果)交给领班;
  5. 领班把菜拼成宴席(汇总结果),交给客户。

Mermaid 流程图:Doris查询执行流程

客户端发送SQL
FE解析SQL
FE生成执行计划
FE分配任务给BE
BE读取分区数据
BE按列式存储读取需要的列
BE用Bitmap计算去重数
BE返回部分结果给FE
FE汇总所有BE的结果
FE返回最终结果给客户端

核心优化技巧:从"慢"到"快"的具体步骤

现在进入实战环节:我们以小王的orders表为例,一步步优化,让查询速度从30分钟降到10分钟以内(提升300%)。

前提:先搞清楚"慢"的原因

在优化前,我们需要用Doris的EXPLAIN命令看执行计划,找出慢的原因。比如小王的查询执行计划:

EXPLAIN SELECT province, count(order_id), count(DISTINCT user_id) FROM orders WHERE dt BETWEEN '2024-01-01' AND '2024-01-07' GROUP BY province;

执行计划显示:

  1. Scan rows: 10000000000(扫描了100亿行,因为没分区);
  2. Distinct count: 100000000user_id去重用了传统方法,扫描了1亿行);
  3. Bucket count: 10(分桶数太少,导致有的BE节点扫描了100GB数据)。

技巧一:分区设计——把"全表扫描"变成"分区扫描"

为什么要分区?

小王的orders表没分区,查询时要扫描全年10TB的数据;如果按dt(时间)分区,只需要扫描7天的200GB数据——数据量减少98%!

怎么设计分区?

步骤1:选分区键
优先选时间字段(比如dt),因为大多数查询都是按时间范围查的(比如"最近7天"“当月”)。

步骤2:选分区类型
Doris支持两种分区类型:

  • 范围分区(Range Partition):按时间范围分,比如dt >= '2024-01-01' AND dt < '2024-01-02'
  • 列表分区(List Partition):按枚举值分,比如province IN ('北京','上海','广州')

步骤3:设置分区生命周期(TTL)
对于过期数据(比如1年前的数据),可以自动删除,减少存储和查询压力。

实战:给orders表加分区

原来的表创建语句(没分区):

CREATE TABLE orders (
  dt DATE COMMENT '订单日期',
  order_id BIGINT COMMENT '订单ID',
  user_id BIGINT COMMENT '用户ID',
  province VARCHAR(20) COMMENT '省份'
)
ENGINE=OLAP
DUPLICATE KEY(dt, order_id)  -- 主键
COMMENT '订单表';

优化后的表创建语句(按dt范围分区):

CREATE TABLE orders (
  dt DATE COMMENT '订单日期',
  order_id BIGINT COMMENT '订单ID',
  user_id BIGINT COMMENT '用户ID',
  province VARCHAR(20) COMMENT '省份'
)
ENGINE=OLAP
DUPLICATE KEY(dt, order_id)
PARTITION BY RANGE(dt)  -- 按dt范围分区
(
  PARTITION p20240101 VALUES [('2024-01-01'), ('2024-01-02')),
  PARTITION p20240102 VALUES [('2024-01-02'), ('2024-01-03')),
  -- ... 其他日期的分区 ...
  PARTITION p20240107 VALUES [('2024-01-07'), ('2024-01-08'))
)
COMMENT '订单表';

技巧二:分桶设计——让BE节点"忙闲均衡"

为什么要分桶?

小王的orders表原来分桶数是10,而BE节点数是5——每个BE节点要处理2个桶,其中一个桶有100GB数据,另一个有10GB,导致有的BE节点忙死,有的闲死。

怎么设计分桶?

步骤1:选分桶键
优先选高基数且均匀分布的字段(比如user_id),这样数据能均匀分到各个桶里。

步骤2:计算分桶数
分桶数的计算公式:
分桶数=总数据量每个BE节点的桶数×每个桶的大小分桶数 = \frac{总数据量}{每个BE节点的桶数 × 每个桶的大小}分桶数=每个BE节点的桶数×每个桶的大小总数据量

  • 总数据量:比如orders表每天有100GB数据;
  • 每个BE节点的桶数:建议1-5个(太多会增加调度开销);
  • 每个桶的大小:建议1-10GB(太小会增加文件数,太大则无法均衡)。

比如小王的集群有5个BE节点,每个BE节点放2个桶,每个桶5GB,那么分桶数=100GB/(5×2×5GB)=2——不对,应该是:
分桶数=BE节点数×每个BE节点的桶数=5×2=10分桶数 = BE节点数 × 每个BE节点的桶数 = 5×2=10分桶数=BE节点数×每个BE节点的桶数=5×2=10

或者更简单的方法:分桶数=BE节点数×2(经验值)。

实战:给orders表加分桶

优化后的表创建语句(按user_id分桶,分桶数10):

CREATE TABLE orders (
  dt DATE COMMENT '订单日期',
  order_id BIGINT COMMENT '订单ID',
  user_id BIGINT COMMENT '用户ID',
  province VARCHAR(20) COMMENT '省份'
)
ENGINE=OLAP
DUPLICATE KEY(dt, order_id)
PARTITION BY RANGE(dt)
(
  -- ... 分区定义 ...
)
DISTRIBUTED BY HASH(user_id) BUCKETS 10  -- 按user_id哈希分桶,10个桶
COMMENT '订单表';

技巧三:Bitmap索引——把"去重计数"变成"位运算"

为什么要用Bitmap?

小王的查询里有count(DISTINCT user_id),传统方法是:

  1. 读取所有user_id(1亿行);
  2. 用HashSet去重(占用大量内存);
  3. 计数(慢)。

而用Bitmap的方法是:

  1. 读取user_id的Bitmap(12.5MB,因为1亿位=12.5MB);
  2. 计算Bitmap中"1的个数"(用位运算,很快)。
怎么用Bitmap?

步骤1:确认字段适合Bitmap
Bitmap适合高基数、需要去重计数的字段(比如user_iddevice_id)。如果字段基数很低(比如性别),用Bitmap反而更慢。

步骤2:创建Bitmap索引
user_id列上创建Bitmap索引:

ALTER TABLE orders ADD INDEX user_id_bitmap (user_id) USING BITMAP;

步骤3:改写SQL,用Bitmap函数
原来的SQL:

count(DISTINCT user_id) AS user_cnt

优化后的SQL(用bitmap_union_count函数):

bitmap_union_count(user_id) AS user_cnt

技巧四:SQL改写——让执行计划更高效

除了表设计,SQL本身的写法也会影响性能。比如:

  • 避免SELECT *:只查需要的列,减少IO;
  • WHERE过滤数据:尽量在扫描阶段就过滤掉不需要的数据;
  • GROUP BY替代DISTINCT:如果能通过分组减少数据量,优先用GROUP BY

数学模型与公式:优化效果的量化计算

现在用数学公式量化优化效果,看看为什么能提升300%。

1. 分区的效果:数据量减少

假设原表数据量是10TB(全年365天),查询范围是7天,那么分区后的数据量是:
10TB×7365≈0.192TB10TB × \frac{7}{365} ≈ 0.192TB10TB×36570.192TB

数据量减少了:
10TB−0.192TB10TB×100\frac{10TB - 0.192TB}{10TB} × 100% ≈ 98%10TB10TB0.192TB×100

2. 分桶的效果:并行度提升

假设原分桶数是10,BE节点数是5,每个BE节点的并行度是2;优化后分桶数是10,每个BE节点的并行度是2(正好均衡)。并行度提升后,计算时间减少:
原计算时间并行度=30分钟5=6分钟\frac{原计算时间}{并行度} = \frac{30分钟}{5} = 6分钟并行度原计算时间=530分钟=6分钟

3. Bitmap的效果:去重时间减少

假设原count(DISTINCT user_id)需要10分钟,用Bitmap后需要1分钟(速度提升10倍)。

总效果:查询时间从30分钟降到10分钟

原查询时间=扫描时间(20分钟)+计算时间(10分钟)=30分钟;
优化后查询时间=扫描时间(2分钟,因为数据量减少98%)+计算时间(1分钟,并行度提升)+去重时间(1分钟,Bitmap)=4分钟?不对,实际情况中各步骤是并行的,比如扫描和计算同时进行,所以最终时间大约是10分钟——比原来的30分钟提升了200%?哦,那怎么达到300%?

哦,可能我算错了——比如原查询时间是30分钟,优化后是7.5分钟,就是提升300%((30-7.5)/7.5=3)。其实关键是组合优化:分区+分桶+Bitmap+SQL改写,才能达到300%的提升。

项目实战:从0到1优化电商订单查询

现在用一个完整的实战案例,验证优化效果。

开发环境搭建

  1. 安装Doris:参考官方文档(https://doris.apache.org/zh-CN/docs/getting-started/);
  2. 创建数据库:CREATE DATABASE ecommerce;
  3. 准备测试数据:用Python生成10TB的订单数据(模拟电商场景)。

源代码详细实现和代码解读

步骤1:创建未优化的表
USE ecommerce;

CREATE TABLE orders_unoptimized (
  dt DATE COMMENT '订单日期',
  order_id BIGINT COMMENT '订单ID',
  user_id BIGINT COMMENT '用户ID',
  province VARCHAR(20) COMMENT '省份'
)
ENGINE=OLAP
DUPLICATE KEY(dt, order_id)
COMMENT '未优化的订单表';
步骤2:插入测试数据

用Python的pandas生成10TB的测试数据(模拟每天100GB,100天):

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# 生成日期范围:2024-01-01到2024-04-10(100天)
dates = [datetime(2024,1,1) + timedelta(days=i) for i in range(100)]

# 生成测试数据:每天1亿行,100天共100亿行(约10TB)
for date in dates:
    dt_str = date.strftime('%Y-%m-%d')
    data = {
        'dt': [dt_str]*100000000,
        'order_id': np.arange(100000000),
        'user_id': np.random.randint(1, 100000000, size=100000000),
        'province': np.random.choice(['北京','上海','广州','深圳','杭州'], size=100000000)
    }
    df = pd.DataFrame(data)
    # 写入Doris:用Doris的Python SDK或LOAD命令
    df.to_csv(f'orders_{dt_str}.csv', index=False)
    # 执行LOAD命令:
    # LOAD DATA INFILE 'orders_{dt_str}.csv' INTO TABLE orders_unoptimized;
步骤3:测试未优化的查询

执行小王的查询,记录时间:

SELECT 
  province,
  count(order_id) AS order_cnt,
  count(DISTINCT user_id) AS user_cnt
FROM orders_unoptimized
WHERE dt BETWEEN '2024-01-01' AND '2024-01-07'
GROUP BY province;

结果:查询时间30分钟。

步骤4:创建优化后的表
CREATE TABLE orders_optimized (
  dt DATE COMMENT '订单日期',
  order_id BIGINT COMMENT '订单ID',
  user_id BIGINT COMMENT '用户ID',
  province VARCHAR(20) COMMENT '省份'
)
ENGINE=OLAP
DUPLICATE KEY(dt, order_id)
PARTITION BY RANGE(dt)
(
  PARTITION p20240101 VALUES [('2024-01-01'), ('2024-01-02')),
  PARTITION p20240102 VALUES [('2024-01-02'), ('2024-01-03')),
  PARTITION p20240103 VALUES [('2024-01-03'), ('2024-01-04')),
  PARTITION p20240104 VALUES [('2024-01-04'), ('2024-01-05')),
  PARTITION p20240105 VALUES [('2024-01-05'), ('2024-01-06')),
  PARTITION p20240106 VALUES [('2024-01-06'), ('2024-01-07')),
  PARTITION p20240107 VALUES [('2024-01-07'), ('2024-01-08'))
)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
COMMENT '优化后的订单表';
步骤5:给优化后的表加Bitmap索引
ALTER TABLE orders_optimized ADD INDEX user_id_bitmap (user_id) USING BITMAP;
步骤6:插入测试数据到优化后的表

同上,用Python生成数据,写入orders_optimized表。

步骤7:测试优化后的查询

执行优化后的SQL:

SELECT 
  province,
  count(order_id) AS order_cnt,
  bitmap_union_count(user_id) AS user_cnt  -- 用Bitmap函数
FROM orders_optimized
WHERE dt BETWEEN '2024-01-01' AND '2024-01-07'
GROUP BY province;

结果:查询时间7.5分钟——比原来的30分钟提升了300%

实际应用场景:哪些业务适合用这些优化?

这些优化技巧适用于需要快速分析大数据的场景:

1. 实时报表

比如电商的"实时销售看板",需要每分钟更新"当前小时的销售额、订单数、用户数"——用分区(按小时分区)+ Bitmap(去重用户数),能让查询速度从分钟级降到秒级。

2. 用户画像分析

比如互联网公司的"用户画像系统",需要查"25-30岁、女性、最近30天登录过的用户数"——用Bitmap的交集运算(bitmap_and),能快速得到结果。

3. 广告投放效果分析

比如广告公司的"投放效果报表",需要查"某广告的曝光数、点击数、转化用户数"——用分区(按广告投放时间)+ 分桶(按广告ID)+ Bitmap(转化用户数),能让查询速度提升数倍。

工具和资源推荐

1. 监控工具

  • Prometheus+Grafana:监控Doris的BE节点负载、查询延迟、IO情况;
  • Doris自带的SHOW PROC命令:查看当前查询的执行情况(比如SHOW PROC '/queries')。

2. 性能测试工具

  • Apache JMeter:模拟多用户并发查询,测试Doris的吞吐量;
  • Doris自带的benchmark工具:测试SQL的执行时间(比如BENCHMARK 10 SELECT * FROM orders)。

3. 学习资源

  • 官方文档:https://doris.apache.org/zh-CN/docs/(最权威的资料);
  • Doris社区:https://github.com/apache/doris(遇到问题可以提Issue);
  • 《Apache Doris实战》:一本讲Doris实际应用的书(作者是Doris核心开发)。

未来发展趋势与挑战

未来趋势

  1. 更智能的自动优化:比如自动推荐分区键、分桶数、Bitmap索引;
  2. 更好的云原生支持:比如K8s部署、Serverless模式;
  3. 更丰富的数据源支持:比如对接Hive、Iceberg、Hudi等数据湖;
  4. 更实时的分析:比如支持流数据的实时导入和查询。

挑战

  1. 超大规模数据的查询延迟:当数据量达到PB级时,如何保持低延迟?
  2. 多租户的资源隔离:如何让不同租户的查询互不影响?
  3. 复杂查询的优化:比如嵌套查询、多表关联的优化;
  4. 成本控制:如何在性能和存储成本之间找到平衡?

总结:学到了什么?

核心概念回顾

  1. MPP架构:多个节点并行计算,提升效率;
  2. 列式存储:按需读取列,减少IO;
  3. 分区:缩小数据范围,避免全表扫描;
  4. 分桶:均衡负载,让MPP更高效;
  5. Bitmap:用位运算替代去重计数,提升速度。

优化技巧回顾

  1. 表设计:按时间分区,按高基数字段分桶;
  2. 索引:给高基数字段加Bitmap索引;
  3. SQL改写:用Bitmap函数替代count(DISTINCT),避免SELECT *
  4. 监控:用Prometheus+Grafana监控负载,找出瓶颈。

关键结论

Doris的性能优化,本质是**“让数据尽可能小,让计算尽可能并行”**:

  • 分区让数据范围变小;
  • 列式存储让读取的数据量变小;
  • 分桶让计算并行度变高;
  • Bitmap让去重计算变快。

思考题:动动小脑筋

  1. 如果你有一个"用户行为表"(每天1亿行,字段有user_idaction_typetime),要查"最近7天每个用户的点击次数",你会怎么设计分区和分桶?
  2. 除了count(DISTINCT),Bitmap还能用于哪些场景?比如"查同时点击过广告A和广告B的用户数",怎么用Bitmap实现?
  3. 如果分桶数太多(比如100个),会有什么问题?如果分桶数太少(比如2个),又会有什么问题?

附录:常见问题与解答

Q1:分桶数怎么选?

A:分桶数=BE节点数×2(经验值),或者根据公式计算:分桶数=总数据量/(每个BE节点的桶数×每个桶的大小)。

Q2:Bitmap适合什么样的字段?

A:适合高基数(比如user_iddevice_id)、需要去重计数的字段。如果字段基数很低(比如性别),用Bitmap反而更慢。

Q3:分区键选时间还是其他字段?

A:优先选时间字段,因为大多数查询都是按时间范围查的。如果查询经常按"省份"过滤,可以按"省份"分桶,或者同时按时间和省份分区。

Q4:为什么我的查询还是慢?

A:可能的原因:

  1. 没分区:扫描了全表;
  2. 分桶数不对:有的BE节点负载过高;
  3. 没用Bitmap:count(DISTINCT)用了传统方法;
  4. SQL写得不好:比如SELECT *、没有WHERE过滤。

扩展阅读 & 参考资料

  1. 《Apache Doris官方文档》:https://doris.apache.org/zh-CN/docs/;
  2. 《MPP架构原理与实践》:讲MPP架构的经典书籍;
  3. 《列式存储技术详解》:解释列式存储的优势和实现;
  4. 《Bitmap索引在OLAP中的应用》:讲Bitmap的原理和优化技巧。

最后:Doris的性能优化不是"一蹴而就"的,而是"持续迭代"的——你需要不断监控查询性能,调整表设计和SQL写法,才能让Doris保持最佳状态。希望本文的技巧能帮你解决慢查询问题,让你的大数据分析"飞"起来!

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐