通过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.mzph.cn/news/744791.shtml

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

相关文章

C#,数值计算,矩阵相乘的斯特拉森(Strassen’s Matrix Multiplication)分治算法与源代码

Volker Strassen 1 矩阵乘法 矩阵乘法是机器学习中最基本的运算之一,对其进行优化是多种优化的关键。通常,将两个大小为N X N的矩阵相乘需要N^3次运算。从那以后,我们在更好、更聪明的矩阵乘法算法方面取得了长足的进步。沃尔克斯特拉森于1969年首次发表了他的算法。这是第…

【刷题】双指针进阶

请看入门篇 &#xff1a;双指针入门 送给我们一句话&#xff1a; 如今我努力奔跑&#xff0c;不过是为了追上那个曾经被寄予厚望的自己 —— 约翰。利文斯顿 双指针进阶 Leetcode 611 有效三角形的个数Leetcode LCR179.查找总价格为目标值的两个商品Leetcode 15.三数之和Thanks…

手把手教你使用Python第三方模块

1.第三方模块 一般是别人解决特定问题的功能进行了封装&#xff0c;可以通过安装直接使用 注意 第三方模块需要先安装&#xff0c;才能使用 常见的安装方式&#xff1a;通过pip工具或者通过pycharm编辑器进行安装 2.pip指令安装 pip -V # 查看pip的版本 pip 23.2.1 fr…

基于PHP的数字化档案管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的数字化档案管理系统 一 介绍 此数字化档案管理系统基于原生PHP&#xff0c;MVC架构开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 php(mvc)mysqlbootstrapphpstudyvscode 二 功能 …

网络原理(网络协议初识)

目录 1.网络通信基础 1.1IP地址 1.2端口号 1.3认识协议 1.4五元组 1.5 协议分层 2.TCP/IP五层&#xff08;或四层&#xff09;模型 2.1网络设备所在分层 2.2网络分层对应 3.封装和分用 1.网络通信基础 网络互连的目的是进行网络通信&#xff0c;也即是网络数据传输&#…

手写简易操作系统(九)--实现打印函数

前情提要 前面我们已经进入内核程序了&#xff0c;中间穿插了一点特权级的知识&#xff0c;现在我们开始准备一个打印函数 很不幸&#xff0c;还有汇编程序 一、C调用规约 因为涉及到C与汇编的联合编程&#xff0c;我们这里简述一下调用规约&#xff0c;调用规约就是约定参…

Node.js 学习笔记 fs、path、http模块;模块化;包;npm

Node.js学习 Node.js一、定义1.前端工程化2.Node.js 为何能执行 JS&#xff1f;3.安装nodejs、删除之前的nodejs4.使用 Node.js 二、fs模块 \- 读写文件三、path 模块案例 - 压缩前端html四、HTTP相关URL中的端口号常见的服务程序http 模块-创建 Web 服务案例&#xff1a;浏览时…

Python二级备考

考试大纲如下&#xff1a; 基本要求 考试内容 考试方式 比较希望能直接刷题&#xff0c;因为不懂的比较多可能会看视频。 基础操作刷题&#xff1a; 知乎大头计算机1-13题 import jieba txtinput() lsjieba.lcut(txt) print("{:.1f}".format(len(txt)/len(ls)…

XIAO ESP32S3部署Edge Impulse模型

在上一篇文章中我们介绍了如何使用edge impulse训练一个图片分类模型并导出arduino库文件。在这篇文章中我们将介绍如何在esp32s3中部署这个训练好的图片分类模型。 添加进Arduino库 有两种方法将下载的文件添加进Arduino库。 在Arduino IDE程序中&#xff0c;转到项目选项卡…

✅技术社区—集成xxl-job实现定时任务的管理和执行

xxl-job是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。(其中XXL是作者许雪里的简称) xxl-job是对老牌调度平台Quartz进行的封装。在开始介绍xxl-job之前我来先简单…

Jmeter —— jmeter中元件的执行顺序作用域

元件的执行顺序&#xff1a; 配置元件-->前置处理程序-->定时器-->取样器-->后置处理程序&#xff08;除非Sampler 得 到的返回结果为空&#xff09;-->断言&#xff08;除非Sampler 得到的返回结果为空&#xff09;-->监听器 &#xff08;除非Sampler 得到…

【Redis系列】深入了解 Redis:一种高性能的内存数据库

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

微信小程序-day01

文章目录 前言微信小程序介绍 一、为什么要学习微信小程序?二、微信小程序的历史创建开发环境1.注册账号2.获取APPID 三、下载微信开发者工具1.创建微信小程序项目2.填写相关信息3.项目创建成功 四、小程序目录结构项目的主体组成结构 总结 前言 微信小程序介绍 微信小程序&…

新 树莓派4B 温湿度监测 基于debian12的树莓派OS

前言 本文旨在完成通过外接温湿度传感器至树莓派使得树莓派不断记录并存储温湿度数据 这个领域有很多文章&#xff0c;但是部分文章已经缺乏了时效性&#xff0c;在最新系统不适用&#xff0c;本文目前适用 硬件 硬件连接 温湿度传感器常选用DHT11和DHT22&#xff0c;淘宝…

HelpLook AI 升级!一键生成SEO设置关键要素

| 现代SEO营销为何选择与AI同行 众多企业面临SEO优化日趋复杂的挑战&#xff0c;投入的时间和资源不断攀升。然而&#xff0c;HelpLook利用AI技术&#xff0c;一键完成SEO关键词和描述的生成&#xff0c;无需额外付费或繁琐操作&#xff0c;轻松提升网站曝光和点击率&#xff…

Android 学习之追踪应用的安装情况

先上结论&#xff0c;急用的话直接看结论 结论一、借助 API 读取安装信息&#xff0c;然后上报二、借助手动埋点&#xff0c;然后上报三、对比 前提过程 结论 一、借助 API 读取安装信息&#xff0c;然后上报 通过 PackageManager 的 API&#xff0c;我们可以得知自身应用安装…

【SSM】任务列表案例 基本CRUD SSM整合

文章目录 一、案例功能预览二、接口分析三、前端工程导入四、后端程序实现和测试4.1 准备4.2 功能实现4.2.1 分页查询显示4.2.2 添加计划4.2.2 删除计划4.2.3 修改计划 4.3 前后联调 一、案例功能预览 Github 地址 &#xff1a; ssm-integration-part 二、接口分析 学习计划…

【话题】2024年AI辅助研发趋势,有那些应用领域

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读文章&#xff01; 此篇是【话题达人】系列文章&#xff0c;这一次的话题是《2024年AI辅助研发趋势》 目录 背景概念实践医药领域汽车设计领域展望未来文章推荐 背景 随着人工智能技术的持续发展与突破&#xff0c;2024年AI辅…

BI数据分析案例详解:零售人货场分析该怎么做?

在当今快节奏、高竞争的商业环境中&#xff0c;人货场分析已成为企业成功的关键因素之一。科技的进步和数据的日益丰富使得企业对人流、货物流和场地布局的深入洞察变得愈发重要。通过科学的人货场分析&#xff0c;企业能更好地理解顾客行为、优化供应链、提高运营效率&#xf…

【wine】WINEDEBUG 分析mame模拟器不能加载roms下面的游戏 可以调整参数,快速启动其中一个游戏kof98

故障现象&#xff0c;MAME启动后&#xff0c;游戏都没有识别 添加日志输出&#xff0c;重新启动wine #!/bin/bashexport WINEPREFIX$(pwd)/.wine export WINESERVER$(pwd)/bin/wineserver export WINELOADER$(pwd)/bin/wine export WINEDEBUG"file,mame,warn,err"…