去年双十一前一周,业务方甩来一句话:"这个大盘一刷新要半小时,能不能快点?"
打开执行计划一看:
3.2 亿条订单明细 两张表 Join 全表扫描 + Shuffle
跑了 28 分钟。
一周后,同样的 SQL,2.7 秒返回。
没换机器,没升级引擎,我只做了 四件事。
今天,我把它们讲清楚。
01 先看一张图:数仓查询的"四层漏斗"
性能问题很少是单点问题。
我习惯把一次查询的生命周期画成一个漏斗,从上到下依次是:
客户端 → 计算引擎 → 存储引擎 → 物理介质
任何一层堵住,上层再优化也白搭。

实战里,收益从下往上递减:
存储层动一动,往往就是十倍级提升;计算层再压榨,只是锦上添花。
02 存储层:把数据"摆好"再说
90% 的慢查询,慢在 扫了不该扫的数据。
存储层优化只做两件事——少读、顺读。
① 分区裁剪:不只是按天
按 dt
分区是入门。按业务高频过滤维度分区,才是进阶。
如果订单表同时被"按天"和"按门店"查询,可以做 二级分区dt/store_id
。
⚠️ 注意:分区总数务必控制在 10万以内,否则元数据本身就会成为瓶颈。
② 分桶(Bucket):干掉 Shuffle
两张超大表 Join 时,把 Join Key 设为分桶键,能直接省掉昂贵的 Shuffle 过程。
经验值:
桶数取 2 的幂。 单桶数据量控制在 200MB ~ 1GB。
③ 文件格式 + 排序:多维过滤的杀手锏
存储格式:Parquet / ORC 列存 + ZSTD 压缩,几乎是当下最优组合。 高级排序:叠加 Z-Order 或 Hilbert Curve 排序,可以让多维过滤同时命中数据聚集。
量化收益对比
方案 扫描数据量 耗时 行存 + 无分区 320 GB 28 min Parquet + dt 分区 12 GB 96 s Parquet + dt/store 二级分区 480 MB 11 s + Z-Order(user_id) 210 MB 3.2 s
03 模型层:用"空间换时间"的三种姿势
SQL 写得再骚,也快不过一张 已经算好的表。
模型层的思路只有一个——把重复计算前置。
1. 宽表
把常用维度打平到事实表,避免 Join。 适合场景:固定报表。
2. 预聚合表(Cube)
按日/周/月/门店/品类等多维度预汇总。 适合场景:OLAP 场景下,百倍提速的关键。
3. 物化视图(性价比之王)
现代引擎(Doris、StarRocks、Databricks)都支持 自动改写。 写 SQL 不用改,引擎自动判断能否命中。 这是性价比最高的一档,强烈推荐。

04 计算层:让引擎"聪明"起来
这一层的核心是 让优化器知道数据长什么样。
三条建议:
定期 ANALYZE收集统计信息,CBO(基于代价的优化器)才不会"瞎猜"。
合理设置并行度不是越大越好。Shuffle 分区数一般设为 集群核数的 2~4 倍。
用 EXPLAIN 看真实执行计划重点关注这三个关键字:
Broadcast Join 谓词下推 分区裁剪
05 附赠一张:性能诊断流程图
遇到慢 SQL,对着这张图走一遍,思路就清晰了。

06 总结:四步法口诀
把上面的经验浓缩成一句话:
"先看数据分布,再看执行计划,然后调存储,最后调计算。"
这四步做完,你会发现,大多数百倍级的提速,都不是玄学。
最后,留一道思考题
如果有一张 50 亿行的用户行为表,日常被 20 个业务方按不同维度查询:
你会优先做宽表、Cube,还是物化视图?


往期推荐
文章转载自陈乔数据观止,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




