(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左右。

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

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

谢谢您的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/583888.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)续-音质问题解决总结

一、前言 之前进行了STM32CubeMXMDK通过I2S接口进行音频输入输出&#xff08;全双工读写一个DMA回调&#xff09;的研究总结&#xff1a; https://juejin.cn/post/7339016190612881408#heading-34 后续音质问题解决了&#xff0c;目前测试下来48khz的双声道使用效果很好&…

基于uniapp+微信小程序的智能停车场管理小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

护航智慧交通安全 | 聚铭精彩亮相2024交通科技创新及信创产品推广交流会

4月26日&#xff0c;石家庄希尔顿酒店内&#xff0c;河北省智能交通协会盛大举办2024年度交通科技创新及信创产品推广交流会。聚铭网络受邀参与&#xff0c;携旗下安全产品及解决方案精彩亮相&#xff0c;为智慧交通安全保驾护航。 为深化高速公路创新驱动发展战略&#xff0…

Java网址url工具类

功能描述 无需引入三方依赖文本匹配网址&#xff08;支持多个&#xff09;网址解析&#xff08;包括协议、主机、路径、参数等&#xff09; package com.qiangesoft.image.utils;import org.springframework.util.Assert; import org.springframework.util.CollectionUtils;i…

深度学习基础之《TensorFlow框架(16)—神经网络案例》

一、mnist手写数字识别 1、数据集介绍 mnist数据集是一个经典的数据集&#xff0c;其中包括70000个样本&#xff0c;包括60000个训练样本和10000个测试样本 2、下载地址&#xff1a;http://yann.lecun.com/exdb/mnist/ 3、文件说明 train-images-idx3-ubyte.gz: training s…

C#编程模式之装饰模式

创作背景&#xff1a;朋友们&#xff0c;我们继续C#编程模式的学习&#xff0c;本文我们将一起探讨装饰模式。装饰模式也是一种结构型设计模式&#xff0c;它允许你通过在运行时向对象添加额外的功能&#xff0c;从而动态的修改对象的行为。装饰模式本质上还是继承的一种替换方…

分享三款可以给pdf做批注的软件

PDF文件不像Word一样可以直接编辑更改&#xff0c;想要在PDF文件上进行编辑批注需要用到一些专业的软件&#xff0c;我自己常用的有三款&#xff0c;全都是官方专业正版的软件&#xff0c;功能丰富强大&#xff0c;使用起来非常方便&#xff01; 1.edge浏览器 这个浏览器不仅可…

爬虫实战-房天下(bengbu.zu.fang.com/)数据爬取

详细代码链接https://flowus.cn/hbzx/3c42674d-8e6f-42e3-a3f6-bc1258034676 import requests from lxml import etree #xpath解析库 def 源代码(url): cookies { global_cookie: xeqnmumh38dvpj96uzseftwdr20lvkwkfb9, otherid: b44a1837638234f1a0a15e…

JavaEE 初阶篇-深入了解特殊文件(Properties 属性文件、XML)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Properties 属性文件概述 1.1 Properties 属性文件特性与作用 1.2 使用 Properties 把键值对数据写出到属性文件中 1.3 使用 Properties 读取属性文件里的键值对数…

《动手学深度学习(Pytorch版)》Task03:线性神经网络——4.29打卡

《动手学深度学习&#xff08;Pytorch版&#xff09;》Task03&#xff1a;线性神经网络 线性回归基本元素线性模型损失函数随机梯度下降 正态分布与平方损失 线性回归的从零开始实现读取数据集初始化模型参数定义模型定义损失函数定义优化算法训练 线性回归的简洁实现读取数据集…

c#创建新项目

确保已安装.NET Core SDK。&#xff08;visual studio installer中可安装&#xff09; cmd中先引用到文件夹目录下。 mkdir MyConsoleApp MyConsoleApp是项目文件夹的名字。 mkdir 是一个命令行工具&#xff0c;用于在文件系统中创建新的目录&#xff08;文件夹&#xff09;…

用OpenCV先去除边框线,以提升OCR准确率

在OpenCV的魔力下&#xff0c;我们如魔法师般巧妙地抹去表格的边框线&#xff0c;让文字如诗如画地跃然纸上。 首先&#xff0c;我们挥动魔杖&#xff0c;将五彩斑斓的图像转化为单一的灰度世界&#xff0c;如同将一幅绚丽的油画化为水墨画&#xff0c;通过cv2.cvtColor()函数的…

主机ping不通虚拟机/虚拟机ping不通主机/xhell连接不了虚拟机/win10或win11系统升级导致无法连接到虚拟机

解决方案 重置网卡 找虚拟机ip&#xff0c;第二个inet对应的就是虚拟机ip地址 xshell连接 参考: 主机ping不通虚拟机

认识认识DHCP

文章目录 认识认识DHCP一、什么是DHCP&#xff1f;1.1、为什么要使用DHCP&#xff1f;1.2、DHCP是怎么工作的&#xff1f;1.2.1、客户端首次接入网络的工作原理1.2.2、客户端重用曾经使用过的地址的工作原理1.2.3、客户端更新租期的工作原理 二、配置DHCP Server&#xff0c;为…

电子式汽车机油压力传感器的接线方法及特点

电子式机油压力传感器由厚膜压力传感器芯片、信号处理电路、外壳、固定电路板装置和两根引线&#xff08;信号线和报警线&#xff09;组成。信号处理电路由电源电路、传感器补偿电路、调零电路、电压放大电路、电流放大电路、滤波电路和报警电路组成。 厚膜压力传感器是20世纪…

【UE5】动态播放媒体

最近项目中有一个需求&#xff0c;需要将场景中的42块屏幕都显示媒体内容&#xff0c;想着如果每一块屏幕都创建一个MediaPlayer资产、一个MediaSource资产、一个MediaTexture资产及创建对应的Material&#xff0c;就是4*42168个资产需要维护了&#xff0c;所以想着就全部采用动…

如何看待Agent AI智能体的未来

Agent AI智能体的未来 Agent AI智能体&#xff0c;也称为自主代理或智能代理&#xff0c;是指能够自主执行任务、与环境交互并作出决策的计算机程序或系统。这些智能体通常具备学习、适应和推理的能力&#xff0c;能够在复杂和不确定的环境中执行任务。随着技术的进步&#xf…

【OC和红移的双面材质】

OC和红移的双面材质 2021-12-23 18:36 rs oc 评论(0)

TiDB 利用binlog 恢复-反解析binlog

我们知道TiDB的binlog记录了所有已经执行成功的dml语句&#xff0c;类似mysql binlog row模式 &#xff0c;TiDB官方也提供了reparo可以进行解析binlog&#xff0c;如下所示: [2024/04/26 20:58:02.136 08:00] [INFO] [config.go:153] ["Parsed start TSO"] [ts449…

Linux网络抓包工具tcpdump是如何实现抓包的,在哪个位置抓包的?

Linux网络抓包工具tcpdump是如何实现抓包的&#xff0c;在哪个位置抓包的&#xff1f; 1. tcpdump抓包架构2. BPF介绍3. 从内核层面看tcpdump抓包流程3.1. 创建socket套接字3.2. 挂载BPF程序 4. 网络收包抓取5. 网络发包抓取6. 疑问和思考6.1 tcpdump抓包跟网卡、内核之间的顺序…
最新文章