oracle 表连接方式: nested loop 嵌套循环和Hash Join的比较

一、创建两张实验用表:wireless_site.merchant和wireless_site.bb

SQL> select count(*) from wireless_site.merchant;

  COUNT(*)
----------
     14005

SQL>

SQL> select count(*) from wireless_site.clickthroughrate;

  COUNT(*)
----------
   2384026

SQL> create table wireless_site.bb as select * from wireless_site.clickthroughrate;

Table created.

SQL> 
SQL> select count(*) from wireless_site.bb;

  COUNT(*)
----------
   2384026

SQL>

二、执行SQL语句并查看sql语句的真实执行计划:

SQL> alter session set statistics_level=all;

Session altered.

SQL> 

2.1、首先查看Hash Join的执行计划:

执行SQL语句:select  * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

由以上执行计划可以看出:

1、HASH JOIN的驱动表为BB,扫描方式为全表扫描,执行了1次,实际返回了1658行

2、HASH JOIN的被驱动表为MERCHANT,扫描方式为全表扫描,执行了1次,实际返回了12行

3、需要注意的是:HASH JOIN的驱动表被被驱动表都会被扫描一次,而nested loop则是驱动表扫描一次,被驱动表被扫描N次(具体N的值是根据驱动表返回的行数来决定的。)

 

2.2、接着使用HINT的方式让优化器强制走nested loop(使用wireless_site.merchant作为驱动表)

执行SQL语句:select /*+ leading(merchant) use_nl(bb) */ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

此时,wireless_site.merchant作为驱动表,加上过滤条件filter后,实际返回行数为:

由以上执行计划可以看出:

1、嵌套循环的驱动表为merchant,采用的扫描方式为全表扫描,执行了1次,返回了12行

2、嵌套循环的被驱动表为bb,采用的扫描方式是全表扫描,执行了12次,实际返回了1658行

 

 

2.3、然后再使用HINT的方式让优化器强制走nested loop(使用wireless_site.bb作为驱动表)

执行SQL语句:select /*+ leading(bb) use_nl(merchant) */ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';
等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

此时,wireless_site.bb作为驱动表,加上过滤条件filter后,实际返回行数为:

由以上执行计划可以看出:

1、表BB作为驱动表,扫描方式是全表扫描,实际返回行数为1658

2、表merchant作为被驱动表,采用的扫描方式为索引唯一扫描,执行了1658次,返回了1658行

3、被驱动表merchant索引扫描完成后,通过rowid回表读数执行了1658次,实际返回了1658行

 

 

2.4、接下来在wireless_site.bb表中的recordtype列上创建索引

SQL> create index wireless_site.bb_idx on wireless_site.bb(recordtype);

Index created.

SQL>

接着使用HINT的方式让优化器强制走nested loop(使用wireless_site.merchant作为驱动表):

执行SQL语句:select /*+ leading(merchant) use_nl(bb) */ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

由执行计划可以看出:

1、merchant作为驱动表,采用的是全表扫描的方式,实际返回行数为12

2、被驱动表BB的索引采用的是Index Range Scan的方式,实际返回行数为1658,共执行了12次

3、被驱动表BB索引被访问了12次,每次扫描完索引后又根据rowid回表读数(TABLE ACCESS BY INDEX ROWID),这样BB表就被访问了1658次

对比之前没有在wireless_site.bb表中的recordtype列上创建索引时的执行计划可以看出,创建索引后SQL花费不到1s的时间就可以出结果;没有创建索引时,需要花费1分32秒左右。

 

2.5、创建索引后,使用HINT的方式让优化器强制走nested loop(使用wireless_site.bb作为驱动表):

执行SQL语句:select /*+ leading(bb) use_nl(merchant) */ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';
等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

由执行计划可以看出:

1、嵌套循环的驱动表为bb,扫描方式为全表扫描,执行了1次,实际返回了1658行

2、嵌套循环的被驱动表的索引为SYS_C0011475,扫描方式为INDEX  UNIQUE SCAN,执行了1658次,实际返回了1658行

3、嵌套循环的被驱动表为merchant,merchant的所用被扫描了1658次,每次扫描完索引后又根据rowid回表(TABLE ACCESS BY INDEX ROWID),执行了1658次,实际返回了1658行

4、sql语句执行完,大概需要7.42秒

 

2.6、创建索引后,使用HINT强制走HASH JOIN(wireless_site.bb作为驱动表)

执行SQL语句:select /*+ leading(bb) use_hash(merchant)*/ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';
等待SQL语句执行完毕,然后再使用这条SQL语句:select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));查看真实执行计划:

由执行计划可以看出:

1、HASH JOIN的驱动表为BB,扫描方式为全表扫描,执行了1次,实际返回了1658行

2、HASH JOIN的被驱动表为merchant,扫描方式为全表扫描,执行了1次,实际返回了12行

3、使用HASH Join 执行时,花费时间约为8.54秒

 

三、结论:

3.1、在nested loop 中,驱动表返回一行数据,被驱动表要被扫描一次

3.2、在nested loop中,往往是将小表作为驱动表,大表作为被驱动表。所谓小表,不是说这个表的segment_size的大小,而是指返回数据的多少;返回数据量少,我们就称之为小表,返回数据量大,我们称之为大表。

3.3、在nested loop中,如果发现SQL的执行计划中的nested loop使用了全表扫描,这时我们就要特别注意了:直接select count(*) from 驱动表where 过滤条件...看它返回多少行(检查驱动表返回的总行数),看他是否能不走全表扫描。

3.4、HASH JOIN的驱动表和被驱动表都被扫描1次。

3.4、HASH JOIN会耗费PGA的work_area。嵌套循环不耗费PGA的work_area。

 

 

