ITPUB??ì3
ITPUB论坛 » Oracle数据库管理 » 对Hash Join的一次优化

在线/呼叫 eagle_fan
高级会员


精华贴数 3
个人空间 0
技术积分 2580 (597)
社区积分 348 (1674)
注册日期 2003-10-6
论坛徽章:6
现任管理团队成员ITPUB元老会员2007贡献徽章会员2006贡献徽章授权会员ITPUB新首页上线纪念徽章
      

发表于 2008-3-17 11:47 
对Hash Join的一次优化

前两天解决了一个优化SQL的case,SQL语句如下,big_table为150G大小,small_table很小,9000多条记录,不到1M大小
hash_area_size, sort_area_size均设置足够大,可以进行optimal hash join和memory sort


select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category  = b.from_cat or
       a.category2 = b.from_cat) and
       a.site_id  = b.site_id and
       a.sale_end >= sysdate;
PHP code:


--------------------------------------------------------------------------

Id  Operation            |  Name        Rows  Bytes Cost (%CPU)|

--------------------------------------------------------------------------

|   
SELECT STATEMENT     |              |     |   174 |    18  (17)|

|   
|  SORT UNIQUE         |              |     |   174 |    18  (17)|

|*  
|   HASH JOIN          |              |     |   174 |    17  (12)|

|   
|    TABLE ACCESS FULL SMALL_TABLE  |  1879 48854 |    14   (8)|

|*  
|    TABLE ACCESS FULL BIG_TABLE    |     |   244 |     3  (34)|

--------------------------------------------------------------------------

粗略来看,PLAN非常的完美,SQL HINT写的也很到位,小表在内build hash table,大表在外进行probe操作,
根据经验来看,整个SQL执行的时间应该和FTS BIG_TABLE的时间差不多

但是FTS BIG_TABLE的时间大约是8分钟,而真个SQL执行的时间长达3~4小时

那么问题究竟出在哪里?

FTS时间应该不会有太大变化,那么问题应该在hash join,设置event来trace一下hash join的过程。

SQL> alter session set events '10104 trace name context forever, level 2';

Session altered.

select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category  = b.from_cat or
       a.category2 = b.from_cat) and
       a.site_id  = b.site_id and
       a.sale_end >= sysdate;

从trace file中Hash Table中这一段找出了问题所在:


### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
#       than the true number.
Number of buckets with   0 rows:      16373
Number of buckets with   1 rows:          0
Number of buckets with   2 rows:          0
Number of buckets with   3 rows:          1
Number of buckets with   4 rows:          0
Number of buckets with   5 rows:          0
Number of buckets with   6 rows:          0
Number of buckets with   7 rows:          1
Number of buckets with   8 rows:          0
Number of buckets with   9 rows:          0
Number of buckets with between  10 and  19 rows:          1
Number of buckets with between  20 and  29 rows:          1
Number of buckets with between  30 and  39 rows:          3
Number of buckets with between  40 and  49 rows:          0
Number of buckets with between  50 and  59 rows:          0
Number of buckets with between  60 and  69 rows:          0
Number of buckets with between  70 and  79 rows:          0
Number of buckets with between  80 and  89 rows:          0
Number of buckets with between  90 and  99 rows:          0
Number of buckets with 100 or more rows:          4
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 16373 Non-empty buckets: 11
Total number of rows: 9232
Maximum number of rows in a bucket: 2531
Average number of rows in non-empty buckets: 839.272705

仔细看,在一个bucket中最多的行数竟然有2531行,因为bucket中是一个链表的结构,所以这几千行都是串在一个链表上。
由这一点想到这个Hash Table所依赖的hash key的distinct value可能太少,重复值太多。否则不应该会有这么多行在同一个bucket里面。

因为Join条件里面有两个列from_cat和site_id,穷举法有三种情况

1. Build hash table based on (from_cat,site_id):


SQL> select site_id,from_cat,count(*) from SMALL_TABLE group by site_id,from_cat having count(*)>100;

no rows selected

2. Build hash table based on (from_cat):

SQL> select from_cat,count(*) from SMALL_TABLE group by from_cat having count(*)>100;

no rows selected

3. Build hash table based on (site_id):

SQL> select site_id,count(*) from SMALL_TABLE group by site_id having count(*)>100;

   SITE_ID   COUNT(*)
---------- ----------
         0       2531
         2       2527
       146       1490
       210       2526

到这里可以发现,基于site_id这种情况和trace file中这两行很相符:

Number of buckets with 100 or more rows: 4
Maximum number of rows in a bucket: 2531

所以推断这个hash table是基于site_id而建的,而Big_Table中大量的行site_id=0,都落在这个linked list最长的bucket中.
而大部分行都会扫描完整个链表而最后被丢弃掉,所以这个Hash Join的操作效率非常差,几乎变为了Nest Loop操作

找到了根本原因,问题也就迎刃而解了。

理想状况下,hash table应当建立于(site_id,from_cat)上,那么问题肯定出在这个OR上,把OR用UNION改写


