通过OceanBase 3.x中not in无法走hash连接的变化,来看OB优化器的发展

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

前言:

对oracle数据库有一定经验的dba都应该知道,not in 和 not exists这两种方式之间存在区别,并不能随意互换使用。原因是,not in在处理null值时会忽略,不会将其处理。因此,在寻求等价替换时,需要考虑主表与关联表中的数据是否包含空值,以避免结果偏差。

案例分析:

提及上面的小知识点,也是为了抛砖引玉,引入今天的案例,有同事在OceanBase V3.2.3.3的环境上发现了一条not in的sql,反馈执行时间1s多,走了nl反连接,他尝试改写为了not exists,走了hash反连接,执行时间降低到了0.2s。所以他尝试添加hint强制让原语句去走hash连接,可以无论如何都无法改变连接顺序。

首先正常等价改写的情况下,not in和not exists在执行计划相同的情况下,一般不存在谁优谁劣,其次正常来说等价的条件下执行计划不会有偏差,不会存在not exists可以走的计划,not in不能走。所以我感觉是优化器的缺陷,下面来看下案例。

##############原始sql语句#########
select    count(*)
  from cccc.LOOOOOOOOOOOOOOOOOOOO a
 where a.cccdate is not null
   and a.cccdate <= (sysdate - 30)
   and a.cccdate > (sysdate - 31)
   and a.status = 2 
   and a.ddd not in (select  t.ccc_dddd from  cccc.LOOOOOOOOOOOOOOOOOOOO_ddddd t );
 ######执行计划 ##########################3
| ===============================================================================
|ID|OPERATOR              |NAME                             |EST. ROWS|COST   |
-------------------------------------------------------------------------------
|0 |SCALAR GROUP BY       |                                 |1        |1897316|
|1 | NESTED-LOOP ANTI JOIN|                                 |1        |1897316|
|2 |  TABLE SCAN          |A(INX_LOOOOOOOOOOOOOOOOOOOO)     |29       |913    |
|3 |  MATERIAL            |                                 |345093   |261579 |
|4 |   PX COORDINATOR     |                                 |345093   |245872 |
|5 |    EXCHANGE OUT DISTR|:EX10000                         |345093   |133484 |
|6 |     TABLE SCAN       |T(LOOOOOOOOOOOOOOOOOOOO_ddddd)   |345093   |133484 |
===============================================================================
Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_COUNT(*)(0x7eb709e254f0)]), filter(nil), 
      group(nil), agg_func([T_FUN_COUNT(*)(0x7eb709e254f0)])
  1 - output([remove_const(1)(0x7eb709eb9f10)]), filter(nil), 
      conds([(T_OP_OR, A.ddd(0x7eb709e25130) = T.ccc_dddd(0x7eb709e22e30)(0x7eb709e2dc90), (T_OP_IS, A.ddd(0x7eb709e25130), NULL, 0)(0x7eb709e2ec10), (T_OP_IS, T.ccc_dddd(0x7eb709e22e30), NULL, 0)(0x7eb709e2f810))(0x7eb709e2e530)]), nl_params_(nil), batch_join=false
  2 - output([A.ddd(0x7eb709e25130)]), filter([(T_OP_IS_NOT, A.cccdate(0x7eb709dc9040), NULL, 0)(0x7eb709dc9930)]), 
      access([A.cccdate(0x7eb709dc9040)], [A.ddd(0x7eb709e25130)]), partitions(p0), 
      is_index_back=true, filter_before_indexback[true], 
      range_key([A.STATUS(0x7eb709dcf130)], [A.cccdate(0x7eb709dc9040)], [A.CITY(0x7eb709dc8d50)], [A.__pk_increment(0x7eb709e76030)]), range(2,2023-12-29 17:17:39,MAX,MAX ; 2,2023-12-30 17:17:39,MAX,MAX), 
      range_cond([A.cccdate(0x7eb709dc9040) <= ?(0x7eb709dcb7d0)], [A.cccdate(0x7eb709dc9040) > ?(0x7eb709dcd7e0)], [A.STATUS(0x7eb709dcf130) = 2(0x7eb709dcea10)])
  3 - output([T.ccc_dddd(0x7eb709e22e30)]), filter(nil)
  4 - output([T.ccc_dddd(0x7eb709e22e30)]), filter(nil)
  5 - output([T.ccc_dddd(0x7eb709e22e30)]), filter(nil), is_single, dop=1
  6 - output([T.ccc_dddd(0x7eb709e22e30)]), filter(nil), 
      access([T.ccc_dddd(0x7eb709e22e30)]), partitions(p0), 
      is_index_back=false, 
      range_key([T.CITY(0x7eb709e21200)], [T.ccc_dddd(0x7eb709e22e30)], [T.IIIITIME(0x7eb709e214f0)], [T.__pk_increment(0x7eb709e8b500)]), range(MIN,MIN,MIN,MIN ; MAX,MAX,MAX,MAX)always true

