《MPP/OLAP 数据库实战优化案例:从 1 小时到 2 分钟,SQL 调优 + 存储优化 + 数据倾斜解决》
(背景说明)
本文记录了笔者在Greenplum数据仓库日常运维中遇到的 6 个真实性能问题,涵盖 SQL 调优、执行计划修复、数据倾斜处理、存储优化等场景。这些优化思路同样适用于Doris、StarRocks、ClickHouse等 MPP 架构的 OLAP 数据库,因为它们的核心原理相通:分布式计算 + 列式存储 + 分区剪枝 + 本地 Join。
一、SQL 极致优化:拆表 + UNION(30 倍提升)
问题现象
某租户核心脚本运行超时(1 小时+),影响下游报表产出。
根因分析
排查 SQL 发现,同一张表自关联时,关联条件中的字段相同,导致优化器生成了笛卡尔积执行计划,数据量爆炸式增长。
优化动作
将大表按字段切分为三部分,分别构造临时表,各自关联后通过UNION ALL合并结果。
sql
-- 优化前(简化示例,实际 SQL 更复杂) SELECT * FROM large_table a JOIN large_table b ON a.id = b.id -- 相同字段自关联,产生笛卡尔积 WHERE ... -- 优化后(拆表 + UNION) CREATE TEMP TABLE tmp1 AS SELECT ... FROM large_table WHERE condition1; CREATE TEMP TABLE tmp2 AS SELECT ... FROM large_table WHERE condition2; CREATE TEMP TABLE tmp3 AS SELECT ... FROM large_table WHERE condition3; SELECT * FROM tmp1 JOIN ... UNION ALL SELECT * FROM tmp2 JOIN ... UNION ALL SELECT * FROM tmp3 JOIN ...;
优化结果
耗时:1 小时 + →2 分钟
性能提升:30 倍
Doris / StarRocks 适用性
✅完全适用。Doris 同样支持UNION ALL和临时表(CTE 或CREATE TEMP VIEW),拆分大表后可以显著减少单次 Join 的数据量。如果 Join 涉及 Bucket 分布,还可以利用Colocation Join进一步优化。
二、循环逻辑重构:三次 Join → 一次 Cross Join
问题现象
某脚本使用循环对同一张表做了三次JOIN,执行效率低下。
根因分析
循环中逐次执行JOIN,导致同一张表被扫描三次,网络和 IO 开销成倍增加。
优化动作
将三次JOIN合并为一次CROSS JOIN+ 条件聚合,三次扫描变一次。
sql
-- 优化前(伪代码) FOR each condition: INSERT INTO result SELECT ... FROM table JOIN table ON ... -- 优化后(一次 CROSS JOIN + CASE WHEN) SELECT CASE WHEN condition1 THEN ... END AS col1, CASE WHEN condition2 THEN ... END AS col2, CASE WHEN condition3 THEN ... END AS col3 FROM table CROSS JOIN table GROUP BY ...;
优化结果
耗时:减半
性能提升:100%
Doris / StarRocks 适用性
✅完全适用。Doris 中同样应避免在循环中反复扫描同一张表,优先使用CASE WHEN + 聚合或UNION ALL替代多次扫描。Doris 的向量化执行引擎对CASE WHEN和聚合操作的优化非常好。
三、执行计划修复:统计信息过旧导致优化器选错路径
问题现象
某任务运行突然变慢,SQL 和索引均无问题,执行计划看起来“不合理”。
根因分析
查看执行计划发现,优化器选择了错误的 Join 顺序或错误的表扫描方式。进一步排查发现表统计信息(pg_statistic)已过时,导致优化器无法准确估算数据量。
优化动作
执行ANALYZE更新统计信息,让优化器重新选择执行路径。
sql
-- Greenplum / PostgreSQL ANALYZE table_name; -- Doris ANALYZE TABLE table_name; -- ClickHouse OPTIMIZE TABLE table_name;
优化结果
执行计划恢复正常,查询性能回归正常水平。
Doris / StarRocks 适用性
✅高度适用。Doris 同样依赖统计信息(ANALYZE TABLE)进行 CBO(基于代价的优化),统计信息过旧同样会导致执行计划退化。建议定期执行ANALYZE TABLE或配置自动收集策略。
四、避免重分布(Shuffle)开销:分区键对齐实现本地 Join
问题现象
两张大表JOIN时,重分布(Shuffle)带来巨大的网络和 IO 开销,甚至跑不出数据。
根因分析
两表的分区键不一致,导致 Join 时数据需要在不同节点间重新分布,产生大量网络传输。
优化动作
将两张表的分区键设置为一致,包括分区键的顺序、字段类型完全对齐。这样 Join 时数据已经在同一节点上,实现本地 Join(Local Join),避免 Shuffle。
sql
-- 优化前:分区键不一致 -- 表 A 按 date 分区,表 B 按 category 分区 -- 优化后:统一按 date 分区 -- 表 A:PARTITION BY date -- 表 B:PARTITION BY date(与 A 完全一致)
优化结果
无需重分区,减少网络和 IO 开销
几个小时跑不完的任务缩短到几分钟
Doris / StarRocks 适用性
✅核心适用场景。Doris 的Colocation Join就是专门解决这个问题的:将两表按相同的 Bucket 分布,实现本地 Join,完全避免 Shuffle。Greenplum 中叫“分区键对齐”,Doris 中叫Colocation Group,原理完全一致。
sql
-- Doris Colocation Join 示例 CREATE TABLE table_a ( ... ) DISTRIBUTED BY HASH(order_id) BUCKETS 16 PROPERTIES ("colocate_with" = "group1"); CREATE TABLE table_b ( ... ) DISTRIBUTED BY HASH(order_id) BUCKETS 16 PROPERTIES ("colocate_with" = "group1");五、数据倾斜处理:大经销商占 30% 数据导致任务卡慢
问题现象
核心任务运行耗时40 分钟,某个大经销商的数据占全表的30%,导致数据严重倾斜,部分节点过载。
根因分析
表没有按字段分区,所有数据随机分布,导致大经销商的数据集中在少数节点上,造成计算倾斜。
优化动作
备份原表
按经销商标识字段重新分区(如按
dealer_id的哈希值或范围分区)同步分区到新表,重新加载数据
sql
-- 优化前:无分区 CREATE TABLE sales (dealer_id INT, amount DECIMAL); -- 优化后:按 dealer_id 分区 CREATE TABLE sales (dealer_id INT, amount DECIMAL) PARTITION BY RANGE (dealer_id) ( PARTITION p1 VALUES LESS THAN (100), PARTITION p2 VALUES LESS THAN (200), ... );
优化结果
数据均匀分布到各节点
耗时从 40 分钟降至 15 分钟
Doris / StarRocks 适用性
✅完全适用。Doris 中数据倾斜同样常见,解决方案包括:
调整 Bucket 键:选择倾斜列作为分桶键,或加盐(加随机数)打散
使用 Dynamic Partition:按时间或业务字段动态分区
开启 Load 时的数据均衡:
enable_auto_bucket = true
六、存储优化:分区 + 压缩,查询性能提升 50%
问题现象
大表查询越来越慢,全表扫描成本高,存储空间紧张。
根因分析
表未做分区,每次查询都是全表扫描
未启用压缩,存储成本高,IO 读取量大
优化动作
按主键分区存储(提升点查性能)
历史表按时间分区(如按月分区,查询时可分区裁剪)
启用压缩(如 ZSTD / LZ4,减少存储和 IO)
sql
-- Greenplum:按日期分区 + 压缩 CREATE TABLE sales ( sale_date DATE, amount DECIMAL ) PARTITION BY RANGE (sale_date) ( PARTITION p2024_01 START ('2024-01-01') END ('2024-02-01'), PARTITION p2024_02 START ('2024-02-01') END ('2024-03-01') ) WITH (appendonly=true, compresstype=zstd);优化结果
查询性能提升 50%
存储成本显著降低
Doris / StarRocks 适用性
✅完全适用且支持更好。
分区:Doris 支持
PARTITION BY RANGE / LIST,自动分区裁剪压缩:Doris 默认使用ZSTD / LZ4压缩,可配置
compression参数冷热分层:Doris 还支持 SSD + HDD 自动分层,进一步降低成本
sql
-- Doris 分区 + 压缩示例 CREATE TABLE sales ( sale_date DATE, amount DECIMAL ) PARTITION BY RANGE (sale_date) ( PARTITION p2024_01 VALUES LESS THAN ("2024-02-01"), PARTITION p2024_02 VALUES LESS THAN ("2024-03-01") ) PROPERTIES ( "compression" = "ZSTD" );总结:优化思路通用性对照表
| 优化场景 | Greenplum 中的做法 | Doris / StarRocks 对应做法 | 是否通用 |
|---|---|---|---|
| SQL 拆表 + UNION | CTE / 临时表 + UNION ALL | CTE + UNION ALL | ✅ 通用 |
| 循环改一次扫描 | CASE WHEN + 聚合 | CASE WHEN + 聚合 / 多表 UNION | ✅ 通用 |
| 统计信息过旧 | ANALYZE | ANALYZE TABLE | ✅ 通用 |
| 本地 Join(防 Shuffle) | 分区键对齐 | Colocation Join(机制相同) | ✅ 通用 |
| 数据倾斜 | 按倾斜字段重新分区 | 加盐 / 调整 Bucket 键 | ✅ 通用 |
| 分区 + 压缩 | Range 分区 + ZSTD | Range 分区 + ZSTD / LZ4 | ✅ 通用 |
结尾总结
MPP/OLAP 数据库虽然引擎各异(Greenplum、Doris、StarRocks、ClickHouse),但性能优化的底层逻辑是相通的:
减少数据扫描量(分区、索引、列裁剪)
减少数据移动(本地 Join、分区键对齐)
让优化器做出正确决策(更新统计信息)
均匀分布数据(避免倾斜)
降低 IO 成本(压缩、冷热分层)
掌握这些通用优化思路,无论换什么 OLAP 引擎,都能快速上手调优。