select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where  a.category  = b.from_cat and
       a.site_id  = b.site_id and
       a.sale_end >= sysdate
UNION
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where  a.category2 = b.from_cat and
       a.site_id  = b.site_id and
       a.sale_end >= sysdate;
PHP code:


--------------------------------------------------------------------------

Id  Operation            |  Name        Rows  Bytes Cost (%CPU)|

--------------------------------------------------------------------------

|   
SELECT STATEMENT     |              |     |   148 |    36  (59)|

|   
|  SORT UNIQUE         |              |     |   148 |    36  (59)|

|   
|   UNION-ALL          |              |       |       |            |

|*  
|    HASH JOIN         |              |     |    74 |    17  (12)|

|   
|     TABLE ACCESS FULLSMALL_TABLE  |  1879 48854 |    14   (8)|

|*  
|     TABLE ACCESS FULLBIG_TABLE    |     |   192 |     3  (34)|

|*  
|    HASH JOIN         |              |     |    74 |    17  (12)|

|   
|     TABLE ACCESS FULLSMALL_TABLE  |  1879 48854 |    14   (8)|

|*  
|     TABLE ACCESS FULLBIG_TABLE    |     |   192 |     3  (34)|

--------------------------------------------------------------------------

初看这个PLAN好像不如第一个PLAN,因为执行了两次BIG_TABLE的FTS,但是让我们在来看看HASH TABLE的结构

### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
#       than the true number.
Number of buckets with   0 rows:       9306
Number of buckets with   1 rows:       5310
Number of buckets with   2 rows:       1436
Number of buckets with   3 rows:        285
Number of buckets with   4 rows:         43
Number of buckets with   5 rows:          4
Number of buckets with   6 rows:          0
Number of buckets with   7 rows:          0
Number of buckets with   8 rows:          0
Number of buckets with   9 rows:          0
Number of buckets with between  10 and  19 rows:          0
Number of buckets with between  20 and  29 rows:          0
Number of buckets with between  30 and  39 rows:          0
Number of buckets with between  40 and  49 rows:          0
Number of buckets with between  50 and  59 rows:          0
Number of buckets with between  60 and  69 rows:          0
Number of buckets with between  70 and  79 rows:          0
Number of buckets with between  80 and  89 rows:          0
Number of buckets with between  90 and  99 rows:          0
Number of buckets with 100 or more rows:          0
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 9306 Non-empty buckets: 7078
Total number of rows: 9232
Maximum number of rows in a bucket: 5
Average number of rows in non-empty buckets: 1.304323

这就是我们所需要的Hash Table,最长的链表只有五行数据

整个SQL的执行时间从三四个小时缩短为16分钟,大大超出了developer的预期

这个SQL单纯从PLAN上很难看出问题所在,需要了解Hash Join的机制,进行更深一步的分析

[ 本帖最后由 eagle_fan 于 2008-3-17 13:09 编辑 ]


__________________
只看该作者    顶部
离线 晶晶小妹
月是上弦


精华贴数 3
个人空间 6470
技术积分 1751 (933)
社区积分 9 (11493)
注册日期 2008-2-15
论坛徽章:4
现任管理团队成员2008北京奥运纪念徽章:跳水2008北京奥运纪念徽章:体操数据库板块每日发贴之星  
      

发表于 2008-3-17 12:10 


楼主辛苦了,先支持,再看!


__________________
没有必胜的秘籍,没有方程式遵循
要赢~只有全身心的投入!



为了方便大家查阅,所有的文章都已转入空间

http://space.itpub.net/?13095417

请大家多多支持!
只看该作者    顶部
离线 eagle3250
一般会员



精华贴数 0
个人空间 0
技术积分 251 (7627)
社区积分 4 (17447)
注册日期 2005-3-1
论坛徽章:0
      
      

发表于 2008-3-17 12:12 
谢谢分享!


__________________
Cut lost short,let profit run!http://eagle3250.itpub.net/
只看该作者    顶部
在线/呼叫 Alienovo
DBA ON THE ROAD


精华贴数 0
个人空间 10
技术积分 2422 (636)
社区积分 4817 (290)
注册日期 2006-7-12
论坛徽章:29
红孩儿授权会员2008北京奥运纪念徽章:花样游泳2008北京奥运纪念徽章:足球行业板块每日发贴之星行业板块每日发贴之星
2008北京奥运纪念徽章:田径2008北京奥运纪念徽章:举重2008北京奥运纪念徽章:垒球2008北京奥运纪念徽章:羽毛球2008北京奥运纪念徽章:水球2008北京奥运纪念徽章:柔道

发表于 2008-3-17 12:19 
谢谢LZ分享!


只看该作者    顶部
离线 gengyonghui
老耿


精华贴数 1
个人空间 0
技术积分 5865 (223)
社区积分 143 (2785)
注册日期 2004-8-28
论坛徽章:13
会员2007贡献徽章授权会员ITPUB新首页上线纪念徽章数据库板块每日发贴之星数据库板块每日发贴之星数据库板块每日发贴之星
数据库板块每日发贴之星数据库板块每日发贴之星数据库板块每日发贴之星生肖徽章:狗数据库板块每日发贴之星数据库板块每日发贴之星

