标量子查询产生的SQL性能瓶颈,该怎么合理优化?
来自:DBAplus社群
作者介绍
郝昊喆,新炬网络数据库专家。擅长数据库方面的开发、整体架构及复杂SQL的调优,参与了多个行业核心系统的优化工作,目前专注于对开源技术、自动化运维和性能调优技术的研究。
一、关于标量子查询及伪代码表示
标量子查询由于需要传值,因此它和嵌套循环连接类似,被驱动表会被扫描N次。SQL语句中的主结果集为驱动表,标量查询为被驱动表,被驱动表的执行次数为主结果集在连接列上distinct值的数量,例如一条带标量子查询的SQL语句:
select ename, (select dname from dept d where d.deptno = e.deptno) dname
from emp e
where e.job in ('SALESMAN', 'ANALYST');
用伪代码可以表示为:
for i in (select distinct deptno from emp e where e.job in ('SALESMAN', 'ANALYST')):
select dname from dept d where d.deptno = i;
标量部分的执行次数可通过SQL语句计算出:
selet count(distinct deptno ) from emp e where e.job in ('SALESMAN', 'ANALYST');
二、标量子查询易产生的性能问题
结合伪代码,通常来说带有标量子查询的SQL语句易产生性能问题的地方有三点:
1、主查询过滤结果集时的效率,在上述例子中,是指对主表emp基于job字段进行过滤时的性能,如果访问路径较差,例如全表扫描、错误的索引扫描,易产生性能问题;
2、主查询过滤结果集后返回的数据量较大(这里的数据量指的是连接列的唯一值),会导致标量部分多次查询,即使标量的访问路径为INDEX UNIQUE SCAN,也容易因为较多的查询次数产生性能问题;
3、标量部分的查询效率,如果标量部分的访问路径较差,易引起性能问题。
三、标量子查询常规优化方案
介绍了标量子查询的特点后,接下来聊聊优化的问题,通常来说,当标量子查询存在性能问题时,可采取的优化方案主要有3种方式:
1、对于查询语句中的标量子查询,通常使用left join改写,当然,如果标量部分与主表在连接列上为主键、外键关系时,可以改写为inner join,进一步提升性能;
2、对于update语句中的标量子查询,通常使用merge语句改写;
3、在某些环境下不能改写时,可通过索引手段优化标量部分的访问路径、连接方式;
个人建议优先选择改写优化,因为改写最大的优势是可以控制执行计划,改变标量的连接方式(例如通过Hint、profile或者让优化器自己去选择),当某些环境下无法改变SQL语句时,再通过索引方式优化。
四、案例分析
本小节分享3个实际工作中遇到的标量子查询改写优化案例,3个案例分别是:
案例1:select语句中的标量子查询改写;
案例2:where语句后的标量子查询改写;
案例3:update语句中的标量子查询改写;
案例1:select中的标量子查询
该案例为数仓平台中的报表SQL,总执行时间约2分钟左右,主表order加上过滤条件并group by后,返回数据量约160万。
真实执行计划显示,这一步仅耗时不到2秒,大部分时间耗费在ID=3-access(“TT”.”ORDER_ID”=:B1),体现在SQL语句中,则是标量部分的SELECT COUNT(1) from dhoe tt where tt.order_id = t.order_id,由于主表返回的数据量在连接列distinct值较多,导致标量部分扫描的次数较多(约160万),造成性能瓶颈。
select t.num_id,
t.create_time,
1 if_3to4,
max(case
when (select count(1)
from dhoe tt
where tt.order_id = t.order_id
and tt.otype = '101') > 0 then
0
when (select count(1)
from dhoe tt
where tt.order_id = t.order_id
and tt.otype = '102') > 0 then
1
else
0
end) if_hk
from order t
where off_rype = '9601'
group by t.num_id, t.create_time;
对于select语句后的标量子查询,我们通常使用left join或者inner join方式改写,在该案例中,标量部分的表dhoe与主表order在连接列order_id上不存在主外键关系,因此只能使用left join改写,改写时只需将标量部分在连接列上进行分组,将结果提前计算后与主表做关联即可。
改写后,优化器选择将主表与原先的标量查询做哈希外连接,每个表只访问一次即可完成查询,大大减少了物理I/O次数。
当然,并不是改写后优化器一定会选择哈希外连接,在某些情况下(如统计信息、特殊查询干扰优化器对rows的估算等等),优化器也会选择与改写后的标量视图做连接列谓词推入,从而导致改写后的标量视图做被驱动表与主表进行嵌套循环连接,这种执行计划通常要比标量写法还要差,因此改写完后我们仍需要检查下执行计划有没有问题。
select num_id,
create_time,
1 if_3to4,
max(case
when b.cnt101 > 0 then
0
when c.cnt102 > 0 then
1
else
0
end) if_hk
from (select t.num_id, t.create_time, 1 if_3to4, order_id
from order t
where off_rype = '9601') a
left outer join (select order_id,
count(case
when otype = '101' then
1
end) cnt101,
count(case
when otype = '102' then
1
end) cnt102
from dhoe
group by order_id) b
> group by num_id, create_time;
案例2:where后的标量子查询
where后面有标量子查询时,由于需要传值,标量部分同样需要执行N次,只不过在执行计划中会出现关键字Filter,这一点和select后的标量子查询执行计划有点区别,但是运算方式是一样的,Filter的驱动表为主结果集,被驱动表为标量子查询。
该案例是大屏定时刷新页面的SQL语句,每次执行耗时52秒,通过真实执行计划可看到,大部分时间耗费在标量部分的传值计算中:
select count(*)
from wp_info ws
inner join wp_center wa
> where ws.status = 'VALID'
and wa.is_del = 'V'
and (select count(1)
from wp_bas wb
left join wp_rep wr
> where wb.WP_STATUS in (2, 3, 4)
and wr.is_valid = 'VALID'
and wr.created > sysdate - 7
and wr.service_no = ws.num) < ws.total_num;
与select中标量子查询的改写方式一致:标量部分按连接列分组提前将结果计算好后与主结果集做left join即可,在这个案例中,需要注意使用nvl函数对改写后的标量字段空值处理后再进行比较:and nvl(cc.cnt, 0) < ws.total_num
select count(*)
from wp_info ws
inner join wp_center wa
> left join (select wr.service_no, count(1) cnt
from wp_bas wb
left join wp_rep wr
on wr.id = wb.id
where wb.WP_STATUS in (2, 3, 4)
and wr.is_valid = 'VALID'
and wr.created > sysdate - 7
group by wr.service_no) cc
> where ws.status = 'VALID'
and wa.is_del = 'V'
and nvl(cc.cnt, 0) < ws.total_num;
这里跟大家聊聊另外一种思路:在某些情况下,我们可以使用with+materialize物化的方式对标量的计算过程进行优化,这样改写优点就是简单方便,可以减少每次计算标量部分带来的性能开销,缺点则是它不能像left join改写那样控制执行计划,即无法改变被驱动表的执行次数。
对于案例二,用with+materialize方式的改写方案如下:
with t as
(select /*+ materialize */
wr.service_no
from wp_bas wb
left join wp_rep wr
> where wb.WP_STATUS in (2, 3, 4)
and wr.is_valid = 'VALID'
and wr.created > sysdate - 7)
select count(*)
from wp_info ws
left join wp_center wa
> where ws.status = 'VALID'
and wa.is_del = 'V'
and (select count(1) from t wr where wr.service_no = ws.num) <
ws.total_num;
案例3:update中的标量子查询
update set语句后面有传值时,也会导致子查询被扫描N次,通常使用merge语句进行改写,使用merge语句改写时,需要注意关联条件的写法。
该案例的更新语句每次执行耗时约20分钟,通过真实执行计划可以看到,标量部分执行了257次是该SQL语句的性能瓶颈:
UPDATE RE_RPT A
SET A.TCNT =
(SELECT NVL(SUM(G.REDCODE), 0)
FROM RP_GRANT G,
(SELECT DISTINCT REDCODE, REDCODE_MD5
FROM RP_SCAN) S,
IMT_CODE V,
(SELECT DISTINCT W_ID, NAME
FROM MATER
WHERE SUBSTR(ORDER, 1, 2) = 'OF'
AND W_ID IS NOT NULL) M
WHERE G.REDCODE = S.REDCODE
AND S.REDCODE_MD5 = V.N_CODE
AND V.W_CODE = M.W_ID
AND G.RP_CLASS = '有效'
AND G.CREATE_DATE = '2018-05-05'
AND A.NAME = M.NAME)
WHERE A.CHOOSE_TIME = '2018-05-05';
通过以下查询也可以计算出标量执行次数:
SQL> select count(distinct NAME)
2 from RE_RPT A
3 WHERE A.CHOOSE_TIME='2018-05-05';
COUNT(DISTINCTNAME)
---------------------------
257
由于该语句每次执行时,是对每日数据进行全量更新,因此merge语句的关联条件可以写为外连接的方式:A.NAME = M.NAME(+)
MERGE INTO (select *
from RE_RPT A
WHERE A.CHOOSE_TIME = '2018-05-05') A
USING (SELECT M.NAME, SUM(G.REDCODE) SUM_CODE
FROM RP_GRANT G,
(SELECT DISTINCT REDCODE, REDCODE_MD5
FROM RP_SCAN) S,
IMT_CODE V,
(SELECT DISTINCT W_ID, NAME
FROM MATER
WHERE SUBSTR(ORDER, 1, 2) = 'OF'
AND W_ID IS NOT NULL) M
WHERE G.REDCODE = S.REDCODE
AND S.REDCODE_MD5 = V.N_CODE
AND V.W_CODE = M.W_ID
AND G.RP_CLASS = '有效'
AND G.CREATE_DATE = '2018-05-05')
group by M.NAME) M
ON (A.NAME = M.NAME(+))
when matched then
update set A.TCNT = nvl(SUM_CODE, 0);
五、总结
本文主要跟大家介绍了标量子查询的特点,并结合实际工作中遇到的3个案例聊了下通用的改写方案,如果一条SQL语句的性能瓶颈在标量子查询,那么可以通过改写SQL来改变主表与标量子查询的连接方式,或者通过建立索引优化标量部分的访问路径,本质都是一样的:减少物理I/O次数,达到提升性能的目的。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
标量子查询产生的SQL性能瓶颈,该怎么合理优化?相关推荐
- sql 标量子查询_SQL Server 2017:标量子查询简化
sql 标量子查询 Nowadays a lot of developers use Object-Relational Mapping (ORM) frameworks. ORM is a prog ...
- 性能为王:SQL标量子查询的优化案例分析
本篇整理内容是黄廷忠在"云和恩墨大讲堂"微信分享中的讲解案例,SQL优化及SQL审核,是从源头解决性能问题的根本手段,无论是开发人员还是DBA,都应当持续深入的学习SQL开发技能, ...
- 又是标量子查询引起的性能问题
某系统巡检,发现TOPSQL里面第一条SQL语句的执行时间相当不合理~ select (select EC_CUST_NOfrom GYL.FAAAAASM cuswhere cus.refcode ...
- order by 子查询_视图,子查询,标量子查询,关联子查询
视图 子查询 标量子查询 关联子查询 如何用SQL解决业务问题 各种函数 1. 视图 视图内存放SQL查询语句,运行时运行该语句.查出的数据为临时数据 创建视图 create view as 视图名称 ...
- 20180601]函数与标量子查询2.txt
[20180601]函数与标量子查询2.txt --//昨天看http://www.cnblogs.com/kerrycode/p/9099507.html链接,里面提到: 通俗来将,当使用标量子查询 ...
- 12C 新特性 | 标量子查询自动转换
有超过6年超大型数据库专业服务经验,擅长数据库解决方案设计与项目管理:在多年的技术实践中,先后为运营商(移动.电信).银行.保险.制造业等各行业客户的业务关键型系统提供了运维.升级.性能优化.项目实施 ...
- [20180626]函数与标量子查询14.txt
[20180626]函数与标量子查询14.txt --//前面看http://www.cnblogs.com/kerrycode/p/9099507.html链接,里面提到: 通俗来将,当使用标量子查 ...
- oracle标量子查询 外层,Oracle标量子查询
Oracle标量子查询 ORACLE允许在select子句中包含单行子查询, 使用标量子查询可以有效的改善性能,当使用到外部连接,或者使用到了聚合函数,就可以考虑标量子查询的可能性. 下面来看几个例子 ...
- 优化案例2:select标量子查询且主查询排序
优化案例2:select标量子查询且主查询排序 1. 场景描述 2. 分析过程 2.1 查看原始SQL执行计划 2.2 ET定位耗时操作符 2.3 注释大法 3. 解决方法 3.1 HINT功法 3. ...
最新文章
- 力扣(LeetCode)刷题,简单题(第12期)
- IDEA不能一个窗口管理多个项目?那是你不会用!
- dnf剑魂buff等级上限_剑魂完美换装BUFF!远古遗愿的用处很大么?
- javascript库之Mustache库使用说明
- 数据生态mysql_数据生态:MySQL复制技术与生产实践
- python求数字平均值_Python简单计算数组元素平均值的方法示例
- JsonView插件的使用
- chrome session丢失_为什么还是由这么多人搞不懂Cookie、Session、Token?
- html自动选择省市,jQuery中国省市区地址三级联动插件Distpicker
- King Arthur's Knights 【HDU - 4337】【哈密顿回路性质Dirac定理】
- ZoomIt 屏幕放大 缩小 屏幕画笔 演示 手写笔迹 倒计时 秒变白板 pointofix
- 那些年面挂的js手写题
- 小区宽带网络解决方案
- 可以下载《全程软件测试》样章电子版
- java 小球抛物线_小球抛物线运动
- 168-203-javajvm-垃圾收集器
- 什么是人脉和人脉资源
- WordPress 不修改代码通过sql语句修改数据库批量增加文章阅读量
- chrome 导入html书签,怎么把谷歌浏览器书签导出来?怎么将书签导入到别的浏览器中?...
- 十大技术类公众微信号地址
热门文章
- python实现socket编程,客户端和服务端之间互相对话(二)
- [kuangbin带你飞]专题六-生成最小树
- POJ2932Coneology(计算几何、平面扫描)
- html中隐藏溢出怎么写,html-如何隐藏表行溢出?
- android资源替换方案overlay,Android 运行时资源替换----Runtime Resource Overlay
- 基于三层BP神经网络的人脸识别
- pta 哈利·波特的考试
- 文件节点的linux指令,Java工程师必学的Linux命令(一)文件与目录管理
- Spring Boot Web 开发相关总结
- 【独立开发人员er Cocos2d-x实战 009】Cocos2dx 菜单项CCMenu使用