(Oracle)SQL优化案例:大表hash连接优化

真实SQL优化案例

为避免项目隐私泄露;

本篇文章所有表名、字段名,包括执行计划内的对象名称都做了处理。 

本篇文章是将速度再10秒左右的SQL优化到1.5s左右;

因为没有优化到1s以下,所以可能还存在更优优化方法;

但其中涉及的优化技巧,可以供您赏析。

目录

项目场景

SQL分析

优化方案

优化总结


项目场景

甲方反应如下SQL执行缓慢,需要10秒左右才能执行完。

SELECT 
COUNT(1) AS CNT
FROM LA 
LEFT JOIN  IMO ON LA.ID = IMO.ID
WHERE IMO.SOURCE_ID IS NULL 
AND IMO.STATUS_ID NOT IN ('080','085')

该SQL的查询结果是 2498900 。 

SQL分析

  • SQL本身逻辑分析

Ⅰ:大表 

上述SQL的查询结果是 2498900 ;说明两张表本身是大表。经过如下查询,确实是两张大表,LA大小是0.9G,IMO大小是2.16G。

也就是说,这是两张大表进行关联查询。

--0.9G
select 
round(ds.bytes/1024/1024/1024,2) as tablesize,
ds.*
from dba_segments ds where segment_name = 'LA'

--2.16G
select 
round(ds.bytes/1024/1024/1024,2) as tablesize,
ds.*
from dba_segments ds where segment_name = 'IMO'

Ⅱ:WHERE条件

WHERE条件中只有两个谓词字段:

IMO.SOURCE_ID IS NULL 、IMO.STATUS_ID,这两个其实在业务逻辑上只是作废状态判断。也就是说过滤掉的数据不会超过总数据量的20%,相当于会统计LA表的80%数据

IMO.STATUS_ID NOT IN ('080','085'),这个条件使用了NOT IN,也就意味着即使在IMO.STATUS_ID 字段上建立了索引,也不会再走索引了

  • 执行计划分析

以下是上述SQL的执行计划,已剔除无关部分。 

Plan hash value: 3381251447
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                               | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                                    |      1 |        |       |       | 30538 (100)|          |      1 |00:00:10.03 |   73476 |       |       |          |
|   1 |  SORT AGGREGATE        |                                    |      1 |      1 |    27 |       |            |          |      1 |00:00:10.03 |   73476 |       |       |          |
|*  2 |   HASH JOIN            |                                    |      1 |   5265K|   135M|    67M| 30538   (1)| 00:00:02 |   2498K|00:00:09.59 |   73476 |   148M|    17M|  161M (0)|
|   3 |    INDEX FAST FULL SCAN| UK_20230901210804_1007994          |      1 |   2608K|    37M|       |  2770   (1)| 00:00:01 |   2625K|00:00:00.80 |   11138 |       |       |          |
|*  4 |    INDEX FAST FULL SCAN| IDX_IMO_3                          |      1 |   6647K|    76M|       | 16837   (1)| 00:00:01 |   6162K|00:00:03.13 |   62338 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("LA"."ID"="IMO"."ID")
   4 - filter(("IMO"."ID" IS NOT NULL AND "IMO"."SOURCE_ID" IS NULL AND "IMO"."STATUS_ID"<>'080' AND "IMO"."STATUS_ID"<>'085'))

id=1(SORT AGGREGATE )

因为上述SQL中有COUNT(),所以 出现了 SORT AGGREGATE 。SORT AGGREGATE并不做真正的排序,不会用到排序空间。所以上述SQL的真正性能问题不在此处。

id=2(HASH JOIN)

两张大表关联,且需要查询大量数据时,需要走hash连接。结合上面的分析,所以此处执行计划,我认为是没有问题的。

id=3 与 id=4

这里大家看不到索引对应的表,这个 UK_20230901210804_1007994 其实是LA表的索引,上文表述过了,LA表的大小小于IMO表,所以理论上LA表应该是hash连接中的驱动表。执行计划中离hash关键字最近的表就是驱动表,所以这里的驱动表没有问题。

且我们可以发现 Used-Mem是161M,说明hash连接消耗了PGA 161M的内存。

根据这个161M需要再进一步和大家分享说明一下hash连接的算法:两表等值关联,返回大量数据,将较小的表作为驱动表。将驱动表的select字段和连接字段读入PGA中,然后对驱动表的连接字段进行hash运算生成hash table;当驱动表的所有数据都读入PGA后,再读取被驱动表,对被驱动表的连接列也进行hash运算;然后在PGA中探测hash table,找到数据就关联上。

所以这就解释了两个大小过G的表只消耗了161M的PGA

