Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1518865
  • 博文数量: 191
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4053
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-30 13:00
个人简介

About me:Oracle ACE pro,optimistic,passionate and harmonious. Focus on ORACLE,MySQL and other database programming,peformance tuning,db design, j2ee,Linux/AIX,Architecture tech,etc

文章分类

全部博文(191)

文章存档

2026年(2)

2025年(17)

2024年(27)

2023年(28)

2022年(43)

2020年(62)

2014年(3)

2013年(9)

分类: Oracle

2026-03-30 17:44:51

ORACLE和MySQL的hash join,如果hash table返回0行,则probe table不需要执行,执行次数是0,而PG的还会执行,hash table a返回0行,但是probe table b还要执行,只不过{BANNED}最佳多只扫描满足条件的一行去探测,发现hash table没有数据,后面的不再探测


ORACLE的hash join执行计划显示的顺序和PG,MySQL相反:
PG和MySQL的树形计划,hash table在下面,按照关键字hash标识,probe table在上面,这个是按照探测流程算的,其实probe table不是驱动表
而oracle是按照hash join执行顺序来的,上面是hash table,下面是probe table




对于hash table返回0行,probe table是否要执行,三大库本质上是**数据库引擎在处理“空结果集”时的流水线止损策略(Short-circuiting)**的不同实现。
我们可以把这三种数据库的行为模式总结为:“先确认,再开工”(Oracle/MySQL)与**“边开工,边确认”**(PostgreSQL)。


1. Oracle & MySQL:极致的短路逻辑(Start-Stop)在 Oracle 和 MySQL 的执行引擎中,Hash Join 被视为两个高度解耦的动作:“构建桶”和“流式探测”。
执行行为:引擎会完全运行 Build Table 分支。一旦 Build Table 完成并发现产生的 Hash Table 为空(A-Rows=0 或 Rows=0),Join 控制器会立刻向优化器或上层节点发出信号:
“由于 Hash Table 没有数据,任何 Probe 动作都是徒劳”。
因此,Probe Table 的扫描被完全跳过。
计划表现:Oracle: Starts = 0。这是一个非常清晰的标志,说明底层物理扫描函数根本没被调用。
MySQL: never executed。MySQL 8.0 的树状计划非常诚实地反映了这一跳过行为。
性能优势: 如果 Probe Table 是一个极其庞大的全表扫描(Full Table Scan),这种短路逻辑能节省巨大的 I/O。


2. PostgreSQL:懒加载与首次触达(Lazy Execution)PostgreSQL 的行为比较特殊。
它的执行器(Executor)是基于“拉取模型(Demand-driven pulling)”的。
执行行为:当 Hash Join 节点被调用时,它首先调用右侧的 Hash 节点(Build Table)来填充内存。
即便 Hash 节点返回了 0 行,由于 PG 的架构设计,Hash Join 节点作为父节点,
通常会尝试向左侧(Probe Table)发出至少一次获取数据的请求(ExecProcNode 调用)。
关键点: 如你观察到的,Seq Scan on sub_t b 确实显示了 rows=1 loops=1。
这代表它为了确认“有没有数据可以用来探测”,至少扫描了{BANNED}中国第一行。
后续行为: 当{BANNED}中国第一行数据拿回来准备匹配时,发现内存里的 Hash Buckets 是空的,
PG 此时才会意识到没必要继续,随后停止扫描。计划表现: 你会看到 actual rows=1,
这在统计上可能让 DBA 误以为扫描了很久,但其实它在获取{BANNED}中国第一行后就“梦醒”了。


3.三大库针对hash table返回0行的总结如下:
数据库 判定逻辑位置 当 Build Table 为空时 标志性输出
Oracle HASH JOIN 调度层 彻底不执行 Probe Table 分支 Starts = 0
MySQL Join Controller 层 彻底不执行 Probe Table 分支 never executed
PostgreSQL HashJoinState 迭代器 至少读取 1 行 探测表,随后停止 rows=1 loops=1




示例分析:
oracle 的hash table t1返回0行,T2的starts=0

点击(此处)折叠或打开

  1. PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    SQL_ID  83m3a3jtmx889, child number 0
    -------------------------------------
    select * from t1,t2 where t1.object_name=t2.object_name and
    t1.object_id=0


    Plan hash value: 1838229974


    -------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
    -------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |      |      1 |        |      0 |00:00:00.05 |    1460 |   1413 |       |       |          |
    |*  1 |  HASH JOIN         |      |      1 |      1 |      0 |00:00:00.05 |    1460 |   1413 |   789K|   789K|  226K (0)|
    |*  2 |   TABLE ACCESS FULL| T1   |      1 |      1 |      0 |00:00:00.05 |    1416 |   1412 |       |       |          |
    |   3 |   TABLE ACCESS FULL| T2   |      0 |  72814 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    -------------------------------------------------------------------------------------------------------------------------


    Predicate Information (identified by operation id):
    ---------------------------------------------------


       1 - access("T1"."OBJECT_NAME"="T2"."OBJECT_NAME")
       2 - filter("T1"."OBJECT_ID"=0)


 MySQL的hash table b表返回0行,probe table a表不执行:never executed

点击(此处)折叠或打开

  1.  explain analyze select/*+no_index(a) no_index(b)*/ * from emp1 a  join emp2 b on a.first_name=b.first_name where b.hire_date>date'2026-1-1'\G
    *************************** 1. row ***************************
    EXPLAIN: -> Inner hash join (a.first_name = b.first_name)  (cost=2885080313.73 rows=23061842) (actual time=146.816..146.816 rows=0 loops=1)
        -> Table scan on a  (cost=0.14 rows=288992) (never executed)
        -> Hash
            -> Filter: (b.hire_date > DATE'2026-01-01')  (cost=30216.55 rows=99831) (actual time=146.807..146.807 rows=0 loops=1)
                -> Table scan on b  (cost=30216.55 rows=299523) (actual time=0.061..129.105 rows=300014 loops=1)


    1 row in set (0.15 sec)




PG的,hash table a返回0行,但是probe table b还要执行,只不过{BANNED}最佳多只扫描满足条件的一行去探测,loops=1,发现hash table没有数据,后面的不再探测


点击(此处)折叠或打开

  1. explain analyze select/*+hashjoin( a b)*/ * from main_t a,sub_t b where a.id=b.id and a.name='abc';
  2.                                                            QUERY PLAN
  3. ---------------------------------------------------------------------------------------------------------------------------------
  4.  Hash Join (cost=8.30..1718.82 rows=1 width=15) (actual time=0.028..0.030 rows=0 loops=1)
  5.    Hash Cond: (b.id = a.id)
  6.    -> Seq Scan on sub_t b (cost=0.00..1448.00 rows=100000 width=7) (actual time=0.011..0.011 rows=1 loops=1)
  7.    -> Hash (cost=8.29..8.29 rows=1 width=8) (actual time=0.012..0.013 rows=0 loops=1)
  8.          Buckets: 1024 Batches: 1 Memory Usage: 8kB
  9.          -> Index Scan using idx_main_t on main_t a (cost=0.28..8.29 rows=1 width=8) (actual time=0.012..0.012 rows=0 loops=1)
  10.                Index Cond: (name = 'abc'::text)
  11.  Planning Time: 0.369 ms
  12.  Execution Time: 0.165 ms


阅读(15) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~