哈希连接是Oracle在处理等值连接、尤其是一张大表关联另一张表时非常常见的执行计划选择。它的核心思路是把驱动表(较小的那张表)的连接列做哈希运算后放入一块内存区域,这块内存就是哈希区Hash Area,然后再扫描另一张大表,逐行计算哈希值去内存中匹配。整个过程能否高效完成,几乎完全取决于哈希区能不能装下驱动表的哈希表。装得下,就是最优的一趟哈希连接;装不下,Oracle就要做分区、溢出到磁盘临时表空间,性能差距可能是几十倍。所以理解Hash Area的配置逻辑,是SQL调优里非常值得投入时间的一课。

哈希连接的执行原理与哈希区的角色
要理解哈希区为什么重要,得先看哈希连接到底是怎么跑的。假设有一条SQL:select /*+ use_hash(a b) */ a.*, b.* from big_table a join small_table b on a.id = b.id。优化器如果选择哈希连接,执行顺序是先把small_table作为驱动表读进来,对每一行的id列计算哈希值,把整行数据存放到哈希区的对应桶里,构建出一张内存中的哈希表。构建完成后,再去扫描big_table,每读到一行就对id算一次哈希值,直接定位到哈希区里的桶进行匹配,匹配成功的行就是结果集的一部分。
这个过程中哈希区扮演的角色相当于一张临时内存索引表。理想状态下驱动表全放内存,探测阶段纯内存操作,速度非常快。但如果驱动表数据量超出哈希区容量,Oracle会启用Optimal、OnePass、MultiPass三种工作区策略中的后两种:OnePass表示额外多扫描一遍输入数据,MultiPass则意味着数据要在内存和磁盘之间来回倒腾多趟。一旦落到磁盘临时表空间,磁盘IO就会成为主要瓶颈,原本几秒的查询可能变成几分钟。
可以通过v$sql_workarea视图观察某个游标实际使用的工作区情况,其中的optimal_executions、onepass_executions、multipass_executions三列能直接告诉你哈希连接有没有因为内存不足而降级,这是判断哈希区配置是否合理最直接的证据。
hash_area_size与PGA自动管理的关系
在Oracle 9i之前,哈希区大小由参数hash_area_size直接控制,排序区由sort_area_size控制,都是会话级的手工管理方式。9i之后Oracle引入了PGA自动管理,通过pga_aggregate_target(10g以后还有pga_aggregate_target之上的隐含限制)和workarea_size_policy参数,让数据库根据整体PGA预算自动为每个工作区分配内存。当workarea_size_policy设为auto时,hash_area_size会被忽略;只有设为manual时才生效。
查看当前配置很简单:
-- 查看工作区管理策略 show parameter workarea_size_policy; -- 查看PGA总预算 show parameter pga_aggregate_target; -- 查看手工模式下的哈希区大小 show parameter hash_area_size;
自动管理下的分配遵循一定的内部规则:串行执行的SQL单个工作区理论上最多能用到PGA预算的5%左右(由隐藏参数_smm_max_size控制,单位KB),并行执行的会话可以拿到更多。这就带来一个现实问题:如果一个实例的pga_aggregate_target只有2GB,而某个哈希连接需要800MB的哈希区,自动管理根本给不到,查询必然降级到OnePass甚至MultiPass。这时要么调大PGA预算,要么对该SQL使用手动hint强制指定内存,要么从SQL本身入手减少驱动表的数据量。
另外要注意,专有连接模式下每个会话的PGA是独立计算的,几百个会话同时跑大哈希连接时,pga_aggregate_target会被多个会话瓜分,实际每个会话拿到的份额远小于预期。这也是为什么并发高的系统里,单纯调大PGA参数未必见效,反而要通过v$pga_target_advice评估合理的总量。
如何判断哈希区已经成为性能瓶颈
判断思路有三条:看执行计划、看运行时统计、看等待事件。执行计划层面,如果哈希连接的驱动表选择不合理,比如优化器把大表选成了驱动表,哈希区天然就不够用,这时应该检查统计信息是否过期,或者考虑加swap_join_inputs类的hint调整驱动表顺序。
运行时统计层面,重点看v$sql_workarea和v$sql_workarea_active。下面这个查询能列出最近执行SQL的工作区使用情况:
select sql_id,
operation_id,
operation_type,
policy,
estimated_optimal_size / 1024 / 1024 as est_opt_mb,
total_executions,
onepass_executions,
multipass_executions,
max_tempseg_size / 1024 / 1024 as max_temp_mb
from v$sql_workarea
where operation_type = 'HASH-JOIN'
and multipass_executions > 0
order by max_tempseg_size desc;如果multipass_executions大于零,说明该哈希连接确实发生了多趟处理,max_tempseg_size列还能看到它落盘占用了多少临时段。等待事件层面,哈希区不足时会看到direct path read temp、direct path write temp等待,这是数据溢出到临时表空间的典型信号,配合AWR报告中的PGA统计部分,基本可以定位问题。
实际调优案例与配置建议
举一个常见的场景:一张5000万行的明细表关联一张20万行的维表,等值关联,驱动表是维表,哈希表大约需要300MB内存。最初实例的pga_aggregate_target设的是1GB,AWR显示该SQL每次都是MultiPass,临时表空间写入量巨大,单次执行40分钟。将pga_aggregate_target逐步上调到4GB后,通过v$pga_target_advice确认缓存命中率曲线趋于平缓,该SQL升级为Optimal一趟完成,执行时间降到90秒左右。这个案例说明哈希区的瓶颈本质上往往不是哈希区本身,而是PGA总预算不够。
配置上有几条经验可以参考。第一,优先使用自动管理,不要轻易退回manual模式,手工设置hash_area_size在并发环境下容易失控。第二,用v$pga_target_advice找到性价比最高的PGA总量,通常曲线在某个点之后收益递减,不必盲目调大。第三,对个别确实需要超大哈希区的报表SQL,考虑改写SQL减少驱动表列的宽度,比如只把连接需要的列放进哈希表,或者用ROWID批处理方式分片执行。第四,确认临时表空间所在磁盘的IO能力,即便内存配得再大,偶发的溢出也不可完全避免,临时表空间的性能决定了兜底水位。
-- 评估不同PGA目标下的缓存命中表现
select round(pga_target_for_estimate / 1024 / 1024) as pga_mb,
bytes_processed,
estd_extra_bytes_rw,
estd_pga_cache_hit_percentage
from v$pga_target_advice;最后补充一点,19c及以后的版本中,还可以通过SQL执行计划基线或SQL Patch固定内存相关的hint(比如mundane的内存提示),同时结合Oracle Real Application Testing验证调整效果。哈希区调优没有万能参数,核心方法论始终是:先用视图确认降级类型,再算清需要的内存量,最后选择调PGA预算、优化SQL或改造执行方式中的最经济路径。
Oracle Hash Area哈希连接PGA自动管理修改时间:2026-09-10 20:02:40