除了驱动表与PGA大小,我们可以看到两张表都走了索引。索引扫描是单块读,全表扫描是多块读,读取大量数据时,选择全表扫描更合适,且全表扫描也不会发生回表操作。但因为SELECT 字段只是一个count计数,Oracle的CBO优化器算法可能认为数据量还不够大,且此时也不需要回表,所以走了索引扫描,我认为问题也不是很大。

综上所述,我认为执行计划本身不存在什么太大问题,那怎么优化呢?

  •  调优技巧:并行查询

这里有一个查询调优技巧:开启并行查询。

启用并行查询,说的比较白话一点就是将hash运算拆成n份。

例如对本文SQL启用10个并行查询,LA表会根据连接列进行hash运算然后拆成10份:LA1、LA2、....... LA10;IMO表也会根据连接列进行hash运算然后拆成10份:IMO1、IMO2、..... IMO10。相当于改写成如下SQL:

SELECT 
COUNT(1) AS CNT
FROM LA1 
LEFT JOIN  IMO1 ON LA1.ID = IMO1.ID
WHERE IMO1.SOURCE_ID IS NULL 
AND IMO1.STATUS_ID NOT IN ('080','085')

UNION ALL

SELECT 
COUNT(1) AS CNT
FROM LA2 
LEFT JOIN  IMO2 ON LA2.ID = IMO2.ID
WHERE IMO2.SOURCE_ID IS NULL 
AND IMO2.STATUS_ID NOT IN ('080','085')

......

UNION ALL

SELECT 
COUNT(1) AS CNT
FROM LA10 
LEFT JOIN  IMO10 ON LA10.ID = IMO10.ID
WHERE IMO10.SOURCE_ID IS NULL 
AND IMO10.STATUS_ID NOT IN ('080','085')

优化方案

那么如何开启并行查询呢?这就需要写HINT,如下代码所示。

SELECT 
/*+ parallel(10) use_hash(LA,IMO) pq_distribute(IMO hash,hash)*/
COUNT(1) AS CNT
FROM LA 
LEFT JOIN  IMO ON LA.ID = IMO.ID
WHERE IMO.SOURCE_ID IS NULL 
AND IMO.STATUS_ID NOT IN ('080','085')

我把HINT单独摘出来,这里添加的hint是pq_distribute(被驱动表 hash hash) ;

其中use_hash(驱动表,被驱动表)的用法是走hash连接,LA是驱动表,IMO是被驱动表,顺序不要错。

/*+ parallel(10) use_hash(LA,IMO) pq_distribute(IMO hash,hash)*/

 此时执行完上述SQL,需要1.5秒左右。速度提升了几倍。

下面是优化后SQL的执行计划

大家可以看到各种资源消耗和ATIME都下降非常多。

SQL_ID  9uwrm2pp44xvp, child number 0
-------------------------------------
SELECT /*+ parallel(10) use_hash(LA,IMO) pq_distribute(IMO hash,hash)*/ 
COUNT(1) CNT FROM INPORD.LAB_APPLY LA LEFT JOIN INPORD.MEDICAL_ORDER 
IMO ON LA.LAB_APPLY_FLOW = IMO.RELATION_KEY WHERE IMO.ORDER_SOURCE_CODE 
IS NULL AND IMO.ORDER_STATUS NOT IN ('080','085')
 
Plan hash value: 4058815446
 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name                               | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   |    TQ  |IN-OUT| PQ Distrib | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                                    |      1 |        |       |  2182 (100)|          |        |      |            |      1 |00:00:00.66 |      24 |       |       |          |
