必须在WHERE阶段过滤数据,而非依赖HAVING;GROUP BY前加WHERE可大幅减少哈希分组内存压力,索引可加速扫描;ROLLUP配合GROUPING()优于UNION ALL;NULL值须用NVL/COALESCE显式处理;大批量聚合写入需控制FORALL批大小(500–2000行)并关闭AUTOCOMMIT。

GROUP BY 前必须加 WHERE 过滤,别等 HAVING
分组聚合慢,80% 是因为没在 WHERE 阶段砍掉无效数据。Oracle 19c 的哈希分组(HASH GROUP BY)会把所有满足 FROM 条件的行全加载进 PGA 内存做中间聚合,哪怕你最终只想要 dept_id IN (10,20) 的结果。
错误写法:SELECT dept_id, COUNT(*) FROM sales GROUP BY dept_id HAVING dept_id IN (10,20) → 所有千万级销售记录都进了内存再筛
正确做法是把条件前移到 WHERE:SELECT dept_id, COUNT(*) FROM sales WHERE dept_id IN (10,20) GROUP BY dept_id
- 哪怕只过滤掉 3% 的原始数据,也能降低哈希表大小和临时段写入压力
- 如果
dept_id上有索引,WHERE还能触发索引快速路径扫描(INDEX RANGE SCAN),跳过大量块读 -
HAVING只适合依赖聚合结果的条件,比如HAVING COUNT(*) > 1000
用 ROLLUP 替代 UNION ALL 多级分组,但得配 GROUPING()
要同时输出“按月”“按月+产品线”“总计”三层结果,别手写三个 UNION ALL 子句。Oracle 19c 对 ROLLUP 有专用优化路径,实测比手工拼接快 20%~40%。
示例:GROUP BY ROLLUP(month_id, product_line) 生成三组:(month_id, product_line)、(month_id, NULL)、(NULL, NULL)
- 必须用
GROUPING(month_id)和GROUPING(product_line)判断哪一列是小计占位符,不能直接判IS NULL—— 原始数据里真有 NULL 值时会混淆 - 推荐写法:
CASE WHEN GROUPING(month_id) = 1 THEN 'TOTAL' ELSE TO_CHAR(month_id) END AS month_label - 如果字段本身基数低(如
product_line只有 5 个值),且建了位图索引,ROLLUP还可能触发BITMAP INDEX SINGLE VALUE扫描,进一步加速
NULL 分组值必须显式处理,NVL/COALESCE 不是可选项
分组字段含 NULL 时,Oracle 默认把所有 NULL 归为同一组。这看起来“省事”,但业务上往往意味着“未填写”“未知”“待分配”,不该和任何真实维度混在一起。
错误写法:GROUP BY region → 所有 region IS NULL 行被强行塞进一组,且无法回溯来源
正确写法:GROUP BY COALESCE(region, 'UNKNOWN', 'N/A') 或 GROUP BY NVL(region, 'UNASSIGNED')
-
COALESCE更标准、支持多备选值;NVL在 Oracle 中性能几乎无差别,但只认两个参数 - 副作用:原列上的普通索引失效,除非你额外建函数索引,比如
CREATE INDEX idx_region_nvl ON sales(NVL(region, 'UNASSIGNED')) - 如果后续还要对分组结果做
ORDER BY,注意'UNKNOWN'字符串会影响排序位置,必要时用CASE控制顺序
大批量聚合写入时,FORALL + BULK COLLECT 要控制批大小
当聚合结果要批量插入另一张表(比如每日汇总写入 sales_daily_agg),别用单条 INSERT 循环,也别一股脑 BULK COLLECT 全部内存 —— PGA 溢出或游标失效风险极高。
实测甜点区间是每批 500~2000 行:
- 超过
2000,PGA 内存占用陡增,尤其目标表有 3+ 索引或触发器时,性能优势迅速归零 - 务必关掉
AUTOCOMMIT,否则每批FORALL后自动提交,退化成单条执行 - 启用
SAVE EXCEPTIONS时,SQL%BULK_EXCEPTIONS(i).ERROR_INDEX返回的是当前批内序号(从 1 开始),不是原始集合下标,取错记录很常见
复杂点在于:分组维度多、NULL 处理逻辑嵌套、ROLLUP 层级判断、批处理边界与事务一致性的平衡——这些地方一旦漏掉一层判断,结果就不可逆。


