发表于 2008-3-17 13:08 
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category  = b.from_cat or
       a.category2 = b.from_cat) and
       a.site_id  = b.site_id and
       a.sale_end >= sysdate;


两点建议:

1.查看sale_end列,看是否有必要建索引。
2.改写成
select a.ID
from BIG_TABLE a
where a.sale_end >= sysdate
and exists(select null
             from  SMALL_TABLE b
            where a.site_id  = b.site_id
              and b.from_cat in(a.category,a.category2)
           )


__________________
msn:gengyonghui@hotmail.com
blog:http://gengyonghui.itpub.net
只看该作者    顶部
在线/呼叫 eagle_fan
高级会员


精华贴数 3
个人空间 0
技术积分 2580 (597)
社区积分 348 (1674)
注册日期 2003-10-6
论坛徽章:6
现任管理团队成员ITPUB元老会员2007贡献徽章会员2006贡献徽章授权会员ITPUB新首页上线纪念徽章
      

发表于 2008-3-17 13:12 
编辑了一下plan的格式为PHPcode,贴段代码真不容易啊


__________________
只看该作者    顶部
在线/呼叫 bluemoon0083
大表哥


精华贴数 2
个人空间 0
技术积分 6660 (186)
社区积分 68 (4100)
注册日期 2005-12-29
论坛徽章:13
会员2007贡献徽章授权会员2008北京奥运纪念徽章:现代五项生肖徽章2007版:猪生肖徽章2007版:牛生肖徽章2007版:鸡
生肖徽章2007版:鼠生肖徽章2007版:猴ITPUB新首页上线纪念徽章生肖徽章:虎数据库板块每日发贴之星 

发表于 2008-3-17 13:17 
我应该会想到把or改成union,但具体为什么速度能够加快就不能分析的这么透彻了



__________________
偶是新手,正在学习oracle db+apps+linux,如有说错的地方请批评指正!
只看该作者    顶部
离线 rollingpig
版主


精华贴数 5
个人空间 100
技术积分 26725 (30)
社区积分 1659 (661)
注册日期 2001-10-18
论坛徽章:54
现任管理团队成员2006年度最佳技术回答Heart of PUBITPUB北京九华山庄2008年会纪念徽章蓝锆石海蓝宝石
会员2007贡献徽章2008北京奥运纪念徽章:沙滩排球2008年新春纪念徽章每日论坛发贴之星  

发表于 2008-3-17 13:24 
这样如何?

select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category||' '||a.site_id  = b.from_cat||' '||b.site_id
   or a.category2||' '||a.site_id  = b.from_cat||' '||b.site_id
) and a.sale_end >= sysdate;


__________________
提问的智慧Oracle版
0。尝试在google,论坛,metalink,online document里搜索。
1。写清楚你的执行log,报错信息,写清楚DB version , OS
2。Instance 方面的问题,请贴出alertlog
3。network的问题,贴出server的listener.ora , sqlnet.ora 并运行lsnrctl service, 贴出client的tnsnames.ora , sqlnet.ora ,并运行tnsping
4。DB总体性能问题,请于peak time做statspack,并上传statspack report。同时附上CPU/MEM配置,以及CPU IDLE/free menory的情况
5。SQL性能问题,列出当前SQL,以及PLAN。同时应提供表结构,index情况,Table的record数。CBO/RBO,table是否analyze等信息。
6。确保你的问题是Oracle相关。
提问的智慧 for Oracle[展开版]
只看该作者    顶部
离线 xzh2000
仙人抚我须 结发授长生



精华贴数 13
个人空间 0
技术积分 46382 (14)
社区积分 5113 (276)
注册日期 2002-7-17
论坛徽章:29
现任管理团队成员ITPUB元老授权会员2008北京奥运纪念徽章:柔道2008北京奥运纪念徽章:帆船生肖徽章2007版:虎
ITPUB新首页上线纪念徽章数据库板块每日发贴之星数据库板块每日发贴之星数据库板块每日发贴之星数据库板块每日发贴之星数据库板块每日发贴之星

发表于 2008-3-17 13:27 
不错,解析的很透彻...


__________________
过目即忘  插柳成荫
只看该作者    顶部
离线 zuohao_lu
haorman



精华贴数 0
个人空间 830
技术积分 3658 (385)
社区积分 5737 (257)
注册日期 2007-11-13
论坛徽章:22
奥运纪念徽章2008北京奥运纪念徽章:篮球生肖徽章2007版:鼠   
      

发表于 2008-3-17 13:48 
多谢楼主分享...
认真看看...


只看该作者    顶部
相关内容


CopyRight 1999-2006 itpub.net All Right Reserved.
北京皓辰广域网络信息技术有限公司. 版权所有
E-mail:Webmaster@itpub.net
京ICP证:010037号 联系我们 法律顾问