|   1 |  SORT AGGREGATE                  |                                    |      1 |      1 |    27 |            |          |        |      |            |      1 |00:00:00.66 |      24 |       |       |          |
|   2 |   PX COORDINATOR                 |                                    |      1 |        |       |            |          |        |      |            |     10 |00:00:00.66 |      24 | 73728 | 73728 |          |
|   3 |    PX SEND QC (RANDOM)           | :TQ10002                           |      0 |      1 |    27 |            |          |  Q1,02 | P->S | QC (RAND)  |      0 |00:00:00.01 |       0 |       |       |          |
|   4 |     SORT AGGREGATE               |                                    |      0 |      1 |    27 |            |          |  Q1,02 | PCWP |            |      0 |00:00:00.01 |       0 |       |       |          |
|*  5 |      HASH JOIN                   |                                    |      0 |   4917K|   126M|  2182   (1)| 00:00:01 |  Q1,02 | PCWP |            |      0 |00:00:00.01 |       0 |   148M|    17M|   15M (0)|
|   6 |       PX RECEIVE                 |                                    |      0 |   2608K|    37M|   308   (1)| 00:00:01 |  Q1,02 | PCWP |            |      0 |00:00:00.01 |       0 |       |       |          |
|   7 |        PX SEND HYBRID HASH       | :TQ10000                           |      0 |   2608K|    37M|   308   (1)| 00:00:01 |  Q1,00 | P->P | HYBRID HASH|      0 |00:00:00.01 |       0 |       |       |          |
|   8 |         STATISTICS COLLECTOR     |                                    |      0 |        |       |            |          |  Q1,00 | PCWC |            |      0 |00:00:00.01 |       0 |       |       |          |
|   9 |          PX BLOCK ITERATOR       |                                    |      0 |   2608K|    37M|   308   (1)| 00:00:01 |  Q1,00 | PCWC |            |      0 |00:00:00.01 |       0 |       |       |          |
|* 10 |           INDEX FAST FULL SCAN   | UK_20230901210804_1007994          |      0 |   2608K|    37M|   308   (1)| 00:00:01 |  Q1,00 | PCWP |            |      0 |00:00:00.01 |       0 |       |       |          |
|  11 |       PX RECEIVE                 |                                    |      0 |   6207K|    71M|  1871   (1)| 00:00:01 |  Q1,02 | PCWP |            |      0 |00:00:00.01 |       0 |       |       |          |
|  12 |        PX SEND HYBRID HASH (SKEW)| :TQ10001                           |      0 |   6207K|    71M|  1871   (1)| 00:00:01 |  Q1,01 | P->P | HYBRID HASH|      0 |00:00:00.01 |       0 |       |       |          |
|  13 |         PX BLOCK ITERATOR        |                                    |      0 |   6207K|    71M|  1871   (1)| 00:00:01 |  Q1,01 | PCWC |            |      0 |00:00:00.01 |       0 |       |       |          |
|* 14 |          INDEX FAST FULL SCAN    | IDX_IMO_3                          |      0 |   6207K|    71M|  1871   (1)| 00:00:01 |  Q1,01 | PCWP |            |      0 |00:00:00.01 |       0 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

Predicate Information (identified by operation id):
---------------------------------------------------
 
   5 - access("LA"."ID"="IMO"."ID")
  10 - access(:Z>=:Z AND :Z<=:Z)
  14 - access(:Z>=:Z AND :Z<=:Z)
       filter(("IMO"."ID" IS NOT NULL AND "IMO"."SOURCE_ID" IS NULL AND "IMO"."STATUS_ID"<>'080' AND "IMO"."STATUS_ID"<>'085'))

 

优化总结

/*+ parallel(n) use_hash(LA,IMO) pq_distribute(IMO hash,hash)*/

parallel(n)中的这个n并不是越大就越好。

这种并行查询方法只会在表连接查询消耗PGA大小合适的时候才能发挥最大作用,

它其实对于几十G这种远远超过PGA大小的表连接时,就不好使了;对于这种情况还有其他优化方法。

这个SQL应该还有其他优化方法,毕竟我只优化到了1.5s左右。

但我现在还是太愚笨了,只能想到这个方法优化。

如果后续我找到其他优化方法,会再和大家分享。

