半连接转内连接 | OceanBase SQL 查询改写

查询优化器是关系型数据库系统的核心模块,是数据库内核开发的重点和难点,也是衡量整个数据库系统成熟度的“试金石”。为了帮助大家更好地理解 OceanBase 查询优化器,我们撰写了查询改写系列文章,带大家更好地掌握查询改写的精髓,熟悉复杂 SQL 的等价性,写出高效的 SQL。本文将重点和大家介绍半连接转内连接,更多文章欢迎进入【OceanBase SQL 查询改写专题】 查看。

引言

查询分析中经常使用子查询语句,数据库为了提高子查询的执行性能,往往会把子查询语句改写成半连接(子查询提升方法参见本系列第二篇:子查询提升首篇)。

例如,我们需要查询2022-08-01到2022-08-02之间已排片的电影,可以通过IN子查询检查电影是否在排片期内。查询SQL如Q1所示。

-- 影片表
MOVIE(movie_id primary key, movie_name, release_date)
-- 排片表
PLAY(play_id, movie_id, play_time, price, seats)


Q1:
SELECT movie_name
FROM   movie
WHERE  movie_id IN (SELECT movie_id
                    FROM   play
                    WHERE  play_time BETWEEN DATE'2022-08-01' 
                                      AND DATE'2022-08-02');
                    
Q2:
SELECT movie_name
FROM   movie LEFT SEMI
JOIN   (
              SELECT movie_id
              FROM   play
              WHERE  play_time BETWEEN date'2022-08-01' 
                                AND    date'2022-08-02' )play
ON     movie.movie_id = play.movie_id;

对于查询Q1,OceanBase会做子查询提升改写,改写成等价的查询Q2,使用半连接来计算子查询。对于新的查询,优化器可以选择hash left semi join、hash right semi join、merge left semi join、merge right semi join、nested loop left semi join五种连接算法执行。下图展示了其中一种执行计划。

Query Plan: 
==========================================
|ID|OPERATOR      |NAME |EST. ROWS|COST  |
------------------------------------------
|0 |HASH SEMI JOIN|     |30       |910000|
|1 | TABLE SCAN   |MOVIE|1000000  |460000|
|2 | SUBPLAN SCAN |PLAY |30       |46    |
|3 |  TABLE SCAN  |PLAY |30       |46    |
==========================================

考虑一种业务场景:movie表的数据量达100w,2022-08-01到2022-08-02之间已排片的电影约30部。上面五种连接算法都需要扫描movie表的全部数据,扫描成本比较高。而我们知道movie表的主键为movie_id,如果我们能够先查询出2022-08-01到2022-08-02之间已排片的movie_id,再去movie表查询movie_name,就能够使用movie表的主键索引,执行30次主键索引扫描即可完成查询。

为了能够按照最优计划执行Q1查询,我们需要以play表作为驱动表,并且使用index nested loop join的算法,把movie_id的连接条件转换为movie表的索引扫描条件。计划如下所示。

Query Plan: 
=====================================================
|ID|OPERATOR                   |NAME |EST. ROWS|COST|
-----------------------------------------------------
|0 |NESTED LOOP RIGHT SEMI JOIN|     |30       |91  |
|1 | SUBPLAN SCAN              |PLAY |30       |96  |
|2 |  TABLE SCAN               |PLAY |30       |96  |
|3 | TABLE GET                 |MOVIE|1        |46  |
=====================================================

但我们知道,数据库没有NESTED LOOP RIGHT SEMI JOIN的算法实现,那我们要怎样改写这条SQL,使数据库能够支持这种优化计划呢?为此,OceanBase引入了一个改写规则:半连接转内连接,将满足一定条件的半连接查询转换为内连接查询,优化器就可以针对上述场景生成最优的index nested loop join计划。

半连接转内连接

