OceanBase中NOT EXISTS是否需要被改写

作者简介

张瑞远,曾经从事银行、证券数仓设计、开发、优化类工作,现主要从事电信级IT系统及数据库的规划设计、架构设计、运维实施、运维服务、故障处理、性能优化等工作。 持有Orale OCM,MySQL OCP及国产代表数据库认证。 获得的专业技能与认证包括 OceanBase OBCE、Oracle OCP 11g、OracleOCM 11g 、MySQL OCP 5.7 、腾讯云TBase、腾讯云TDSQ

背景:

前段时间写了一篇《 关于OB中左外连接和反连接的探究 》的文章。OceanBase官网知识库后来也更新了这部分的内容,链接如下:

https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000475695 

1704244993

这意味着在OceanBase中就不建议使用not exists,或者说只能通过改写来优化它吗?

实际上,并非如此。包括我在文章中的介绍,也仅仅是表明在特定情况下,batch rescan的优化性能可能会优于无法使用该特性的anti join,但这并不意味着not exists的优化途径仅此一种。

为了更直观地说明这一点,我仍倾向于通过实验来验证。

接下来,我将以我之前文章中的sql为例,进行一次优化实验。

实验过程:

sql原文及效率和执行计划如下,因为CTM_GM是视图,所以执行计划中看到的表名是SD_CTM_GM。

#####sql文本select  count(1)from tttt.mmmmm_sssssale twhere t.sssss  not in ('e111', 'ddddda')and t.stats  = '1'and t.parean  is nulland t.city  = 2208AND (t.cusystatus  = 'FFFFGGGGG')AND (T.ORGGGGGGNEL  is null or T.ORGGGGGGNEL  != 'infonow')AND NOT EXISTS (SELECT  1FROM tttt.TTTT_OWN_C  TOWWHERE T.PID  = TOW.OIDAND TOW.CT_ID  = T.city AND TOW.OODDD_sTS   IN(SELECT DDDCFROM tttt.CTM_GMWHERE GPID  = 'OtherThing 'AND stats  = '1'))  and to_char(createdate,'yyyymmdd') between 20150101 and 202301211;	
###执行时间+----------+
| COUNT(1) |
+----------+
|    29493 |
+----------+
1 row in set (43.87 sec)
####执行计划
| ===========================================================================================
|ID|OPERATOR                           |NAME                             |EST. ROWS|COST  |
-------------------------------------------------------------------------------------------
|0 |SCALAR GROUP BY                    |                                 |1        |161064|
|1 | NESTED-LOOP ANTI JOIN             |                                 |808      |161033|
|2 |  TABLE SCAN                       |T                                |1278     |40623 |
|3 |  PX COORDINATOR                   |                                 |1        |94    |
|4 |   EXCHANGE OUT DISTR              |:EX10001                         |1        |94    |
|5 |    SUBPLAN SCAN                   |VIEW2                            |1        |94    |
|6 |     NESTED-LOOP JOIN              |                                 |1        |94    |
|7 |      EXCHANGE IN DISTR            |                                 |1        |92    |
|8 |       EXCHANGE OUT DISTR (BC2HOST)|:EX10000                         |1        |92    |
|9 |        TABLE SCAN                 |TOW(IDX_TTTT_OWN_C _ORDERID)     |1        |92    |
|10|      TABLE SCAN                   |SD_CTM_GM(PK_SD_CTM_GM)          |1        |32    | 
===========================================================================================Outputs & filters: 
-------------------------------------0 - output([T_FUN_COUNT(*)]), filter(nil), group(nil), agg_func([T_FUN_COUNT(*)])1 - output([1]), filter(nil), conds(nil), nl_params_([T.PID ])2 - output([T.PID ]), filter([T.city  = 2208], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) >= 20150101], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) <= 202301211], [(T_OP_IS, T.ORGGGGGGNEL , NULL, 0) OR T.ORGGGGGGNEL  != ?], [(T_OP_NOT_IN, T.sssss , (?, ?))], [(T_OP_IS, T.parean , NULL, 0)], [T.stats  = ?], [T.cusystatus  = ?]), access([T.sssss ], [T.stats ], [T.parean ], [T.city ], [T.cusystatus ], [T.ORGGGGGGNEL ], [T.PID ], [T.CREATEDATE]), partitions(p0)3 - output([1]), filter(nil)4 - output([1]), filter(nil), is_single, dop=15 - output([1]), filter(nil), access([VIEW2.TOW.OID])6 - output([TOW.OID]), filter(nil), conds(nil), nl_params_([TOW.OODDD_sTS  ])7 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil)8 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil), is_single, dop=19 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter([TOW.CT_ID  = 2208]), access([TOW.OID], [TOW.CT_ID ], [TOW.OODDD_sTS  ]), partitions(p0)10 - output([1]), filter([SD_CTM_GM.stats  = ?]), access([SD_CTM_GM.stats ]), partitions(p0)

