oralce之 10046对Hash Join分析
前两天解决了一个优化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.IDfrom BIG_TABLE a, SMALL_TABLE bwhere (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: 16373Number of buckets with 1 rows: 0Number of buckets with 2 rows: 0Number of buckets with 3 rows: 1Number of buckets with 4 rows: 0Number of buckets with 5 rows: 0Number of buckets with 6 rows: 0Number of buckets with 7 rows: 1Number of buckets with 8 rows: 0Number of buckets with 9 rows: 0Number of buckets with between 10 and 19 rows: 1Number of buckets with between 20 and 29 rows: 1Number of buckets with between 30 and 39 rows: 3Number of buckets with between 40 and 49 rows: 0Number of buckets with between 50 and 59 rows: 0Number of buckets with between 60 and 69 rows: 0Number of buckets with between 70 and 79 rows: 0Number of buckets with between 80 and 89 rows: 0Number of buckets with between 90 and 99 rows: 0Number of buckets with 100 or more rows: 4### Hash table overall statistics ###Total buckets: 16384 Empty buckets: 16373 Non-empty buckets: 11Total number of rows: 9232Maximum number of rows in a bucket: 2531Average 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 selected2. Build hash table based on (from_cat):SQL> select from_cat,count(*) from SMALL_TABLE group by from_cat having count(*)>100;no rows selected3. 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: 4Maximum 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.IDfrom BIG_TABLE a, SMALL_TABLE bwhere a.category = b.from_cat and a.site_id = b.site_id and a.sale_end >= sysdateUNIONselect /*+ leading(b) use_hash(a b) */ distinct a.IDfrom BIG_TABLE a, SMALL_TABLE bwhere 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: 9306Number of buckets with 1 rows: 5310Number of buckets with 2 rows: 1436Number of buckets with 3 rows: 285Number of buckets with 4 rows: 43Number of buckets with 5 rows: 4Number of buckets with 6 rows: 0Number of buckets with 7 rows: 0Number of buckets with 8 rows: 0Number of buckets with 9 rows: 0Number of buckets with between 10 and 19 rows: 0Number of buckets with between 20 and 29 rows: 0Number of buckets with between 30 and 39 rows: 0Number of buckets with between 40 and 49 rows: 0Number of buckets with between 50 and 59 rows: 0Number of buckets with between 60 and 69 rows: 0Number of buckets with between 70 and 79 rows: 0Number of buckets with between 80 and 89 rows: 0Number of buckets with between 90 and 99 rows: 0Number of buckets with 100 or more rows: 0### Hash table overall statistics ###Total buckets: 16384 Empty buckets: 9306 Non-empty buckets: 7078Total number of rows: 9232Maximum number of rows in a bucket: 5Average 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分析的更多相关文章
- minhash pyspark 源码分析——hash join table是关键
从下面分析可以看出,是先做了hash计算,然后使用hash join table来讲hash值相等的数据合并在一起.然后再使用udf计算距离,最后再filter出满足阈值的数据: 参考:https:/ ...
- Merge join、Hash join、Nested loop join对比分析
简介 我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge Join,Hash Join ...
- SQL Tuning 基础概述06 - 表的关联方式:Nested Loops Join,Merge Sort Join & Hash Join
nested loops join(嵌套循环) 驱动表返回几条结果集,被驱动表访问多少次,有驱动顺序,无须排序,无任何限制. 驱动表限制条件有索引,被驱动表连接条件有索引. hints:use_n ...
- Sort merge join、Nested loops、Hash join(三种连接类型)
目前为止,典型的连接类型有3种: Sort merge join(SMJ排序-合并连接):首先生产driving table需要的数据,然后对这些数据按照连接操作关联列进行排序:然后生产probed ...
- 视图合并、hash join连接列数据分布不均匀引发的惨案
表大小 SQL> select count(*) from agent.TB_AGENT_INFO; COUNT(*) ---------- 1751 SQL> select count( ...
- Oracle 表的连接方式(2)-----HASH JOIN的基本机制1
我们对hash join的常见误解,一般包括两个: 第一个误解:是我们经常以为hash join需要对两个做join的表都做全表扫描 第二个误解:是经常以为hash join会选择比较小的表做buil ...
- oracle 表连接 - hash join 哈希连接
一. hash 连接(哈希连接)原理 指的是两个表连接时, 先利用两表中记录较少的表在内存中建立 hash 表, 然后扫描记录较多的表并探測 hash 表, 找出与 hash 表相匹配的行来得到结果集 ...
- [20180713]关于hash join 测试中一个疑问.txt
[20180713]关于hash join 测试中一个疑问.txt --//上个星期做的测试,链接: http://blog.itpub.net/267265/viewspace-2157424/-- ...
- [20180705]关于hash join 2.txt
[20180705]关于hash join 2.txt --//昨天优化sql语句,执行计划hash join right sna,加入一个约束设置XX字段not null,逻辑读从上万下降到50.- ...
随机推荐
- 2-4-搭建DHCP服务实现动态分配IP地址-NTP网络时间同步
本节所讲内容: •DHCP服务器工作原理 •使用DHCP为局域网中的机器分配IP地址 •使用DHCP为服务器分配固定IP地址 •ntpdate加计划任务同步服务器时间 ---------------- ...
- cat 命令|more命令|less命令
cat主要有三大功能:1.一次显示整个文件:cat [-n] filename2.从键盘创建一个文件:cat > filename 3.将几个文件合并为一个文件:cat file1 file2 ...
- 【BZOJ】3389: [Usaco2004 Dec]Cleaning Shifts安排值班(贪心)
http://www.lydsy.com/JudgeOnline/problem.php?id=3389 显然左端点排序后,依次取. 要考虑下一次取的方案: 待选点为a[j].x<=a[now] ...
- lambda表达式 <二>
概念了解: 1.什么是匿名委托(匿名方法的简单介绍.为什么要用匿名方法) 2.匿名方法的[拉姆达表达式]方法定义 3.匿名方法的调用(匿名方法的参数传递.使用过程中需要注意什么) 什么是匿名方法? 匿 ...
- hdu2853
题解: KM算法模板 然后我把另一边加了点 然后写了#define int long long 然后莫名挂... 然后去掉就过了 代码: #include<cstdio> #include ...
- python3 快速排序
思路 第一步:找到一个随机的数,一般都是第一个数,也就是left,递归中也用left,放到缓存中,专业叫 基准值,基准值是要放在中间的. 第二步:最左边空出一个位置就是索引left的位置,所以从右向左 ...
- 用国内镜像源pip加速安装模块
记住,如果使用了virtualenv,一定要先workon进入虚拟环境再执行包安装命令. pip install -i https://pypi.douban.com/simple/ 模块名(如:dj ...
- Week12《java程序设计》第12次作业总结
Week12<java程序设计>第12次作业总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 面向系统综合设计-图书馆管理系统或购物车 ...
- 在微信里面打开链接,显示501 Not Implemented,但是同样的链接在其他浏览器是可以打开的。
在微信里面打开链接,显示501 Not Implemented,但是同样的链接在其他浏览器是可以打开的. 显示: 还原:该链接在2017年之前微信还是可以访问的. 访问的地址格式是:http://xx ...
- JSP里的<c:if>不起作用[待解答]
JSP页面的部分代码如下: 下面的title作为请求参数,shoppingCart作为session范围域的属性. 问题1: 如果去掉<c:if>的判断条件,第一行打印:可以正常显示出来, ...