介绍半连接转内连接规则前,我们先了解下半连接的执行逻辑。还是以Q1为例进行说明,从movie表中读取一行数据,然后从play表内查找指定movie_id的数据,如果存在,则执行数据,否则不输出。从描述中我们可以知道,对于movie表中给定的一行数据,无论play表存在多少条数据与指定的movie_id相同,查询都只输出一行数据。

而内连接对于符合条件的每一条数据都会输出,也就是说,如果半连接直接转内连接,执行结果可能会重复输出多次。为了保证改写不改变查询语义,我们需要对play的movie_id去重,保证movie表的每行数据在play表中只匹配一行数据,改写后的查询如Q3所示。

Q3:
SELECT movie_name
FROM   movie INNER JOIN
JOIN   (
              SELECT DISTINCT movie_id
              FROM   play
              WHERE  play_time BETWEEN date'2022-08-01' 
                                AND    date'2022-08-02' )play
ON     movie.movie_id = play.movie_id;

对于新的查询Q3,优化器可以尝试movie hash join play、play hash join movie、movie merge join play、 play merge join movie、movie nested loop join play、play nested loop join movie这六种连接算法执行,比原来多了一种。此时,优化器可以生成之前描述的最优计划。

================================================
|ID|OPERATOR              |NAME |EST. ROWS|COST|
------------------------------------------------
|0 |NESTED-LOOP JOIN      |     |30       |46  |
|1 | SUBPLAN SCAN         |PLAY |30       |46  |
|2 |  MERGE DISTINCT      |     |30       |46  |
|3 |   SORT               |     |30       |46  |
|4 |    TABLE SCAN        |PLAY |30       |46  |
|5 | TABLE GET            |MOVIE|1        |7   |
================================================

注意到改写之后的查询比原来的查询多了一次去重计算,Q3查询并不是在所有场景下都比Q2查询更优,因此,OceanBase的半连接转内连接改写是一种基于代价的改写,即优化器会对比改写前后最优计划的代价,如果代价降低了,才会应用改写,否则不会改写查询。

优化点

上文我们介绍了半连接转内连接主要是增加去重计算来保证语义的正确性,也正因为增加了去重计算,改写之后的查询并不总是比改写之前的查询更优。

我们可以思考一下,是否所有场景都需要加去重计算?答案是否定的,在有些场景下,我们可以把半连接直接转成内连接,例如:play表的movie_id本身就有唯一约束,或者play表只有一行数据满足条件。在这些场景下,我们可以不添加去重计算,这也意味着改写之后的查询总是比改写之前的查询更优,不需要额外比较代价。

改写陷阱

在之前的介绍中,我们没有说明数据类型对改写规则的影响,实际上半连接转内连接对数据类型是有要求的。通过一个例子说明,对于查询Q4,如果需要改写成内连接,改写的SQL如Q5所示。

create table t1(c1 int);
insert into t1 values(0);
create table t2(c1 varchar(20));
insert into t2 values('0.0');
insert into t2 values('0.1');


Q4:
SELECT *
FROM   t1
WHERE  c1 IN (SELECT c1
              FROM   t2); 
Q5:
SELECT t1.c1
FROM   t1
       INNER JOIN (SELECT DISTINCT c1
                   FROM   t2)t2
               ON t1.c1 = t2.c1; 

上面的改写正确吗?对于Q4,结果是一行数据:0,对于Q5,结果是两行数据:0, 0。为什么呢?在对t2表的c1列去重时,使用的是varchar(20)类型,'0.0'与'0.1'属于不同的数据,不会发生去重操作,与t1表连接时需要把varchar(20)类型的数据转换成int类型比较,此时'0.0'与'0.1'转换成了0与0,导致执行结果不正确。

为了避免数据类型影响改写的正确性,我们需要在改写时,对数据类型做适当的处理,你可以思考一下怎样是正确的改写查询。

总结

本文主要介绍OceanBase的半连接转内连接改写,以及这个改写的优化点、容易被忽略的错误。OceanBase会把满足一定条件的半连接转换成内连接,使优化器能够尝试更多的计划,生成的查询计划可能更优。

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

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