可以看到该sql走了NESTED-LOOP ANTI JOIN,执行时间是43s,执行时间比较长。

按照正常的优化思路来分析下,咱们先看下实际的数据量。

obclient>  select  count(1)->     from tttt.mmmmm_sssssale t->    where t.sssss  not in ('e111', 'ddddda')->      and t.stats  = '1'->      and t.parean  is null->      and t.city  = 2208->      AND (t.cusystatus  = 'FFFFGGGGG')->      AND (T.ORGGGGGGNEL  is null or T.ORGGGGGGNEL  != 'infonow') and to_char(createdate,'yyyymmdd') between 20150101 and 202301211; 
+----------+
| COUNT(1) |
+----------+
|    29493 |
+----------+
1 row in set (0.08 sec)obclient> SELECT count(*)->                     FROM tttt.CTM_GM->                    WHERE GPID  = 'OtherThing '->        AND stats  = '1' ;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)obclient> SELECT count(*) from  tttt.TTTT_OWN_C  TOW;
+----------+
| COUNT(*) |
+----------+
|  5087140 |
+----------+
1 row in set (1.99 sec)

可以看到CTM_GM的结果是0行,扫描速度很快,那么not exists的子查询结果也是0行,但是TOW表有500W数据,原本的执行计划是TOW通过nl连接CTM_GM,要取TOW的结果集去匹配CTM_GM,理论上我们修改这两个表关联顺序,就可以只取CTM_GM的0行消除掉TOW表这么大数据量的消耗代价。