原始的sql无法走hash连接,检查发现两表关联列没有非空约束,都是普通列,但是数据都是非空的,那么直接改写成not exists看下。

#############改写后sql##################33
select  count(*)
  from cccc.LOOOOOOOOOOOOOOOOOOOO a
 where a.cccdate is not null
   and a.cccdate <= (sysdate - 30)
   and a.cccdate > (sysdate - 31)
   and a.status = 2
   and not exists (select  1 from  cccc.LOOOOOOOOOOOOOOOOOOOO_ddddd t where a.ddd =t.ccc_dddd);  
##############执行计划#######################33
| =============================================================================
|ID|OPERATOR             |NAME                             |EST. ROWS|COST  |
-----------------------------------------------------------------------------
|0 |SCALAR GROUP BY      |                                 |1        |416897|
|1 | HASH ANTI JOIN      |                                 |1        |416897|
|2 |  TABLE SCAN         |A(INX_LOOOOOOOOOOOOOOOOOOOO)     |29       |913   |
|3 |  PX COORDINATOR     |                                 |345093   |192135|
|4 |   EXCHANGE OUT DISTR|:EX10000                         |345093   |140758|
|5 |    SUBPLAN SCAN     |VIEW1                            |345093   |140758|
|6 |     TABLE SCAN      |T                                |345093   |135571|
============================================================================= 
Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_COUNT(*)(0x7e87236e4a70)]), filter(nil), 
      group(nil), agg_func([T_FUN_COUNT(*)(0x7e87236e4a70)])
  1 - output([remove_const(1)(0x7e8723778930)]), filter(nil), 
      equal_conds([A.ddd(0x7e87236e1ea0) = T.ccc_dddd(0x7e87236e2190)(0x7e87236ecfc0)]), other_conds(nil)
  2 - output([A.ddd(0x7e87236e1ea0)]), filter([(T_OP_IS_NOT, A.cccdate(0x7e87236880a0), NULL, 0)(0x7e8723688990)]), 
      access([A.cccdate(0x7e87236880a0)], [A.ddd(0x7e87236e1ea0)]), partitions(p0), 
      is_index_back=true, filter_before_indexback[true], 
      range_key([A.STATUS(0x7e872368e190)], [A.cccdate(0x7e87236880a0)], [A.CITY(0x7e8723687db0)], [A.__pk_increment(0x7e8723732910)]), range(2,2023-12-29 17:16:30,MAX,MAX ; 2,2023-12-30 17:16:30,MAX,MAX), 
      range_cond([A.cccdate(0x7e87236880a0) <= ?(0x7e872368a830)], [A.cccdate(0x7e87236880a0) > ?(0x7e872368c840)], [A.STATUS(0x7e872368e190) = 2(0x7e872368da70)])
  3 - output([T.ccc_dddd(0x7e87236e2190)]), filter(nil)
  4 - output([T.ccc_dddd(0x7e87236e2190)]), filter(nil), is_single, dop=1
  5 - output([T.ccc_dddd(0x7e87236e2190)]), filter(nil), 
      access([T.ccc_dddd(0x7e87236e2190)]), partitions(p0), 
      is_index_back=false, 
      range_key([T.CITY(0x7e87236df830)], [T.ccc_dddd(0x7e87236e2190)], [T.IIIITIME(0x7e87236dfb20)], [T.__pk_increment(0x7e872374bf50)]), range(MIN,MIN,MIN,MIN ; MAX,MAX,MAX,MAX)always true   

   我们主要看下1号算子的output&filters 的对比

1706597590

可以看到not in的计划走了nest loop anti join,无法改为hash连接的原因是因为,关联列有可能为空的原因。为了验证这个点,我改写了下原始sql,手工限制关联列不为空。

