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

1
2
3
4
5
6
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--------------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |     2 |   174 |    18  (17)|
|   1 |  SORT UNIQUE         |              |     2 |   174 |    18  (17)|
|*  2 |   HASH JOIN          |              |     2 |   174 |    17  (12)|
|   3 |    TABLE ACCESS FULL | SMALL_TABLE  |  1879 | 48854 |    14   (8)|
|*  4 |    TABLE ACCESS FULL | BIG_TABLE    |     4 |   244 |     3  (34)|
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("A"."SITE_ID"="B"."SITE_ID")
       filter("A"."CATEGORY"="B"."FROM_CAT" OR
              "A"."CATEGORY2"="B"."FROM_CAT")
   4 - filter("A"."SALE_END">=SYSDATE@!)

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

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

那么问题究竟出在哪里?

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

1
alter session set events '10104 trace name context forever, level 2';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
### 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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中这两行很相符:

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

注:这判断过程可以从执行计划的“Predicate Information”部分看出:

1
access("A"."SITE_ID"="B"."SITE_ID")

所以推断这个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改写:

1
2
3
4
5
6
7
8
9
10
11
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--------------------------------------------------------------------------
| Id  | Operation            |  Name        | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |              |     2 |   148 |    36  (59)|
|   1 |  SORT UNIQUE         |              |     2 |   148 |    36  (59)|
|   2 |   UNION-ALL          |              |       |       |            |
|*  3 |    HASH JOIN         |              |     1 |    74 |    17  (12)|
|   4 |     TABLE ACCESS FULL| SMALL_TABLE  |  1879 | 48854 |    14   (8)|
|*  5 |     TABLE ACCESS FULL| BIG_TABLE    |     4 |   192 |     3  (34)|
|*  6 |    HASH JOIN         |              |     1 |    74 |    17  (12)|
|   7 |     TABLE ACCESS FULL| SMALL_TABLE  |  1879 | 48854 |    14   (8)|
|*  8 |     TABLE ACCESS FULL| BIG_TABLE    |     4 |   192 |     3  (34)|
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("A"."CATEGORY"="B"."FROM_CAT" AND
              "A"."SITE_ID"="B"."SITE_ID")
   5 - filter("A"."SALE_END">=SYSDATE@!)
   6 - access("A"."CATEGORY2"="B"."FROM_CAT" AND
              "A"."SITE_ID"="B"."SITE_ID")
   8 - filter("A"."SALE_END">=SYSDATE@!)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
### 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的机制,进行更深一步的分析。

source:http://www.itpub.net/thread-955209-1-1.html

oralce之 10046对Hash Join分析的更多相关文章

  1. minhash pyspark 源码分析——hash join table是关键

    从下面分析可以看出,是先做了hash计算,然后使用hash join table来讲hash值相等的数据合并在一起.然后再使用udf计算距离,最后再filter出满足阈值的数据: 参考:https:/ ...

  2. Merge join、Hash join、Nested loop join对比分析

    简介 我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge Join,Hash Join ...

  3. SQL Tuning 基础概述06 - 表的关联方式:Nested Loops Join,Merge Sort Join & Hash Join

    nested loops join(嵌套循环)   驱动表返回几条结果集,被驱动表访问多少次,有驱动顺序,无须排序,无任何限制. 驱动表限制条件有索引,被驱动表连接条件有索引. hints:use_n ...

  4. Sort merge join、Nested loops、Hash join(三种连接类型)

    目前为止,典型的连接类型有3种: Sort merge join(SMJ排序-合并连接):首先生产driving table需要的数据,然后对这些数据按照连接操作关联列进行排序:然后生产probed ...

  5. 视图合并、hash join连接列数据分布不均匀引发的惨案

    表大小 SQL> select count(*) from agent.TB_AGENT_INFO; COUNT(*) ---------- 1751 SQL> select count( ...

  6. Oracle 表的连接方式(2)-----HASH JOIN的基本机制1

    我们对hash join的常见误解,一般包括两个: 第一个误解:是我们经常以为hash join需要对两个做join的表都做全表扫描 第二个误解:是经常以为hash join会选择比较小的表做buil ...

  7. oracle 表连接 - hash join 哈希连接

    一. hash 连接(哈希连接)原理 指的是两个表连接时, 先利用两表中记录较少的表在内存中建立 hash 表, 然后扫描记录较多的表并探測 hash 表, 找出与 hash 表相匹配的行来得到结果集 ...

  8. [20180713]关于hash join 测试中一个疑问.txt

    [20180713]关于hash join 测试中一个疑问.txt --//上个星期做的测试,链接: http://blog.itpub.net/267265/viewspace-2157424/-- ...

  9. [20180705]关于hash join 2.txt

    [20180705]关于hash join 2.txt --//昨天优化sql语句,执行计划hash join right sna,加入一个约束设置XX字段not null,逻辑读从上万下降到50.- ...

随机推荐

  1. oracle 11g各种下载地址

    Oracle Database 11g Release 2 Standard Edition and Enterprise Edition Software Downloadsoracle 数据库 1 ...

  2. 1-23-shell脚本之-if流程控制语句和for循环语句的使用

    大纲: 1.逻辑判断 2.if流程控制语句 3.for循环控制语句   ---------------------------------------------- 在开始之前,先了解一下逻辑判断符号 ...

  3. neutron源码分析(一)OpenStack环境搭建

    一.OpenStack安装 安装一个初始化的Mitaka版本的OpenStack环境用于分析,neutron源码 序号 角色 IP地址 版本 1 controller 172.16.15.161 mi ...

  4. echarts在vue中使用的感悟

    echarts在vue中使用的感悟 echarts作为图表展示的强大存在,每当使用后台系统,或多或少都会使用到,但是作为菜鸟的我,则是一路采坑,各种头大,比比皆是,为了避免下次再犯同样的错误,特意记录 ...

  5. 【zznu-夏季队内积分赛3-J】追忆

    题目描述 “别人总说我瓜,其实我一点也不瓜,大多数时候我都机智的一批“现在是阳历2018/8/7,宝儿姐想起自己参加ACM整整1000天了.她想知道她刚入坑是什么时间.那么问题来了,请帮宝儿姐追忆一下 ...

  6. 【Wannafly挑战赛9-A】找一找

    链接:https://www.nowcoder.net/acm/contest/71/A 题目描述 给定n个正整数,请找出其中有多少个数x满足:在这n个数中存在数y=kx,其中k为大于1的整数 输入描 ...

  7. Ubuntu 13.10 如何修改背景色--豆沙绿

    如何修改Ubunut 13.10的窗口背景色,在网上找到了多资料,安装了dconf,Unity Tweak Tool,GNOME Color Chooser都无法修改背景色. 最后,还是直接去修改主题 ...

  8. Laravel 文件夹结构简介

    表 1.1:Laravel 文件夹结构简介 文件夹名称 简介 app 应用程序的业务逻辑代码存放文件夹 app/Console 存放自定义 Artisian 命令文件 app/Http/Control ...

  9. 利用 squid 反向代理提高网站性能(转载)

    本文在介绍 squid 反向代理的工作原理的基础上,指出反向代理技术在提高网站访问速度,增强网站可用性.安全性方面有很好的用途.作者在具体的实验环境下,利用 DNS 轮询和 Squid 反向代理技术, ...

  10. asp.net button浏览器端事件和服务器端事件

    OnClientClick:触发浏览器端的响应,OnClick触发服务器端响应; 在服务器aspx.cs脚本中设置按钮属性: this.btnTest.Attributes["OnClick ...