######sql文本
select /*+use_nl(@"SEL$1" ("VIEW2"@"SEL$1" ))*/ count(1)from tttt.mmmmm_sssssale twhere t.sssss  not in ('e111', 'ddddda')and t.stats  = '1'and t.parean  is nulland t.city  = 2208AND (t.cusystatus  = 'FFFFGGGGG')AND (T.ORGGGGGGNEL  is null or T.ORGGGGGGNEL  != 'infonow')AND NOT EXISTS (SELECT  /*+leading(CTM_GM) use_nl(CTM_GM,TOW) */ 1FROM tttt.TTTT_OWN_C  TOWWHERE T.PID  = TOW.OIDAND TOW.CT_ID  = T.city AND TOW.OODDD_sTS   IN(SELECT DDDCFROM tttt.CTM_GMWHERE GPID  = 'OtherThing 'AND stats  = '1'))  and to_char(createdate,'yyyymmdd') between 20150101 and 202301211;		#######执行效率
+----------+
| COUNT(1) |
+----------+
|    29493 |
+----------+
1 row in set (0.21 sec)	  ####执行计划| =========================================================================================
|ID|OPERATOR                |NAME                                      |EST. ROWS|COST  |
-----------------------------------------------------------------------------------------
|0 |SCALAR GROUP BY         |                                          |1        |275986|
|1 | NESTED-LOOP ANTI JOIN  |                                          |808      |275955|
|2 |  PX COORDINATOR        |                                          |1278     |41514 |
|3 |   EXCHANGE OUT DISTR   |:EX10000                                  |1278     |40623 |
|4 |    TABLE SCAN          |T                                         |1278     |40623 |
|5 |  SUBPLAN SCAN          |VIEW2                                     |1        |183   |
|6 |   NESTED-LOOP JOIN     |                                          |1        |183   |
|7 |    TABLE SCAN          |SD_CTM_GM(INX_SD_CTM_GM_GPID )|1        |92    |
|8 |    MATERIAL            |                                          |1        |92    |
|9 |     PX COORDINATOR     |                                          |1        |92    |
|10|      EXCHANGE OUT DISTR|:EX20000                                  |1        |92    |
|11|       TABLE SCAN       |TOW(IDX_TTTT_OWN_C _ORDERID)            |1        |92    |
=========================================================================================Outputs & filters: 
-------------------------------------0 - output([T_FUN_COUNT(*)]), filter(nil), group(nil), agg_func([T_FUN_COUNT(*)])1 - output([1]), filter(nil), conds(nil), nl_params_([T.PID ])2 - output([T.PID ]), filter(nil)3 - output([T.PID ]), filter(nil), is_single, dop=14 - output([T.PID ]), filter([T.city  = 2208], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) >= 20150101], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) <= 202301211], [(T_OP_IS, T.ORGGGGGGNEL , NULL, 0) OR T.ORGGGGGGNEL  != ?], [(T_OP_NOT_IN, T.sssss , (?, ?))], [(T_OP_IS, T.parean , NULL, 0)], [T.stats  = ?], [T.cusystatus  = ?]), access([T.sssss ], [T.stats ], [T.parean ], [T.city ], [T.cusystatus ], [T.ORGGGGGGNEL ], [T.PID ], [T.CREATEDATE]), partitions(p0)5 - output([1]), filter(nil), access([VIEW2.TOW.OID])6 - output([TOW.OID]), filter(nil), conds([TOW.OODDD_sTS   = SD_CTM_GM.DDDC]), nl_params_(nil)7 - output([SD_CTM_GM.DDDC]), filter([SD_CTM_GM.stats  = ?]), access([SD_CTM_GM.stats ], [SD_CTM_GM.DDDC]), partitions(p0)8 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil)9 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil)10 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil), is_single, dop=111 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter([TOW.CT_ID  = 2208]), access([TOW.OID], [TOW.CT_ID ], [TOW.OODDD_sTS  ]), partitions(p0) 

通过添加hint的方式我们可以看到我只调整了TOW和CTM_GM的连接顺序,效率从43s优化到了0.21s,而我上一篇文章中改写之后的效率是1s,所以该sql不需要改写就可以优化掉。

那这个效率还可以优化吗,看起来还有优化空间,既然not exists的结果集是0,那我用上面同样的方式,干预下view结果集和T表的顺序,能不能得到更好的结果那?