相关文章

imx6ull qt多页面控制系统(正点原子imx系列驱动开发)

开题答辩完了也考完了四六级,赶紧来更新一下一个月前留下的坑吧 QAQ首先,因为毕业设计需要用到这些知识所以就从网络上找了一个智能车机系统,借鉴了一下大佬的项目思路,缝缝补补一个月终于完成了这一内容。 在这里先感谢从两位大佬…

Intel-ECI之Codesys PLC + Ethercat 远端IO + Codesys IDE编程

目录 一、 准备工作 二、安装Codesys 软件 PLC 三、 使用Codesys IDE 编程测试 CODESYS* 是领先的独立于制造商的 IEC 61131-3 自动化软件,适用于工程控制系统。它用于 Intel Edge Controls for Industrial(Intel ECI 或 ECI),…

vscode的keil assistant 中搜索不到全局变量

搜不到 但是在包含的文件中输入 ../../../,就是全局搜索的结果 我的文件结构是:\Desktop\LVGL文件系统移植(lvgl8.3)\Projects\MDK-ARM 盲猜是keil assistant 当前文件夹打开的时候是进入到了MDK-ARM文件夹层次&…

Unity A*算法实现+演示

注意: 本文是对基于下方文章链接的理论,并最终代码实现,感谢作者大大的描述,非常详细,流程稍微做了些改动,文末有工程网盘链接,感兴趣的可以下载。 A*算法详解(个人认为最详细,最通俗易懂的一…

格式工厂,各类文件格式转换

今天给大家推荐一个老牌的软件格式工厂。这个软件早就能支持转换视频、音频、图片、文档等市面上主流格式的软件了,现在也很能打。 格式工厂 各类文件格式转换 软件无需安装,打开这个图标就能直接使用。 屏幕录像功能还是非常强大的,可以全屏…

Java web的发展历史

目录 前言: 一.Model I和Model II 1.Model I开发模式 ​编辑 2.Model II开发模式 二. MVC模式 前言: 该篇文章主要介绍了Java web的发展历史,以及MVC相关内容 一.Model I和Model II 1.Model I开发模式 Model1的开发模式是&#xff…

Pyqt6在lineEdit中输入文件名称并创建或删除JSON文件

1、创建JSON文件 代码 import osdef addModulekeyWordFile(self):if "" ! self.lineEdit_module.text():moduleFile self.lineEdit_module.text() .jsonelse:self.toolLogPrinting(请输入模块名称)returnfilePath modulekeyWordFileDir moduleFileif os.path.e…

练习题 最小栈

最小栈 最小栈 class MinStack {private Stack<Integer> stack;private Stack<Integer> minstack;public MinStack() {stacknew Stack<>();minstacknew Stack<>();}public void push(int val) {stack.push(val);if(minstack.empty()){minstack.push(…

概率论得学习和整理32: 用EXCEL描述正态分布,用δ求累计概率,以及已知概率求X的区间

目录 1 正态分布相关 2 正态分布的函数和曲线 2.1 正态分布的函数值&#xff0c;用norm.dist() 函数求 2.2 正态分布的pdf 和 cdf 2.3 正态分布的图形随着u 和 δ^2的变化 3 正态分布最重要的3δ原则 3.0 注意&#xff0c;这里说的概率一定是累计概率CDF&#xff0c;而…

食家巷大烤馍:岁月沉淀下的麦香传奇

在繁华都市的街角巷尾&#xff0c;隐藏着许多不为人知的美食宝藏&#xff0c;食家巷大烤馍便是其中之一。它宛如一位低调的美食大师&#xff0c;默默散发着独特的魅力&#xff0c;用最质朴的味道&#xff0c;征服着每一个过往食客的味蕾。 初见食家巷大烤馍&#xff0c;你会被…

wxWidgets使用wxStyledTextCtrl(Scintilla编辑器)的正确姿势

开发CuteMySQL/CuteSqlite开源客户端的时候&#xff0c;需要使用Scintilla编辑器&#xff0c;来高亮显示SQL语句&#xff0c;作为C/C领域最成熟稳定又小巧的开源编辑器&#xff0c;Scintilla提供了强大的功能&#xff0c;wxWidgets对Scintilla进行包装后的是控件类&#xff1a;…

【基础还得练】数值分析中的样条插值

什么是三次样条&#xff08;Cubic Spline&#xff09;&#xff1f; 三次样条&#xff08;Cubic Spline&#xff09;是一种常用于数据插值和曲线拟合的数学方法&#xff0c;它利用多个三次多项式函数来平滑连接数据点&#xff0c;使得拟合曲线不仅通过所有数据点&#xff0c;同时…

AMS1117芯片驱动电路·降压芯片的驱动电路详解

目录 AMS1117常见封装 AMS1117不同系列 AMS1117驱动电路 参考数据手册 编写不易&#xff0c;仅供学习&#xff0c;请勿搬运&#xff0c;感谢理解 相同LDO芯片驱动专栏文章 LM7805系列降压芯片驱动电路降压芯片驱动电路详解-CSDN博客 ME6211C系列降压芯片驱动电路降压芯片…

[项目代码] YOLOv8 遥感航拍飞机和船舶识别 [目标检测]

项目代码下载链接 &#xff1c;项目代码&#xff1e;YOLO 遥感航拍飞机和船舶识别&#xff1c;目标检测&#xff1e;https://download.csdn.net/download/qq_53332949/90163939YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为…

《Qt Creator 4.11.1 教程》

《Qt Creator 4.11.1 教程》 一、Qt Creator 4.11.1 概述&#xff08;一&#xff09;简介&#xff08;二&#xff09;界面构成 二、常用设置指南&#xff08;一&#xff09;环境设置&#xff08;二&#xff09;文本编辑器设置&#xff08;三&#xff09;构建和运行设置 三、构建…

探索未知,乐享惊喜 —— 盲盒APP开发,开启您的个性化惊喜之旅!

在这个瞬息万变的数字时代&#xff0c;我们总在寻找那些能触动心灵、带来无限可能的小确幸。为了满足您对未知的好奇与对惊喜的渴望&#xff0c;我们匠心打造了一款全新的盲盒APP&#xff0c;旨在为您的生活增添一抹不同寻常的色彩&#xff0c;让每一次打开都是一次全新的探索与…

前端和后端解决跨域问题的方法

目前很多java web开发都是采用前后端分离框架进行开发&#xff0c;相比于单体项目容易产生跨域问题。 一、跨域问题CORS 1.什么是跨域问题&#xff1f; 后端接收到请求并返回结果了&#xff0c;浏览器把这个响应拦截了。 2.跨域问题是怎么产生的&#xff1f; 浏览器基于同源…

c#上班,上学,交通方式接口

using System;namespace INTERFACE {abstract class Person{public string Name { get; set; }public int Age { get; set; }public virtual void ShowInfo(){Console.WriteLine($"Name: {Name}, Age: {Age}");}}// 接口 IWorkinterface IWork{void GotoCompany();}/…

SQL血缘解析

Druid 作为使用率特别高的的数据库连接池工具,在具备完善的连接池管理功能外,同时Druid 的 SQL解析功能可以用来防止 SQL注入等安全风险。通过对 SQL 语句进行解析和检查,Druid 可以识别并阻止潜在的恶意 SQL 语句执行,黑名单(阻止特定的 SQL 语句执行)、白名单(仅允许特…

常用的JVM启动参数有哪些?

大家好&#xff0c;我是锋哥。今天分享关于【常用的JVM启动参数有哪些?】面试题。希望对大家有帮助&#xff1b; 常用的JVM启动参数有哪些? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 JVM启动参数用于配置Java虚拟机&#xff08;JVM&#xff09;的运行时行为…