###########修改后的sql##########
select  count(*)
      from cccc.LOOOOOOOOOOOOOOOOOOOO a
      where a.cccdate is not null
      and a.cccdate <= (sysdate - 30)
      and a.cccdate > (sysdate - 31)
      and a.status = 2 and a.ddd is not null 
      and a.ddd not in (select  t.ccc_dddd from  cccc.LOOOOOOOOOOOOOOOOOOOO_ddddd t where t.ccc_dddd is not null);
########执行计划###########
| =============================================================================
|ID|OPERATOR             |NAME                             |EST. ROWS|COST  |
-----------------------------------------------------------------------------
|0 |SCALAR GROUP BY      |                                 |1        |416897|
|1 | HASH ANTI JOIN      |                                 |1        |416897|
|2 |  TABLE SCAN         |A(INX_LOOOOOOOOOOOOOOOOOOOO)     |29       |913   |
|3 |  PX COORDINATOR     |                                 |345093   |192135|
|4 |   EXCHANGE OUT DISTR|:EX10000                         |345093   |140758|
|5 |    SUBPLAN SCAN     |VIEW1                            |345093   |140758|
|6 |     TABLE SCAN      |T                                |345093   |135571|
=============================================================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_COUNT(*)]), filter(nil), 
      group(nil), agg_func([T_FUN_COUNT(*)])
  1 - output([1]), filter(nil), 
      equal_conds([A.ddd = VIEW1.ccc_dddd]), other_conds(nil)
  2 - output([A.ddd]), filter([(T_OP_IS_NOT, A.cccdate, NULL, 0)], [(T_OP_IS_NOT, A.ddd, NULL, 0)]), 
      access([A.cccdate], [A.ddd]), partitions(p0)
  3 - output([VIEW1.ccc_dddd]), filter(nil)
  4 - output([VIEW1.ccc_dddd]), filter(nil), is_single, dop=1
  5 - output([VIEW1.ccc_dddd]), filter(nil), 
      access([VIEW1.ccc_dddd])
  6 - output([T.ccc_dddd]), filter([(T_OP_IS_NOT, T.ccc_dddd, NULL, 0)]), 
      access([T.ccc_dddd]), partitions(p0)         

  可以看出来在我手工排除掉关联列为空的情况下,执行计划可以走hash连接了,那这个问题应该就是ob3.x的一个优化器的小缺陷了,后面为了验证我的想法(也为了看下后续优化器有没有修补这个问题),我又去4.x上测试了一下。

测试情况:

数据库版本为4.2.1.2

###########测试数据#########
obclient [SYS]> select  * from a; select  * from b;
+------+------+
| ID   | VA   |
+------+------+
|    1 | cc   |
|    2 | cc   |
| NULL | cc   |
+------+------+
3 rows in set (0.004 sec)

+------+------+
| ID   | VA   |
+------+------+
|    1 | cc   |
| NULL | cc   |
+------+------+
2 rows in set (0.006 sec)
##############测试语句##########
obclient [SYS]> desc select  * from a where va='cc' and id not in (select id from b );
+------------------------------------------------------------------------------------------+
| Query Plan                                                                               |
+------------------------------------------------------------------------------------------+
| ===========================================================                              |
| |ID|OPERATOR               |NAME    |EST.ROWS|EST.TIME(us)|                              |
| -----------------------------------------------------------                              |
| |0 |HASH RIGHT ANTI JOIN NA|        |1       |9           |                              |
| |1 |├─PX COORDINATOR       |        |1       |5           |                              |
| |2 |│ └─EXCHANGE OUT DISTR |:EX10000|1       |5           |                              |
| |3 |│   └─TABLE FULL SCAN  |B       |1       |4           |                              |
| |4 |└─TABLE FULL SCAN      |A       |2       |4           |                              |
| ===========================================================                              |
| Outputs & filters:                                                                       |
| -------------------------------------                                                    |
|   0 - output([A.ID], [A.VA]), filter(nil), rowset=16                                     |
|       equal_conds([A.ID = B.ID]), other_conds(nil)                                       |
|   1 - output([B.ID]), filter(nil), rowset=16                                             |
|   2 - output([B.ID]), filter(nil), rowset=16                                             |
|       is_single, dop=1                                                                   |
|   3 - output([B.ID]), filter(nil), rowset=16                                             |
|       access([B.ID]), partitions(p0)                                                     |
|       is_index_back=false, is_global_index=false,                                        |
|       range_key([B.__pk_increment]), range(MIN ; MAX)always true                         |
|   4 - output([A.VA], [A.ID]), filter([A.VA = cast('cc', VARCHAR2(1048576 ))]), rowset=16 |
|       access([A.VA], [A.ID]), partitions(p0)                                             |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],        |
|       range_key([A.__pk_increment]), range(MIN ; MAX)always true                         |
+------------------------------------------------------------------------------------------+
24 rows in set (0.072 sec)