四、附录

4.1、在SQL语句优化过程中,我们经常会用到hint,现总结一下在SQL优化过程中常见Oracle HINT的用法:

1. /*+ALL_ROWS*/表明对语句块选择基于开销的优化方法,并获得最佳吞吐量,使资源消耗最小化.

例如:SELECT /*+ALL+_ROWS*/ merchantid,merchantname,status FROM merchant WHERE merchantid='3712098761';

2. /*+FIRST_ROWS*/表明对语句块选择基于开销的优化方法,并获得最佳响应时间,使资源消耗最小化.

例如:SELECT /*+FIRST_ROWS*/ merchantid,merchantname,status FROM merchant WHERE merchantid='3712098761';

3. /*+CHOOSE*/表明如果数据字典中有访问表的统计信息,将基于开销的优化方法,并获得最佳的吞吐量;表明如果数据字典中没有访问表的统计信息,将基于规则开销的优化方法;

例如:SELECT /*+CHOOSE*/ merchantid,merchantname,status FROM merchant WHERE merchantid='3712098761';

4. /*+RULE*/表明对语句块选择基于规则的优化方法.

例如:SELECT /*+ RULE */ merchantid,merchantname,status FROM merchant WHERE merchantid='3712098761';

5. /*+FULL(TABLE)*/表明对表选择全局扫描的方法.

例如:SELECT /*+FULL(A)*/ merchantid,merchantname,status FROM merchant WHERE merchantid='3712098761';

6. /*+ROWID(TABLE)*/提示明确表明对指定表根据ROWID进行访问.

例如:SELECT /*+ROWID(BSEMPMS)*/ * FROM merchant WHERE ROWID>='AAAAAAAAAAAAAA'AND merchantid='3712098761';

7. /*+CLUSTER(TABLE)*/提示明确表明对指定表选择簇扫描的访问方法,它只对簇对象有效.

例如:SELECT /*+CLUSTER */ merchant.merchantid,package.packagename FROM merchant,package where merchantid='3712098761' AND merchant.merchantid=package.merchantid;

8. /*+INDEX(TABLE INDEX_NAME)*/表明对表选择索引的扫描方法.

例如:SELECT /*+INDEX(bb bb_idx)  */ FROM wireless_site.bb WHERE recordtype='3712098761';

9. /*+INDEX_ASC(TABLE INDEX_NAME)*/表明对表选择索引升序的扫描方法.

例如:SELECT /*+INDEX_ASC(bb bb_idx) */ FROM wireless_site.bb WHERE recordtype='3712098761';

10. /*+USE_HASH(TABLE)*/将指定的表与其他行源通过哈希连接方式连接起来.

例如:SELECT /*+USE_HASH(bb,merchant)*/ * FROM wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

or:select /*+ leading(bb) use_hash(merchant)*/ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

11. /*+USE_NL(TABLE)*/将指定表与嵌套的连接的行源进行连接,并把指定表作为内部表.

例如:SELECT /*+bb USE_NL(merchant)*/ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

or:select /*+ leading(merchant) use_nl(bb) */ * from wireless_site.merchant,wireless_site.bb where bb.recordtype = merchant.merchantid and merchant.merchantid like '%3210%';

Leading(): 指示Oracle在执行join(hash join, nested loop join, merge join)时的连接顺序。当执行计划不按照最优的表连接顺序时,考虑使用Leading 改变执行计划。 使用leading 的目的是减少检索数据量, 提高查询性能

 

4.2、查看某张表的统计信息是否过期

执行如下SQL语句查询:

SELECT OWNER,TABLE_NAME,OBJECT_TYPE,STALE_STATS,LAST_ANALYZED FROM DBA_TAB_STATISTICS WHERE (STALE_STATS='YES' OR LAST_ANALYZED IS NULL) AND OWNER NOT IN ('SYS','SYSTEM','SYSMAN','DMSYS','OLAPSYS','XDB','EXFSYS','CTXSYS','WMSYS','DBSNMP','ORDSYS','OUTLN','TSMSYS','MDSYS') AND TABLE_NAME NOT LIKE 'BIN%';

结果如下:

或者,这行这条SQL:

select owner,table_name,object_type,stale_stats,last_analyzed from dba_tab_statistics where table_name in (table_name) and owner = 'WIRELESS_SITE' and (stale_stats = 'YES' or last_analyzed in null);

或者执行这条SQL:

SELECT OWNER,TABLE_NAME,OBJECT_TYPE,STALE_STATS,LAST_ANALYZED FROM DBA_TAB_STATISTICS WHERE OWNER='&OWNER' AND TABLE_NAME='&TABLE_NAME';

 

首先创建一张实验表:

create table wireless_site.xxx as select rownum as col1, rownum + 1 as col2, rownum + 2 as col3 from dual connect by level <= 10000;

查看表结构

对该表收集统计信息:

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname => 'wireless_site',
tabname => 'xxx',
estimate_percent => 30,
method_opt => 'for all columns size repeat',
no_invalidate => FALSE,
degree => 8,
cascade => TRUE);
END;
/

运行如下两个脚本:

exec dbms_stats.flush_database_monitoring_info; 

select owner,table_name,object_type,stale_stats,last_analyzed from dba_tab_statistics where table_name in (table_name) and owner = 'WIRELESS_SITE' and (stale_stats = 'YES' or last_analyzed in null);

结果显示没有记录是因为我的表是刚刚收集完统计信息,所以没有过期。

接下来删除表中20%的数据:

delete from wireless_site.xxx where rownum<=(select count(*) from wireless_site.xxx)*0.2;

然后再来运行脚本:exec dbms_stats.flush_database_monitoring_info;

查看结果,已过期:

或者:

或者:

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值