####sql文本
select /*+leading(("VIEW2"@"SEL$1" ))*/ count(1)from tttt.mmmmm_sssssale twhere t.sssss  not in ('e111', 'ddddda')and t.stats  = '1'and t.parean  is nulland t.city  = 2208AND (t.cusystatus  = 'FFFFGGGGG')AND (T.ORGGGGGGNEL  is null or T.ORGGGGGGNEL  != 'infonow')AND NOT EXISTS (SELECT  /*+leading(CTM_GM) use_nl(CTM_GM,TOW) */ 1FROM tttt.TTTT_OWN_C  TOWWHERE T.PID  = TOW.OIDAND TOW.CT_ID  = T.city AND TOW.OODDD_sTS   IN(SELECT DDDCFROM tttt.CTM_GMWHERE GPID  = 'OtherThing 'AND stats  = '1'))  and to_char(createdate,'yyyymmdd') between 20150101 and 202301211;		  
####执行效率	   
+----------+
| COUNT(1) |
+----------+
|    29493 |
+----------+
1 row in set (0.08 sec)
#########执行计划	   
| ==========================================================================================
|ID|OPERATOR                |NAME                                      |EST. ROWS|COST   |
------------------------------------------------------------------------------------------
|0 |SCALAR GROUP BY         |                                          |1        |2231317|
|1 | HASH RIGHT ANTI JOIN   |                                          |808      |2231286|
|2 |  SUBPLAN SCAN          |VIEW2                                     |470      |2188504|
|3 |   NESTED-LOOP JOIN     |                                          |470      |2188497|
|4 |    TABLE SCAN          |SD_CTM_GM(INX_SD_CTM_GM_GPID )|1        |92     |
|5 |    MATERIAL            |                                          |299244   |2182932|
|6 |     PX COORDINATOR     |                                          |299244   |2169701|
|7 |      EXCHANGE OUT DISTR|:EX10000                                  |299244   |2075029|
|8 |       TABLE SCAN       |TOW                                       |299244   |2075029|
|9 |  PX COORDINATOR        |                                          |1278     |41514  |
|10|   EXCHANGE OUT DISTR   |:EX20000                                  |1278     |40623  |
|11|    TABLE SCAN          |T                                         |1278     |40623  |
==========================================================================================Outputs & filters: 
-------------------------------------0 - output([T_FUN_COUNT(*)]), filter(nil), group(nil), agg_func([T_FUN_COUNT(*)])1 - output([1]), filter(nil), equal_conds([T.PID  = VIEW2.TOW.OID]), other_conds(nil)2 - output([VIEW2.TOW.OID]), filter(nil), access([VIEW2.TOW.OID])3 - output([TOW.OID]), filter(nil), conds([TOW.OODDD_sTS   = SD_CTM_GM.DDDC]), nl_params_(nil)4 - output([SD_CTM_GM.DDDC]), filter([SD_CTM_GM.stats  = ?]), access([SD_CTM_GM.stats ], [SD_CTM_GM.DDDC]), partitions(p0)5 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil)6 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil)7 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter(nil), is_single, dop=18 - output([TOW.OID], [TOW.OODDD_sTS  ]), filter([TOW.CT_ID  = 2208]), access([TOW.OID], [TOW.CT_ID ], [TOW.OODDD_sTS  ]), partitions(p0)9 - output([T.PID ]), filter(nil)10 - output([T.PID ]), filter(nil), is_single, dop=111 - output([T.PID ]), filter([T.city  = 2208], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) >= 20150101], [cast(cast(TO_CHAR(T.CREATEDATE, ?), VARCHAR2(256 BYTE)), NUMBER(-1, -85)) <= 202301211], [(T_OP_IS, T.ORGGGGGGNEL , NULL, 0) OR T.ORGGGGGGNEL  != ?], [(T_OP_NOT_IN, T.sssss , (?, ?))], [(T_OP_IS, T.parean , NULL, 0)], [T.stats  = ?], [T.cusystatus  = ?]), access([T.sssss ], [T.stats ], [T.parean ], [T.city ], [T.cusystatus ], [T.ORGGGGGGNEL ], [T.PID ], [T.CREATEDATE]), partitions(p0)	   

可以看到现在效率0.08s比一开始的43s提升了500多倍比单纯的改写效率提升了12倍多,所以NOT EXISTS不一定需要改写,可能只是计划走错了,这种情况下我们绑定下计划就好了,很多时候应用改代码的代价也很大。

结论:

很多sql不是一定要去改写才能解决的,虽然改写有可能可以使用到一些优化算子,但是可能问题的根因不在这里,我们一定要提高自己分析问题的能力,准确判断分析问题,这样在工作中可以得心应手,也可以避免很多不必要的冗余的工作。

行之所向,莫问远方。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通知中心架构:打造高效沟通平台,提升信息传递效率

随着信息技术的快速发展&#xff0c;通知中心架构作为一种关键的沟通工具&#xff0c;正逐渐成为各类应用和系统中必不可少的组成部分。本文将深入探讨通知中心架构的意义、设计原则以及在实际场景中的应用。 ### 什么是通知中心架构&#xff1f; 通知中心架构是指通过集中管…

前端学习<二>CSS基础——06-CSS盒模型详解

盒子模型 前言 盒子模型&#xff0c;英文即box model。无论是div、span、还是a都是盒子。 但是&#xff0c;图片、表单元素一律看作是文本&#xff0c;它们并不是盒子。这个很好理解&#xff0c;比如说&#xff0c;一张图片里并不能放东西&#xff0c;它自己就是自己的内容。…