通过这个简单的测试可以看出来OB在4.x的优化器中已经把这个小问题优化掉了。

结论:

首先先说明下,该问题只是因为判断逻辑的原因影响了执行计划的选择,导致可以选择的执行路径变少了,所以可能无法选择最优的执行路径,并不会影响数据的结果。

从OB 4.x的测试结果来看,OB的优化器(当然不止优化器,整个产品都在进步)在不断的进步和优化, 也希望国产化数据库早日成熟。

行之所向,莫问远方。

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

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

相关文章

卷径计算(膜厚叠加+数值积分器应用博途PLC SCL代码)

VN积分法卷径计算的其它方法,可以参考下面文章链接: 1、VN积分法卷径计算FB https://rxxw-control.blog.csdn.net/article/details/131612206https://rxxw-control.blog.csdn.net/article/details/1316122062、PLC数值积分器 https://rxxw-control.blog.csdn.net/article/…

【刷题训练】LeetCode:557. 反转字符串中的单词 III

557. 反转字符串中的单词 III 题目要求 示例 1&#xff1a; 输入&#xff1a;s “Let’s take LeetCode contest” 输出&#xff1a;“s’teL ekat edoCteeL tsetnoc” 示例 2: 输入&#xff1a; s “Mr Ding” 输出&#xff1a;“rM gniD” 思路&#xff1a; 第一步&am…

Cassandra 集群安装部署

文章目录 一、概述1.官方文档2. 克隆服务器3.安装说明4.安装准备4.1.安装 JDK 114.2.安装 Python4.3.下载文件 二、安装部署1.配置 Cassandra2.启动 Cassandra3.关闭Cassandra4.查看状态5.客户端连接服务器6.服务运行脚本 开源中间件 # Cassandrahttps://iothub.org.cn/docs/m…

TikTok新手如何起号?环境因素与内容创新技巧

相信很多刚入行的TikTok玩家都遇到过一个难题&#xff0c;那就是账号权重低&#xff0c;播放量在个位数徘徊&#xff0c;其实都是因为还没起号&#xff01;那么具体如何起号呢&#xff1f;下面小编也给大家分享一下技巧。 一、如何起号 1、明确注册 TikTok 账号的目的 无论是…

嵌入式3-14

1、整理思维导图 2、重写链表的代码 3、实现链表&#xff0c;按值查找返回位置的功能&#xff0c;按位置查找返回值&#xff0c;释放单链表&#xff0c;链表逆置 node_p create_link_list()//创建头结点 { node_p p(node_p)malloc(sizeof(node)); if(pNULL) { …

python中的常用模块

os和sys模块 os和sys是Python标准库中两个非常重要的模块&#xff0c;它们提供了丰富的方法来与Python解释器以及操作系统交互。 os模块 os模块提供了许多函数&#xff0c;用于处理文件和目录等操作系统任务&#xff0c;如路径管理、执行命令、获取进程信息等。 常用方法&a…

计算机网络-第7章 网络安全(1)

主要内容&#xff1a;安全威胁与问题、对称密钥密码体制和公钥密码体制、数字签名与鉴别、网络层和运输层安全协议、应用层电子邮件、系统安全&#xff1a;防火墙与入侵检测 当网络中的用户都来自社会各个阶层和部门时&#xff0c;网络中存储和传输的数据需要保护。 7.1 网络安…

【python开发】并发编程(上)

并发编程&#xff08;上&#xff09; 一、进程和线程&#xff08;一&#xff09;多线程&#xff08;二&#xff09;多进程&#xff08;三&#xff09;GIL锁 二、多线程开发&#xff08;一&#xff09;t.start()&#xff08;二&#xff09;t.join()&#xff08;三&#xff09;t.…

深入了解栈和队列

小伙伴们&#xff0c;今天我们来继续学习数据结构的第二部分内容&#xff0c;就是栈和队列了。那么栈和队列有什么用呢&#xff1f; 栈和队列是两种重要的线性结构。从数据结构的角度看&#xff0c;栈和队列也是线性表&#xff0c;其特殊性在于栈和队列的基本操作是线性表操作…

