【MySQL·8.0·源码】subquery 子查询处理分析(二)

引文

在【MySQL·8.0·源码】subquery 子查询处理分析(一)中,已经介绍了 MySQL 子查询的语法树形式,并简单介绍了非相关 scalar 子查询的一些处理流程,本文将继续介绍更多子查询的处理流程。

本文后续以 “分析(一)” 来代替 “【MySQL·8.0·源码】subquery 子查询处理分析(一)”

假如对子查询不做任何优化处理,那么子查询应该怎么执行呢?

先来看一个 IN 子查询

Q1>: select * from t1 where a in (select pk from t2);

显然,先执行子查询,将 t2 的所有 pk 都查询出来,然后以所有的 pk 作为查询条件,检索 t1 中满足条件的记录返回即可。

再来看一个 Exists 子查询

Q2>: select * from t3 where exists (select * from t2 where t2.b=t3.a);

在 “分析(一)” 中已经知道其等同于

select * from t3 where WHERE 0 < (select count(*) from t2 where t2.b=t3.a)

所以只要检索遍历 t3 表记录,然后用 t3 一行行的 t3.a 值去充当 t2 的 select count(*) 条件,不为 0 则返回 t3 记录,否则跳过并继续下一行即可。

到此,IN 子查询和 Exists 子查询的大致执行过程已经清楚,那么就此执行会有什么问题?

  1. 假如 Q1 中,t2 表非常大,而 t1 表只有几行记录,也要遍历完 t2 表,代价很高
  2. 假如 Q2 中,t3 表非常大,而 t2 表满足条件的记录只有几行记录,也要遍历完所有的 t3 表,代价也很高

有没有发现,上述问题都是子查询按着严格的表的先后执行顺序引来的,那么如果子查询是否可以不按着严格的先后执行顺序来执行?

这就引申出了对子查询的各种优化策略。

去重策略

总统上,为了打破子查询的固有执行顺序,只有将子查询转换为 JOIN 操作,
但转换为 JOIN 操作后会引来什么问题? 例如:

Q3> select * from t1 where a in (select b from t2);

转换为 JOIN 之后

Q4> select * from t1 join t2 where t1.a = t2.b;

假如
t1 有 2 条记录,t1(a,b):(1,1),(2,2)
t2 有 4 条记录,t2(a,b):(1,1),(1,1),(2,2),(2,2)

则转换为 JOIN 之后结果为:

select * from t1 join t2 where t1.a = t2.b;
+------+------+------+------+
| a    | b    | a    | b    |
+------+------+------+------+
|    1 |    1 |    1 |    1 |
|    2 |    2 |    2 |    2 |
|    1 |    1 |    1 |    1 |
|    2 |    2 |    2 |    2 |
+------+------+------+------+
4 rows in set (0.00 sec)

而子查询结果为:

select * from t1 where a in (select b from t2);
+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    2 |    2 |
+------+------+

可以看到,如果做 JOIN 操作,可能导致问题:

  1. Q4 查询结果的 schema 比子查询 Q3 多,Q3 只需要 t1 表对应的列;
  2. Q4 查询结果可能会比子查询 Q3 更多,原因在于子查询 t2 有重复的记录
Q5> select b from t2;
+------+
| b    |
+------+
|    1 |
|    2 |
|    1 |
|    2 |
+------+

为了解决第 1 个问题,需要保证外表的 select list 中的 “*” 要在转成 JOIN 之前展开或者把 JOIN 换为 Semi-Join 即可,即表明只需要返回外表关系的 select list;
为了解决第 2 个问题,需要对结果进行去重 duplicate remove 即可,去重也可以选择对子查询去重,也可以选择对外表结果集去重:

  • 对子查询去重即去除 Q5 中重复的记录,即只返回 1,2,而不是 1,2,1,2,即可得到正确结果;
  • 对外表结果去重即去除 Q4 中重复的记录也能得到正确结果

MySQL 对子查询的优化策略总是围绕着这些点进行的

MySQL 优化策略

在 MySQL 中,对子查询的优化策略可以总结为:

  • Transform to join derived table
  • Transform to semi-join
    • Pullout as simple JOIN
    • FirstMatch
    • LooseScan
    • DuplicateWeedout
    • Materialization
      • MaterializeLookup
      • MaterializeScan
  • Exists
    • Transform IN to Exists
    • Exists/Not Exists
  • Materialize
    • MaterializeLookup

