通过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 awhere a.cccdate is not nulland 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=false2 - 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=16 - 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 awhere a.cccdate is not nulland a.cccdate <= (sysdate - 30)and a.cccdate > (sysdate - 31)and a.status = 2and 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=15 - 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 awhere a.cccdate is not nulland 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=15 - 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.hqwc.cn/news/540145.html

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

相关文章

【数据结构】单向链表的创建及4种应用

目录 前言 自定义“单向”链表类 1. 自定义一个链表类&#xff0c;并完成“初始化链表”、“添加元素&#xff08;头插法/尾插法&#xff09;”、“计算链表长度”操作&#xff1b; 自定义链表 向链表中插入元素&#xff08;头插法&#xff09; 向链表中插入元素&#xf…

爬虫入门到精通_框架篇16(Scrapy框架基本使用)_名人名言的抓取

1 目标站点分析 抓取网站&#xff1a;http://quotes.toscrape.com/ 主要显示了一些名人名言&#xff0c;以及作者、标签等等信息&#xff1a; 点击next&#xff0c;page变为2&#xff1a; 2 流程框架 抓取第一页&#xff1a;请求第一页的URL并得到源代码&#xff0c;进行下…

如何使用第三方接入淘宝商品详情(主图,详情图)

1、找到可用的API接口&#xff1a;首先&#xff0c;需要找到支持查询商品信息的API接口。这些信息通常可以在电商平台的官方文档或开发者门户网站上找到。 2、注册并获取API密钥&#xff1a;在使用API接口之前&#xff0c;需要注册并获取API密钥。API密钥是识别身份的唯一标识符…

HDFS的架构优势与基本操作

目录 写在前面一、 HDFS概述1.1 HDFS简介1.2 HDFS优缺点1.2.1 优点1.2.2 缺点 1.3 HDFS组成架构1.4 HDFS文件块大小 二、HDFS的Shell操作&#xff08;开发重点&#xff09;2.1 基本语法2.2 命令大全2.3 常用命令实操2.3.1 上传2.3.2 下载2.3.3 HDFS直接操作 三、HDFS的API操作3…

【Java基础】IO流(二)字符集知识

目录 字符集知识 1、GBK字符集 2、Unicode字符集&#xff08;万国码&#xff09; 3、乱码 4、Java中编码和解码的方法 字符集知识 字符&#xff08;Character&#xff09;&#xff1a;在计算机和电信技术中&#xff0c;一个字符是一个单位的字形、类字形单位或符号的基本信…

揭秘动态住宅代理:如何合法获取全球数据洞察

文章目录 写在前面代理网络的崛起什么是代理网络&#xff1f;动态住宅代理的革命为什么选择亮数据动态代理 如何利用采集工具获取全球亮数据写在最后 写在前面 随着互联网技术的发展&#xff0c;数据已经成为企业生存和发展的不可或缺的资源。尤其在商业世界里&#xff0c;如何…

IAB视频广告标准《数字视频和有线电视广告格式指南》之 简介、目录及视频配套广告 - 我为什么要翻译介绍美国人工智能科技公司IAB系列(2)

写在前面 谈及到中国企业走入国际市场&#xff0c;拓展海外营销渠道的时候&#xff0c;如果单纯依靠一个小公司去国外做广告&#xff0c;拉渠道&#xff0c;找代理公司&#xff0c;从售前到售后&#xff0c;都是非常不现实的。我们可以回想一下40年前&#xff0c;30年前&#x…

什么是VPS?如何使用VPS?

什么是VPS&#xff1f;VPS有什么用&#xff1f; VPS是Virtual Private Server的缩写&#xff0c;中文则为虚拟专用服务器&#xff0c;VPS是利用虚拟服务器软件在一台物理服务器上创建多个相互隔离的小服务器&#xff0c;是托管在机房物理服务器上的虚拟机。每个VPS服务器都可分…

海格里斯HEGERLS智能托盘四向车系统为物流仓储自动化升级提供新答案

随着实体企业面临需求多样化、订单履行实时化、商业模式加速迭代等挑战&#xff0c;客户对物流仓储解决方案的需求也逐渐趋向于柔性化、智能化。作为近十年来发展起来的新型智能仓储设备&#xff0c;四向车系统正是弥补了先前托盘搬运领域柔性解决方案的空白。随着小车本体设计…

【C语言_C语言语句_复习篇】

目录 一、C语言的语句有哪些 1.1 空语句 1.2 表达式语句 1.3 函数调用语句 1.4 复合语句 1.5 控制语句 二、分支语句&#xff08;两种&#xff09; 1.1 if语句 1.1.1 普通分支语句(if、if_else) 1.1.2 嵌套if语句 1.1.3 else嵌套if两种写法的比较 1.1.4 else悬空问题 1.1.…

嵌入式毕业设计-基于智能家居灯光控制系统的设计与实现

项目介绍 技术&#xff1a;C语言、单片机等 在科技进步与社会发展的今天&#xff0c;智能家居这一概念慢慢走进人们的生活。照明控制作为智能家居的重要组成部分&#xff0c;它与人们日常生活的联系最为紧密&#xff0c;本文研究了利用ZigBee网络进行室内通信&#xff0c;克服…

美团大规模KV存储挑战与架构实践

KV 存储作为美团一项重要的在线存储服务&#xff0c;承载了在线服务每天万亿级的请求量&#xff0c;并且保持着 99.995% 的服务可用性。在 DataFunSummit 2023 数据基础架构峰会上&#xff0c;我们分享了《美团大规模 KV 存储挑战与架构实践》&#xff0c;本文为演讲内容的整理…