Linux系统部署Swagger Editor结合内网穿透实现公网管理本地接口文档

文章目录 Swagger Editor本地接口文档公网远程访问1. 部署Swagger Editor2. Linux安装Cpolar3. 配置Swagger Editor公网地址4. 远程访问Swagger Editor5. 固定Swagger Editor公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xf…

数据结构:图的存储与遍历(待续)

图&#xff08;Graph&#xff09;是一种较线性表和树更为复杂的非线性结构。在图结构中&#xff0c;对结点&#xff08;图中常称为顶点&#xff09;的前驱和后继个数不加限制&#xff0c; 即结点之间的关系是任意的。 一、基本概念和一般结论 因为一条边关联两个顶点&#xff0…

计算机服务器中了devos勒索病毒怎么解密,devos勒索病毒解密工具流程

随着网络技术的不断发展与更新&#xff0c;越来越多的企业利用网络开展了各项工作业务&#xff0c;网络也为企业提供了极大便利&#xff0c;大大提高了办公效率。但网络是一把双刃剑&#xff0c;企业的数据安全问题一直是企业关心的主要话题&#xff0c;近日&#xff0c;云天数…

如何在Windows搭建WebDav服务,并外网可访问

目录 1. 安装IIS必要WebDav组件 2. 客户端测试 3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网 3.1 打开Web-UI管理界面 3.2 创建隧道 3.3 查看在线隧道列表 4. 公网远程访问 4.1 浏览器访问测试 4.2 映射本地盘符访问 4.3 安装Raidrive客户端 总结&…

由世界第一个AI软件工程师Devin引发的热潮背后----程序员到底会不会被代替?AI发展至如今是否初衷已变?

目录 一.Devin的登场是突破也是导火索 二.Devin的"逆天"能力 1、端到端构建和部署程序 2、自主查找并修复bug 3、训练和微调自己的AI模型 4、修复开源库 5、成熟的生产库也能做贡献 6、学习能力 三.Devin的出现甚至整个AI领域的进步,编程还有未来吗? 1.业…

机试:蛇形矩阵

问题描述: 代码示例: //蛇形矩阵 #include <bits/stdc.h> using namespace std;int main(){int n;cout << "输入样例" << endl; cin >> n;int k 1; for(int i 0; i < n; i){if( i %2 0){//单数行for(int j 0; j < n; j){ cout &…

国际前十正规外汇实时行情走势app软件最新排名(综合版)

外汇交易&#xff0c;作为当今世界金融市场上一个重要的板块&#xff0c;备受关注和热议。随着金融市场的日益发展&#xff0c;外汇交易也发展成为一个新兴的投资交易渠道。为了更好地满足投资者对外汇市场的需求&#xff0c;外汇实时行情走势app软件应运而生&#xff0c;它为投…

字符指针

1、字符指针 在指针的类型中我们知道有一种指针类型为字符指针 char* 一般使用方式&#xff1a; 还有使用方式如下&#xff1a; 注意观察区别&#xff1a;%C 与 %S &#xff1a; 这种方式是将字符串的首地址放到指针中&#xff0c;通过指针可以找到该字符串&#xff08;千万不要…

配置华为交换机环路检测案例

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系&#xff01; ①微思网络&#xff0c;始于2002年&#xff01;专注IT认证培训22年。 ② 领取学习资料/课程咨询&#xff1a;小美老师&#xff08;wx&#xff09;&…

使用采购管理软件构建更高效的采购模式

采购流程是企业整个采购部门的关键要素。无论企业规模大小&#xff0c;构建采购流程的模式都将直接影响企业控制成本、管理风险和保持流程弹性的能力。 下面我们将解释采购模式的类型、优缺点&#xff0c;以及如何确定哪种模式最适合你的采购部门。 集中采购的优缺点 在集中采…

关于腾讯云服务器“地域”选择,地域四点因素请牢记!

腾讯云服务器地域怎么选择&#xff1f;不同地域之间有什么区别&#xff1f;腾讯云哪个地域好&#xff1f;地域选择遵循就近原则&#xff0c;访客距离地域越近网络延迟越低&#xff0c;速度越快。腾讯云百科txybk.com告诉大家关于地域的选择还有很多因素&#xff0c;地域节点选择…