订阅更新

同一张表同样的查询,SQL写法不同性能差5倍——Doris SQL优化技巧

Doris手册 第 17 题 约 5 分钟读完

本文目录
  1. 先说结论
  2. 技巧一:只SELECT需要的列
  3. 技巧二:用分区列做WHERE过滤
  4. 技巧三:避免SELECT DISTINCT,改用GROUP BY
  5. 技巧四:小表放JOIN右边 + CTE替代嵌套子查询
  6. 实战经验:SQL改写让查询从8秒降到1.5秒
  7. 面试高频追问
  8. 总结

先说结论

Doris的SQL优化有五个关键技巧:① 只SELECT需要的列(减少IO);② 用分区列做过滤(分区裁剪);③ 避免SELECT DISTINCT改用GROUP BY;④ 利用CTE替代嵌套子查询;⑤ 大表JOIN小表时小表放右边。这些技巧组合使用,可以把查询速度提升数倍。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

前面讲了分区裁剪、索引、Colocate Join这些表层面的优化,这篇聊SQL写法层面的优化。在某元宇宙社交公司,同一张表同样的分析需求,一个同事写的SQL跑8秒,我改了几行跑1.5秒,差5倍。SQL优化技巧看起来都是小事,但在TB级数据量下,每一个优化点都可以带来成倍的性能提升。

技巧一:只SELECT需要的列

-- 慢:SELECT * 读取所有50个列
SELECT * FROM dwd_user_event
WHERE event_time >= '2024-01-15' AND user_id = 12345;
-- 列式存储要读取50列的数据文件,IO巨大
 
-- 快:只SELECT需要的5个列
SELECT event_time, event_name, amount, device_id, properties
FROM dwd_user_event
WHERE event_time >= '2024-01-15' AND user_id = 12345;
-- 只读取5列的数据文件,IO减少90%
-- Doris是列式存储,SELECT的列越少IO越小

技巧二:用分区列做WHERE过滤

-- 慢:不带分区条件(扫描全部365天)
SELECT COUNT(*) FROM dwd_user_event
WHERE event_name = 'gift_send';
 
-- 快:加上分区列条件(只扫描1天)
SELECT COUNT(*) FROM dwd_user_event
WHERE event_time >= '2024-01-15'
  AND event_time < '2024-01-16'
  AND event_name = 'gift_send';
-- 数据量减少97%,速度提升30倍以上
 
-- 即使业务逻辑不需要时间过滤,也建议加一个合理的时间范围
-- 比如最近30天:WHERE event_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)

技巧三:避免SELECT DISTINCT,改用GROUP BY

-- 慢:SELECT DISTINCT
SELECT DISTINCT user_id FROM dwd_user_event
WHERE event_time >= '2024-01-15';
 
-- 快:改用GROUP BY
SELECT user_id FROM dwd_user_event
WHERE event_time >= '2024-01-15'
GROUP BY user_id;
 
-- 更快:如果只要去重后的数量,用近似去重
SELECT APPROX_COUNT_DISTINCT(user_id) FROM dwd_user_event
WHERE event_time >= '2024-01-15';
-- 近似去重(HyperLogLog算法,误差约1-2%)在亿级数据上快10倍以上

技巧四:小表放JOIN右边 + CTE替代嵌套子查询

-- Doris默认广播JOIN的右表,小表放右边效率更高
SELECT e.event_time, e.amount, u.nickname, u.country
FROM dwd_user_event e           -- 大表(5000万行/天)
LEFT JOIN dim_user u             -- 小表(200万行)放右边
    ON e.user_id = u.user_id
WHERE e.event_time >= '2024-01-15';
-- 用WITH (CTE) 替代嵌套子查询,清晰且易优化
WITH gift_events AS (
    SELECT user_id, amount
    FROM dwd_user_event
    WHERE event_time >= '2024-01-15'
      AND event_name = 'gift_send'
),
user_totals AS (
    SELECT user_id, SUM(amount) AS total
    FROM gift_events GROUP BY user_id
)
SELECT * FROM user_totals WHERE total > 100;

实战经验:SQL改写让查询从8秒降到1.5秒

在某元宇宙社交公司,一个同事写了一条查'每日送礼Top10用户'的SQL,跑了8秒。我帮他改了四个地方:

改动一:SELECT *改成只选需要的6个字段。IO减少80%。

改动二:加上了event_time的分区过滤条件。扫描量从3个月减到1天。

改动三:子查询嵌套改成CTE写法。优化器生成了更好的执行计划。

改动四:ORDER BY + LIMIT放在最外层而不是子查询里。减少了中间结果集的排序量。

四个改动加起来,从8秒降到1.5秒。纯SQL层面的优化,没有改任何表结构。

面试高频追问

追问一:APPROX_COUNT_DISTINCT的误差有多大?

默认使用HyperLogLog算法,误差约1-2%。对于大盘UV统计完全够用。我在元宇宙社交公司的实时大盘UV用的就是近似去重——投手不care差1%,但很care查询要等5秒。

追问二:Doris的WHERE条件顺序影响性能吗?

理论上优化器会自动调整,但实测发现把分区列条件放在WHERE第一个位置,分区裁剪更容易被触发。建议分区列条件永远写在WHERE第一行。

追问三:Doris支持窗口函数吗?

支持。ROW_NUMBER、RANK、LAG、LEAD、SUM OVER等都支持。但窗口函数消耗很多内存,建议先用WHERE/GROUP BY缩小数据量再用窗口函数。

总结

Doris SQL优化面试记住五个技巧:① 只SELECT需要的列;② 用分区列过滤;③ 避免DISTINCT用GROUP BY;④ 小表放JOIN右边;⑤ CTE替代嵌套子查询。这些都是SQL写法层面的优化,不需要改表结构就能生效。能讲出实际的SQL改写案例和量化效果,面试官会很认可。

下一篇聊Doris的Multi-Catalog:不用导入数据就能查Hive和S3?

你们有什么Doris SQL优化的经验?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器