【vue3学习笔记(二)】(第141-143节)初识setup;ref函数_处理基本类型;ref函数_处理对象类型

尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 本篇内容对应课程第141-143节 课程 P141节 《初识setup》笔记 1、setup是所有组合式API“表演的舞台”&#xff0c;组件中所用到的所有数据、方法、监视数据、生命周期钩子等都需要配置在setup中。 2、setup的两种返回值&…

JAVA的控制语句

控制语句 分为顺序、选择和循环 顺序结构 先执行a&#xff0c;再执行b 条件判断结构 如果......则....... 循环结构 如果.....则重复执行 条件判断结构&#xff08;选择结构&#xff09; if单分支结构 语法结构&#xff1a; if(布尔表达式){ 语句块 } 注&#xff…

【Java程序员福音】国外Java 程序员开发常用的工具(全)

Java是一门开源语言&#xff0c;所以可以选择的开发环境很多&#xff0c;你适合哪一个呢&#xff1f;整理了一些Java程序员开发常用的工具&#xff0c;需要的同学可以收藏。 1、免费开源Eclipse Eclipse最初是由IBM公司开发的替代商业软件Visual Age for Java的下一代IDE开发环…

安静:内向性格的竞争力 - 三余书屋 3ysw.net

精读文稿 这期我们介绍的这本书叫做《安静》&#xff0c;副标题是《内向性格的竞争力》。本书共有267页&#xff0c;我会用大约25分钟的时间为你讲述书中的精髓。内向性格具备什么样的竞争力&#xff1f;内向性格的人在人际交往和日常生活中似乎总是吃亏&#xff0c;因为他们不…

汉化必备工具 Poedit Pro 翻译编辑器

特色功能 简单易用的界面&#xff1a; Poedit的界面简洁直观&#xff0c;没有复杂的选项和设置。它专注于提供最基本的翻译编辑功能&#xff0c;使得用户能够快速上手并高效完成翻译工作。多种文件格式支持&#xff1a; Poedit支持多种常见的翻译文件格式&#xff0c;包括Gett…

JAVA WEB 能够实现整个文件夹的上传下载吗?

导入项目&#xff1a; 导入到Eclipse&#xff1a;导入项目 导入到IDEA&#xff1a;导入项目 springboot统一配置&#xff1a;springboot-配置 下载示例&#xff1a; https://gitee.com/xproer/up6-jsp-eclipse/tree/6.5.40/ 工程 NOSQL NOSQL示例不需要任何配置&#xff0c;可…

Jenkins升级中的小问题

文章目录 使用固定版本安装根据jenkins页面下载war包升级jenkins重启jenkins报错问题解决 K8s部署过程中的一些小问题 ##### Jenkins版本小插曲 ​ 在Jenkins环境进行插件安装时全部清一色飘红&#xff0c;发现是因为Jenkins版本过低导致&#xff0c;报错的位置可以找到更新je…

vue纯前端过滤嵌套数据,通过关键字搜索过滤嵌套数据

1.过滤效果&#xff1a; 2. cardList 数据源&#xff1a; [ { "id": 4, "createTime": "2024-03-28 02:47:18", "updateTime": "2024-03-28 02:47:18", "uniqueId": "…

python实战之基础篇(一)

1. 注释 # coding utf-8 # 该注释放到文件第一行, 这个注释告诉python解释器该文件的编码集是UTF-82. 导入语句有三种形式 import <模块名> from <模块名> import <代码元素> from <模块名> import <代码元素> as <代码元素别名>3. 获取…

FPGA时钟资源详解(1)——时钟Buffer的选择

FPGA时钟系列文章总览&#xff1a;FPGA原理与结构&#xff08;14&#xff09;——时钟资源https://ztzhang.blog.csdn.net/article/details/132307564 目录 一、概述 二、时钟Buffer的选择 2.1 BUFG 2.2 BUFR 和 BUFIO 2.2.1 源同步接口的支持 2.2.2 扩展时钟域…