首先,对于特定的子查询,MySQL 支持将其子查询谓词转换为派生表来做 JOIN 操作
其次会尽可能尝试转换为 semi-join 来优化表的驱动顺序
MySQL 有一系列相关的开关进行控制(optimizer_switch)

subquery_to_derived=on;
semijoin=on;
loosescan=on
firstmatch=on
duplicateweedout=on
subquery_materialization_cost_based=on

Transform

在子查询诸多优化策略确定之前,在 prepare 阶段,需要将语法树进行 rewrite 改写,改写会改变整个
父子查询 query_expression 和 query_block 的关系,也可能将 where 和 on 条件进行整合重组,整个
带子查询的 Transform 大致处理流程为:

例如有查询语句:
SELECT #1 WHERE X IN (SELECT #2 WHERE Y IN (SELECT#3)) :

Query_block::prepare()(select#1-> Query_block::setup_conds() 在父查询块 IN 条件上执行 Item_exists_subselect::fix_fields()
        -> Query_block::prepare() 处理子查询(select#2-> 在IN条件上执行 fix_fields()
                  -> Query_block::prepare() 处理子查询(select#3<- Query_block::prepare()
             <-fix_fields() 中结束
             -> flatten_subqueries: 将 #3 合并到 #2<- 在 flatten_subqueries 中结束
         <- Query_block::prepare()
     <-fix_fields() 中结束
     -> flatten_subqueries: 将 #2 合并到 #1

从父查询块在解析条件到子查询条件时,不断递归到子查询块的 prepare 工作,再不断深入子查询条件解析,
如果可能,进一步递归到子查询 in 条件的下一个子查询的解析中。
在这里插入图片描述
整个工作自顶而下,从父查询一直到最底层子查询
然后自下往上的进行子查询相关优化的判断,可能则进行自底而上的进行 merge 合并,达到去除子查询并简化查询块的目的

Transform to join derived table

转换形式为

FROM [tables] WHERE ... AND/OR oe IN (SELECT ie FROM it) ...
变为
FROM (tables) LEFT JOIN (SELECT DISTINCT ie FROM it) AS derived
              ON oe = derived.ie WHERE ... AND/OR derived.ie IS NOT NULL ...

例如,对于上述的 Q3 转换为 join derived table 为:

select t1.* from t1 left join (select distinct b from t2) as derived 
                             on t1.a = derived.b where derived.b is not null;

对于 derived table 的 Transform,子查询需要满足以下条件,才能称为候选者

 Item_exists_subselect::is_derived_candidate(THD *thd)
  • 开关已开启,optimizer_switch = ‘subquery_to_derived=on’
  • 子查询是一个简单的查询(非集合运算,例如 union/intersect/except,非带有括号嵌套查询)
  • 如果是 [Not] Exists 子查询,子查询必须不带 group 或者 windows 函数
  • 子查询谓词是
    • 在 Where 子句中,非 On 子句
    • 子查询谓词必须直接链接在根条件谓词 AND 或 OR 之下
  • 父查询块必须支持接受 semi-join(如果父查询是 update/delete 是不支持的)
  • 父查询块必须存在基表,因为转换之后的 derived 是通过 left join 连接到父查询块基表后的
  • 父查询块必须未禁止 semi-join 操作
  • 如果是 IN 谓词,IN 谓词的左操作数必须是确定性的(例如,非 random 函数)
  • not in/not = ,左右谓词可能为 NULLs 的都不允许做 semi-join

Transform

Query_block::transform_table_subquery_to_join_with_derived()

对于 derived table 转换,查询块会创建一个派生表添加到外部查询块上,派生表的名称一般为 “derived_%d_%d”,
后面两个数字为查询块的 id,例如把 select #3 子查询创建一个派生表添加到 select #2 查询块上,名称为 “derived_2_3”。

同时,子查询 IN 表达式会被替换为关联条件,例如 select #2 上的 Item_in_subselect 替换为 Item_func_eq(t2.a,derived_2_3.Name_exp_1)。
在这里插入图片描述

Transform to semi-join

还是以 Q3 为例,转换为 semi-join 为:

select t1.* from t1 semi join t2 where t1.a = t2.b;

对于 semi join 的 Transform,子查询需要满足以下条件,才能称为候选者

Item_exists_subselect::is_semijoin_candidate()
  • 启用了 semi join,optimizer_switch = ‘semijoin=on’
  • 子查询是简单的查询块(非集合运算,例如 union/intersect/except,非带有括号嵌套查询)
  • 子查询没有 group 聚集(显式的或隐式的)
  • 子查询没有 having
  • 子查询没有 window 窗口函数
  • 子查询谓词在 ON/WHERE 子句中,并且在根 AND 谓词下
  • 父查询块接受 semi-join(如果父查询是 update/delete 是不支持的)
  • 子查询不是不存在基表的查询,例如:select 1
  • 父子查询均无 straight join
  • 父查询块未禁止 semi-join
  • IN 谓词左侧是确定性的
  • not in/not = ,左右谓词可能为 NULLs 的都不允许做 semi-join
  • 引擎要么支持 anti-join,要么不是 anti-join
  • 如果存在 OFFSET,则需从第一行开始,LIMIT 不为 0

Transform

Query_block::convert_subquery_to_semijoin()

对于 semi join 转换,查询块会不断通过调用 convert_subquery_to_semijoin() 尝试将查询块的子查询谓词(比如说 IN 谓词)转换为
Table_ref 的半连接嵌套查询,

转换形式为:

  • IN
  SELECT ...
  FROM ot1 ... otN
  WHERE (oe1, ... oeM) IN (SELECT ie1, ..., ieM
                           FROM it1 ... itK
                          [WHERE inner-cond])
   [AND outer-cond]
  [GROUP BY ...] [HAVING ...] [ORDER BY ...]

会被转换为

  SELECT ...
  FROM (ot1 ... otN) SJ (it1 ... itK)
                     ON (oe1, ... oeM) = (ie1, ..., ieM)
                        [AND inner-cond]
  [WHERE outer-cond]
  [GROUP BY ...] [HAVING ...] [ORDER BY ...]
  • EXISTS
  SELECT ...
  FROM ot1 ... otN
  WHERE EXISTS (SELECT expressions
                FROM it1 ... itK
                [WHERE inner-cond])
   [AND outer-cond]
  [GROUP BY ...] [HAVING ...] [ORDER BY ...]

会被转换为

  SELECT ...
  FROM (ot1 ... otN) SJ (it1 ... itK)
                     [ON inner-cond]
  [WHERE outer-cond]
  [GROUP BY ...] [HAVING ...] [ORDER BY ...]
  • 同理 NOT IN、NOT EXISTS 会被转换为 Anti Join

用图示来展示之前 Q3 的转换如下在这里插入图片描述

Transform to exists

最终,当子查询无法称为 derived table 或 semi join 的候选者时,只能对子查询做 IN->Exists 转换查询
遍历查询块的子查询谓词,调用子查询谓词对应的 transform() 函数进行转换

Item_exists_subselect::transformer()
	Item_in_subselect::transformer()
	Item_allany_subselect::transformer()
	Item_in_subselect::transformer()
	Item_allany_subselect::transformer()

在 Item_in_subselect::transformer() 中,会做一些规则性的转换,例如:

  • 非表的 IN、NOT IN 子查询并且不带 group by、where、和 having 子句
    例如:

    select * from t1 where a  in (select 3)
    

    IN 子查询会被转换为 where t1.a = 3

  • 单列子查询,子查询左侧操作数为单列
    单列子查询会调用 Item_in_subselect::single_value_transformer,以尝试是否可能将子查询转换为 scalar 查询,形如:

    oe $cmp$ (SELECT ie FROM ... WHERE subq_where ... HAVING subq_having)
    

    可以尝试转换为:

    - oe $cmp$ (SELECT MAX(...) )  // 由 Item_singlerow_subselect 处理
    - oe $cmp$ \<max\>(SELECT ...)   // 由 Item_maxmin_subselect 处理
    

    如果不能转换,则使用 Item_in_optimzer 类处理子查询

  • 多列子查询,子查询左侧操作数为多列
    多列子查询会调用 Item_in_subselect::row_value_transformer(),根据规则对多列 IN/ALL/ANY 子查询做一定的重写,形如:

      (oe1, oe2, ...)  $cmp$ (SELECT ie1, ie2, ... FROM ... WHERE subq_where ...
                              HAVING subq_having)
    

    转换为类似形如:

    (l1, l2, l3) IN (SELECT v1, v2, v3 ... WHERE where) =>
          EXISTS (SELECT ... WHERE where and
                                   (l1 = v1 or is null v1) and
                                   (l2 = v2 or is null v2) and
                                   (l3 = v3 or is null v3)
                             HAVING is_not_null_test(v1) and
                                    is_not_null_test(v2) and
                                    is_not_null_test(v3))
    

    在 where 条件中,为每个左操作创建等值连接条件

对于子查询做完一定的 Transform 之后,就会赋予子查询临时性的执行策 Subquery_strategy::CANDIDATE_FOR_IN2EXISTS_OR_MAT,
即在 IN2EXISTS 转换或 Materialization 两种策略之中二选一,
然后在 optimize 阶段的 JOIN::make_join_plan()->JOIN::decide_subquery_strategy() 中最终决定走 Materialization 或者 EXISTS 执行策略。

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

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

相关文章

HTML5:七天学会基础动画网页6

CSS3自定义字体 ①&#xff1a;首先需要下载所需字体 ②&#xff1a;把下载字体文件放入 font文件夹里&#xff0c;建议font文件夹与 css 和 image文件夹平级 ③&#xff1a;引入字体&#xff0c;可直接在html文件里用font-face引入字体&#xff0c;分别是字体名字和路径 例…

【OrthoFinder】直系同源基因分析工具

目录 OrthoFinder工具介绍 OrthoFinder的安装方法 OrthoFinder使用方法 参数介绍 输入与输出 OrthoFinder结果解读 Comparative_Genomics_Statistics&#xff1a; Gene_Duplication_Events&#xff1a; Gene_Trees: Orthogroups&#xff1a; Orthogroup_Sequences&am…

【比较mybatis、lazy、sqltoy、lambda、操作数据 】操作批量新增、分页查询【一】

orm框架使用Lambda性能比较 环境&#xff1a; idea jdk17 spring boot 3.0.7 mysql 8.0测试条件常规对象 orm 框架是否支持xml是否支持 Lambda对比版本mybatis☑️☑️3.5.4sqltoy☑️☑️5.2.98lazy✖️☑️1.2.3-JDK17 数据库表(含有唯一性索引s_u) CREATE TABLE sys_u…

AGM CPLD (AGRV2K )的时钟(外部时钟和片上内部振荡器)

AGM CPLD &#xff08;AGRV2K &#xff09;的时钟(外部时钟和片上内部振荡器) 外部晶振 与 内部振荡器&#xff1a; mcu 和 cpld 联合编程时&#xff0c; 整颗芯片需要一颗外部晶振。 &#xff08;芯片有内部振荡器&#xff0c; 但误差较大&#xff0c; 校准后 5%以内误差&…

基于springboot实现企业员工绩效考评系统项目【项目源码+论文说明】

基于springboot实现企业员工绩效考评系统演示 摘要 时代的变化速度实在超出人类的所料&#xff0c;21世纪&#xff0c;计算机已经发展到各行各业&#xff0c;各个地区&#xff0c;它的载体媒介-计算机&#xff0c;大众称之为的电脑&#xff0c;是一种特高速的科学仪器&#xf…

【web | CTF】BUUCTF [HCTF 2018]WarmUp

天命&#xff1a;这题本地php代码是无法复现的 首先打开网站&#xff0c;啥也没有&#xff0c;查看源码 发现文件&#xff0c;打开访问一下看看&#xff0c;发现是代码审计 <?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page){$whit…

C语言-------指针进阶(2)

1.指针数组 指针数组表较简单&#xff0c;类比整型数组&#xff0c;字符数组&#xff0c;整型数组里面的元素都是整型变量&#xff0c;字符数组里面 的元素是字符类型&#xff0c;那么指针数组就是数组里面的每个元素都是指针类型&#xff0c;例如int*arr[5]就是一个 指针数…

18个惊艳的可视化大屏(第12辑):智慧校园与教育方向

智慧校园可视化大屏通过数据可视化技术&#xff0c;将学校各个方面的数据信息进行展示&#xff0c;可以提高信息公开透明度、优化校园管理、提高学生教育质量和提高校内活动宣传效果等。 1提高信息公开透明度&#xff1a; 通过大屏幕展示校园各个方面的数据信息&#xff0c;可…

Golang Vs Java:为您的下一个项目选择正确的工具

Java 首次出现在 1995 年&#xff0c;由 James Gosling 和 Sun Microsystems 的其他人开发的一种新编程语言。从那时起&#xff0c;Java 已成为世界上最受欢迎和广泛使用的编程语言之一。Java 的主要特点包括其面向对象的设计、健壮性、平台独立性、自动内存管理以及广泛的内置…

【InternLM 实战营笔记】OpenCompass大模型评测

随着人工智能技术的快速发展&#xff0c; 大规模预训练自然语言模型成为了研究热点和关注焦点。OpenAI于2018年提出了第一代GPT模型&#xff0c;开辟了自然语言模型生成式预训练的路线。沿着这条路线&#xff0c;随后又陆续发布了GPT-2和GPT-3模型。与此同时&#xff0c;谷歌也…

击鼓传花游戏

有N个小朋友围成一圈玩击鼓传花游戏&#xff0c;将小朋友编号为1-N&#xff0c;从1号开始传花&#xff0c;每次传3个&#xff0c;拿到花的小朋友表演节目后退出。任给N&#xff0c;问最后一个表演的小朋友编号是多少&#xff1f;例如&#xff1a;输入5&#xff0c;从1号开始传花…

Java网络通信TCP

目录 TCP两个核心类 服务端 1.用ServerSocker类创建对象并且手动指定端口号 2.accept阻塞连接服务端与客户端 3.给客户端提供处理业务方法 4.处理业务 整体代码 客户端 1.创建Socket对象&#xff0c;并连接服务端的ip与端口号 2.获取Socket流对象&#xff0c;写入数据…

基于springboot+vue的智能学习平台系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

2024年阿里云2核4G配置服务器测评_ECS和轻量性能测评

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

备战蓝桥杯---状态压缩DP基础2之TSP问题

先来一个题衔接一下&#xff1a; 与上一题的思路差不多&#xff0c;不过这里有几点需要注意&#xff1a; 1.因为某一列的状态还与上上一行有关&#xff0c;因此我们令f[i][j][k]表示第i行状态为j,第i-1行状态为k的最大炮兵数。 因此&#xff0c;我们可以得到状态转移方程&…

一篇文章吃透整个JVM,JVM超详细笔记

这里写目录标题 JVMJVM执行流程JVM执行流程 JVM内存模型1.堆区&#xff08;Heap&#xff09;2.虚拟机栈&#xff08;JVM Stacks&#xff09;3.本地方法栈&#xff08;Native Method Stacks&#xff09;4.程序计数器&#xff08;Program Counter Register&#xff09;5.方法区/元…

机器学习 -- Octave基本操作

场景 Octave语言是一种高级数值计算和数据可视化的开源软件。它提供了一种方便的方式来执行数值计算、数据分析和可视化&#xff0c;特别是在科学和工程领域中。今天学习了一下Octave的基本操作&#xff0c;记录一下。 下载 去Octave官网下载即可。octave下载可自行下载。 …

学习人工智能:Sora技术报告Video generation models as world simulators,2024.2

原文链接&#xff1a; Video generation models as world simulators (openai.com) 摘要&#xff1a; 我们探索了在视频数据上大规模训练生成模型。具体来说&#xff0c;我们在可变片长、分辨率和纵横比的视频和图像上联合训练文本条件扩散模型text-conditional diffusion mo…

案例介绍:信息抽取技术在汽车销售与分销策略中的应用与实践

一、引言 在当今竞争激烈的汽车制造业中&#xff0c;成功的销售策略、市场营销和分销网络的构建是确保品牌立足市场的关键。作为一名经验丰富的项目经理&#xff0c;我曾领导一个专注于汽车销售和分销的项目&#xff0c;该项目深入挖掘市场数据&#xff0c;运用先进的信息抽取…

【Mybatis】快速入门 基本使用 第一期

文章目录 Mybatis是什么&#xff1f;一、快速入门&#xff08;基于Mybatis3方式&#xff09;二、MyBatis基本使用2.1 向SQL语句传参2.1.1 mybatis日志输出配置2.1.2 #{}形式2.1.3 ${}形式 2.2 数据输入2.2.1 Mybatis总体机制概括2.2.2 概念说明2.2.3 单个简单类型参数2.2.4 实体…