谢谢您的阅读!

  • 55
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第1章 全局在胸——用工具对SQL整体优化 1 1.1 都有哪些性能工具 1 1.1.1 不同调优场景分析 2 1.1.2 不同场景对应工具 2 1.2 整体性能工具的要点 4 1.2.1 五大性能报告的获取 5 1.2.2 五大报告关注的要点 10 1.3 案例的分享与交流 18 1.3.1 和并行等待有关的案例 18 1.3.2 和热块竞争有关的案例 19 1.3.3 和日志等待有关的案例 20 1.3.4 新疆某系统的前台优化 20 1.3.5 浙江某系统的调优案例 21 1.4 本章总结延伸与习题 21 1.4.1 总结延伸 21 1.4.2 习题训练 23 第2章 风驰电掣——有效缩短SQL优化过程 24 2.1 SQL调优时间都去哪儿了 25 2.1.1 不善于批处理频频忙交互 25 2.1.2 无法抓住主要矛盾瞎折腾 25 2.1.3 未能明确需求目标白费劲 26 2.1.4 没有分析操作难度乱调优 26 2.2 如何缩短SQL调优时间 27 2.2.1 先获取有助调优的数据库整体信息 27 2.2.2 快速获取SQL运行台前信息 27 2.2.3 快速拿到SQL关联幕后信息 28 2.3 从案例看快速SQL调优 29 2.3.1 获取数据库整体的运行情况 29 2.3.2 获取SQL的各种详细信息 29 2.4 本章总结延伸与习题 32 2.4.1 总结延伸 32 2.4.2 习题训练 33 第3章 循规蹈矩——如何读懂SQL执行计划 34 3.1 执行计划分析概述 35 3.1.1 SQL执行计划是什么 35 3.1.2 统计信息用来做什么 36 3.1.3 数据库统计信息的收集 37 3.1.4 数据库的动态采样 37 3.1.5 获取执行计划的方法(6种武器) 40 3.2 读懂执行计划的关键 48 3.2.1 解释经典执行计划方法 49 3.2.2 总结说明 55 3.3 从案例辨别低效SQL 55 3.3.1 从执行计划读出效率 56 3.3.2 执行计划效率总结 60 3.4 本章习题、总结与延伸 60 第4章 运筹帷幄——左右SQL执行计划妙招 62 4.1 控制执行计划的方法综述 63 4.1.1 控制执行计划的意义 63 4.1.2 控制执行计划的思路 64 4.2 从案例探索其方法及意义 65 4.2.1 HINT的思路 65 4.2.2 非HINT方式的执行计划改变 72 4.2.3 执行计划的固定 100 4.3 本章习题、总结与延伸 102 第5章 且慢,感受体系结构让SQL飞 103 5.1 体系结构知识 104 5.1.1 组成 104 5.1.2 原理 104 5.1.3 体会 105 5.2 体系与SQL优化 106 5.2.1 与共享池相关 107 5.2.2 数据缓冲相关 111 5.2.3 日志归档相关 116 5.3 扩展优化案例 118 5.3.1 与共享池相关 118 5.3.2 数据缓冲相关 122 5.3.3 日志归档相关 126 5.4 本章习题、总结与延伸 130 第6章 且慢,体验逻辑结构让SQL飞 132 6.1 逻辑结构 132 6.2 体系细节与SQL优化 133 6.2.1 Block 133 6.2.2 Segment与extent 137 6.2.3 Tablespace 139 6.2.4 rowid 139 6.3 相关优化案例分析 140 6.3.1 块的相关案例 141 6.3.2 段的相关案例 144 6.3.3 空间的案例 148 6.3.4 rowid 151 6.4 本章习题、总结与延伸 153 第7章 且慢,探寻的设计让SQL飞 154 7.1 设计 154 7.1.1 的设计 155 7.1.2 其他补充 155 7.2 设计与SQL优化 156 7.2.1 的设计 156 7.2.2 其他补充 179 7.3 相关优化案例分析 184 7.3.1 分区相关案例 185 7.3.2 全局临时案例 190 7.3.3 监控异常的设计 195 7.3.4 设计优化相关案例总结 199 7.4 本章习题、总结与延伸 199 第8章 且慢,学习索引如何让SQL飞 200 8.1 索引知识要点概述 201 8.1.1 索引结构的推理 201 8.1.2 索引特性的提炼 204 8.2 索引的SQL优化 206 8.2.1 经典三大特性 207 8.2.2 组合索引选用 217 8.2.3 索引扫描类型的分类与构造 219 8.3 索引相关优化案例 225 8.3.1 三大特性的相关案例 225 8.3.2 组合索引的经典案例 231 8.4 本章习题、总结与延伸 234 第9章 且慢,弄清索引之阻碍让SQL飞 23
Oracle SQL优化是指通过调整SQL查询语句和数据库结构,以提高查询性能和响应时间的过程。下面是一些常用的Oracle SQL优化技巧: 1. 使用合适的索引:确保中的列上有适当的索引,以加快查询速度。可以使用EXPLAIN PLAN或者SQL Tuning Advisor来分析查询计划,并建议适当的索引。 2. 编写高效的查询语句:尽量避免使用SELECT *,只选择需要的列;使用合适的JOIN语句,避免不必要的连接;使用WHERE子句限制返回的行数。 3. 使用合适的数据类型:选择合适的数据类型可以减少存储空间和加速查询。例如,使用整数类型代替字符类型存储数字数据。 4. 避免使用函数和达式:在WHERE子句中使用函数或达式会导致索引失效,影响查询性能。尽量将函数和达式移到SELECT列之外。 5. 分析统计信息:确保收集和更新和索引的统计信息,以便优化查询计划的生成。可以使用DBMS_STATS包来收集统计信息。 6. 优化连接操作:对于复杂的连接操作,可以考虑使用合适的连接方式(如HASH JOIN、NESTED LOOP等),以及使用连接池和缓存来提高性能。 7. 使用分区:对于大型,可以考虑使用分区来提高查询性能。分区可以根据特定的列值将数据划分为更小的块,使查询更加高效。 8. 避免全扫描:尽量避免全扫描操作,可以通过合理使用索引、分区等技术来避免全扫描,以提高查询性能。 9. 使用Hint提示:可以使用查询提示(Hint)来指导优化器生成最优的执行计划。但是需要谨慎使用,因为错误的Hint可能导致性能下降。 10. 监视和调整数据库参数:根据实际情况,监视和调整数据库参数,以优化整体数据库性能。 综上所述,Oracle SQL优化需要综合考虑查询语句、索引、结构、统计信息等多个方面的因素。通过合理的优化策略,可以提高查询性能和响应时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜豆豆耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值