OceanBase v4.2 特性解析:Lateral Derived Table 优化查询

前言

从传统规则来看,内联视图通常不允许引用在同一FROM子句中前面定义的表的列。但从OceanBase 4.2.2版本开始,这一限制得到了突破,允许内联视图作为Lateral Derived Table来定义,从而允许此类引用。Lateral Derived Table的语法与普通内联视图的语法相似,只是需要在内联视图之前之前添加关键字LATERAL。LATERAL关键字必须紧跟在需要作为Lateral Derived Table的每一个子查询之前。

LATERAL关键字及其使用实例

允许在MySQL模式和Oracle模式下使用Lateral Derived Table,同时需要满足如下要求:

  1. LATERAL关键字只能出现在FROM子句中,可以是用逗号分隔的表列表或者是JOIN(包含JOIN、INNER JOIN、CROSS JOIN、LEFT [OUTER] JOIN 或 RIGHT [OUTER] JOIN)中的一种。
  2. 如果LATERAL关键字在JOIN子句的右操作数中,并且包含对左操作数的引用,那么JOIN操作必须是INNER JOIN、CROSS JOIN或LEFT [OUTER] JOIN。如果表在左操作数中,并且包含对右操作数的引用,则JOIN操作必须是RIGHT [OUTER] JOIN。
  3. 如果Lateral Derived Table引用聚合函数,则该函数的聚合查询不能是包含当前Lateral Derived Table所属的查询。
-- 满足要求1,2,3
select * from t1, lateral (select * from t2 where t1.c1 = t2.c1);
select * from t1 cross join lateral (select * from t2 where t1.c1 = t2.c1) on 1=1;
select * from t1 left join lateral (select * from t2 where t1.c1 = t2.c1) on 1=1;
select * from lateral (select * from t2 where t1.c1 = t2.c1) right join t1 on 1=1;-- 不满足要求3
select sum(t1.c1) as s from t1, lateral (select * from t2 where s = t2.c1);
ERROR 1054 (42S22): Unknown column 's' in 'where clause'

典型使用场景

场景1

Lateral关键字可以解决一些select list中的子查询需要返回多列的场景,对于原先的查询需要写两次子查询,但使用Lateral就可以很好地解决这个问题。

-- Q1
select 
(select avg(score) from score s where s.course_id = c.course_id) avg_score,
(select max(score) from score s where s.course_id = c.course_id) max_score
from course c where course_name = 'math';

Q1 会产生 2 次 score 表的扫描任务,使用 Lateral 改写为 Q2 后,score 表可减少 1 次扫描。

-- Q2
select v1.avg_score, v1.max_scorefrom course c, lateral (select avg(score) avg_score, max(score) max_score from score s where s.course_id = c.course_id) v1where course_name = 'math';
场景2
-- 当前存在两张表,一张是课程表,还有一张是成绩表
create table Course (course_id int primary key,course_name varchar(20),teacher_id varchar(20)
);create table Score (student_id int,course_id int,score int,key i_course (course_id)
);

现在需要查出数学这门课的平均分和最高分,并且统计出超过平均分人数,基于现有的语法我们可以先使用子查询分别查出平均分和最高分,然后在另外的子查询中筛选出超过平均分的人数。

-- Q3
select 
(select avg(score) from score s where s.course_id = c.course_id) avg_score,
(select max(score) from score s where s.course_id = c.course_id) max_score,
(select count(1) from score s where s.course_id = c.course_id and s.score >  (select avg(score) from score s where s.course_id = c.course_id)) gt_avg_count
from course c where course_name = 'math';
===========================================================================
|ID|OPERATOR                            |NAME       |EST.ROWS|EST.TIME(us)|
---------------------------------------------------------------------------
|0 |SUBPLAN FILTER                      |           |1       |95          |
|1 |├─TABLE FULL SCAN                   |c          |1       |4           |
|2 |├─SCALAR GROUP BY                   |           |1       |23          |
|3 |│ └─DISTRIBUTED TABLE RANGE SCAN    |s(i_course)|1       |23          |
|4 |├─SCALAR GROUP BY                   |           |1       |23          |
|5 |│ └─DISTRIBUTED TABLE RANGE SCAN    |s(i_course)|1       |23          |
|6 |└─SCALAR GROUP BY                   |           |1       |46          |
|7 |  └─SUBPLAN FILTER                  |           |1       |46          |
|8 |    ├─DISTRIBUTED TABLE RANGE SCAN  |s(i_course)|1       |23          |
|9 |    └─SCALAR GROUP BY               |           |1       |23          |
|10|      └─DISTRIBUTED TABLE RANGE SCAN|s(i_course)|1       |23          |
===========================================================================

从上面的计划可以看出,查询Q3效率比较差,取平均分的子查询调用了两次,且平均分和最高分的子查询可以合并到一起计算,现在分别使用两个子查询需要对score表扫描两次相同的数据集。

对于查询Q3,可以使用Lateral子句改写一下,将查询最大值和最小值的两个子查询合并,然后查询超过平均分人数的子查询引用外面已经计算好的平均分,改写后的查询语句Q4 如下。

-- Q3
select v1.avg_score, v1.max_score, v2.gv_avg_countfrom course c, lateral (select avg(score) avg_score, max(score) max_score from score s where s.course_id = c.course_id) v1,lateral (select count(1) gv_avg_count from score s where s.course_id = c.course_id and s.score > v1.avg_score) v2where course_name = 'math';
===========================================================================
|ID|OPERATOR                            |NAME       |EST.ROWS|EST.TIME(us)|
---------------------------------------------------------------------------
|0 |NESTED-LOOP JOIN                    |           |1       |51          |
|1 |├─NESTED-LOOP JOIN                  |           |1       |28          |
|2 |│ ├─TABLE FULL SCAN                 |c          |1       |4           |
|3 |│ └─SUBPLAN SCAN                    |v1         |1       |23          |
|4 |│   └─SCALAR GROUP BY               |           |1       |23          |
|5 |│     └─DISTRIBUTED TABLE RANGE SCAN|s(i_course)|1       |23          |
|6 |└─SUBPLAN SCAN                      |v2         |1       |23          |
|7 |  └─SCALAR GROUP BY                 |           |1       |23          |
|8 |    └─DISTRIBUTED TABLE RANGE SCAN  |s(i_course)|1       |23          |
===========================================================================

从改写之后的计划可以看出,效率明显是高于Q3,现在对于score表只需要扫描两次就能得到结果。

优化器改写优化

查询Q2改写为LATERAL子句后,生成的执行计划如下:

=========================================================================
|ID|OPERATOR                          |NAME       |EST.ROWS|EST.TIME(us)|
-------------------------------------------------------------------------
|0 |NESTED-LOOP JOIN                  |           |1       |28          |
|1 |├─TABLE FULL SCAN                 |c          |1       |4           |
|2 |└─SUBPLAN SCAN                    |v1         |1       |23          |
|3 |  └─SCALAR GROUP BY               |           |1       |23          |
|4 |    └─DISTRIBUTED TABLE RANGE SCAN|s(i_course)|1       |23          |
=========================================================================
Outputs & filters:
-------------------------------------0 - output([v1.avg_score], [v1.max_score]), filter(nil), rowset=16conds(nil), nl_params_([c.course_id(:0)]), use_batch=true1 - output([c.course_id]), filter([c.course_name = 'math']), rowset=16access([c.course_id], [c.course_name]), partitions(p0)is_index_back=false, is_global_index=false, filter_before_indexback[false],range_key([c.course_id]), range(MIN ; MAX)always true2 - output([v1.avg_score], [v1.max_score]), filter(nil), rowset=16access([v1.avg_score], [v1.max_score])3 - output([T_FUN_SUM(s.score) / cast(T_FUN_COUNT(s.score), DECIMAL(20, 0))], [T_FUN_MAX(s.score)]), filter(nil), rowset=16group(nil), agg_func([T_FUN_MAX(s.score)], [T_FUN_SUM(s.score)], [T_FUN_COUNT(s.score)])4 - output([s.score]), filter(nil), rowset=16access([GROUP_ID], [s.__pk_increment], [s.score]), partitions(p0)is_index_back=true, is_global_index=false,range_key([s.course_id], [s.__pk_increment]), range(MIN,MIN ; MAX,MAX)always true,range_cond([s.course_id = :0])

从执行计划可以看出,需要通过c表驱动v1执行,只能走Nested Loop Join,在c表数据量很小的情况下执行的效率很高。但当c表数据量很大,score表中的数据很少时,执行的效率就会很差。显然Lateral关键字限制了Join Order的枚举顺序,因此需要优化器通过预设的改写规则去除LATERAL关键字,提升计划的枚举空间。

-- Q5
select v1.avg_score, v1.max_scorefrom course c, (select course_id, avg(score) avg_score, max(score) max_score from score s group by course_id ) v1where course_name = 'math' and v1.course_id = c.course_id;
=======================================================
|ID|OPERATOR               |NAME|EST.ROWS|EST.TIME(us)|
-------------------------------------------------------
|0 |MERGE JOIN             |    |1       |9           |
|1 |├─TABLE FULL SCAN      |c   |1       |4           |
|2 |└─SORT                 |    |1       |5           |
|3 |  └─SUBPLAN SCAN       |v1  |1       |5           |
|4 |    └─HASH GROUP BY    |    |1       |5           |
|5 |      └─TABLE FULL SCAN|s   |1       |4           |
=======================================================
Outputs & filters:
-------------------------------------0 - output([v1.avg_score], [v1.max_score]), filter(nil), rowset=16equal_conds([v1.course_id = c.course_id]), other_conds(nil)merge_directions([ASC])1 - output([c.course_id]), filter([c.course_name = 'math']), rowset=16access([c.course_id], [c.course_name]), partitions(p0)is_index_back=false, is_global_index=false, filter_before_indexback[false],range_key([c.course_id]), range(MIN ; MAX)always true2 - output([v1.avg_score], [v1.max_score], [v1.course_id]), filter(nil), rowset=16sort_keys([v1.course_id, ASC])3 - output([v1.course_id], [v1.avg_score], [v1.max_score]), filter(nil), rowset=16access([v1.course_id], [v1.avg_score], [v1.max_score])4 - output([s.course_id], [T_FUN_SUM(s.score) / cast(T_FUN_COUNT(s.score), DECIMAL(20, 0))], [T_FUN_MAX(s.score)]), filter(nil), rowset=16group([s.course_id]), agg_func([T_FUN_MAX(s.score)], [T_FUN_SUM(s.score)], [T_FUN_COUNT(s.score)])5 - output([s.course_id], [s.score]), filter(nil), rowset=16access([s.course_id], [s.score]), partitions(p0)is_index_back=false, is_global_index=false,range_key([s.__pk_increment]), range(MIN ; MAX)always true

优化器改写后的SQL去除掉了Lateral关键字,增加了c表和v1的计划枚举空间,在score表数据量很小时,优化器还是会根据代价生成下面的计划,通过v1来驱动c表执行,减少计划执行时间。

=======================================================
|ID|OPERATOR               |NAME|EST.ROWS|EST.TIME(us)|
-------------------------------------------------------
|0 |NESTED-LOOP JOIN       |    |1       |23          |
|1 |├─SUBPLAN SCAN         |v1  |1       |5           |
|2 |│ └─HASH GROUP BY      |    |1       |5           |
|3 |│   └─TABLE FULL SCAN  |s   |1       |4           |
|4 |└─DISTRIBUTED TABLE GET|c   |1       |18          |
=======================================================
Outputs & filters:
-------------------------------------0 - output([v1.avg_score], [v1.max_score]), filter(nil), rowset=16conds(nil), nl_params_([v1.course_id(:0)]), use_batch=true1 - output([v1.course_id], [v1.avg_score], [v1.max_score]), filter(nil), rowset=16access([v1.course_id], [v1.avg_score], [v1.max_score])2 - output([s.course_id], [T_FUN_SUM(s.score) / cast(T_FUN_COUNT(s.score), DECIMAL(20, 0))], [T_FUN_MAX(s.score)]), filter(nil), rowset=16group([s.course_id]), agg_func([T_FUN_MAX(s.score)], [T_FUN_SUM(s.score)], [T_FUN_COUNT(s.score)])3 - output([s.course_id], [s.score]), filter(nil), rowset=16access([s.course_id], [s.score]), partitions(p0)is_index_back=false, is_global_index=false,range_key([s.__pk_increment]), range(MIN ; MAX)always true4 - output(nil), filter([c.course_name = 'math']), rowset=16access([GROUP_ID], [c.course_name]), partitions(p0)is_index_back=false, is_global_index=false, filter_before_indexback[false],range_key([c.course_id]), range(MIN ; MAX),range_cond([:0 = c.course_id])

通过改写可以提高SQL在不同场景下的适应性,减少差计划产生的可能。但是如果业务不需要做这个改写,可以通过NO_DECORRELATE这个hint来禁用对Lateral Derived Table的改写。

-- Q4'
select /*+ NO_DECORRELATE */ v1.avg_score, v1.max_scorefrom course c, lateral (select avg(score) avg_score, max(score) max_score from score s where s.course_id = c.course_id) v1where course_name = 'math';

总结

Lateral语法打开了之前同一From子句不能引用前面表的列的限制,在很多情况下都可以⽤来加速SQL执行,或者可以使SQL更容易理解。

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

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

相关文章

26-LINUX--I/O复用-select

一.I/O复用概述 /O复用使得多个程序能够同时监听多个文件描述符,对提高程序的性能有很大帮助。以下情况适用于I/O复用技术: ◼ TCP 服务器同时要处理监听套接字和连接套接字。 ◼ 服务器要同时处理 TCP 请求和 UDP 请求。 ◼ 程序要同时处理多个套接…

Facebook广告素材如何测试?手把手教你!

广告素材对Facebook广告效果的影响是很大的,用对了素材,Facebook广告的价值就越高。广告主们通常会先通过广告测试,根据数据反馈来挑选素材。今天就手把手教你做Facebook素材测试的技巧,让你更有灵感和思路! 创意测试 …

Hudi CLI 安装配置总结

前言 上篇文章 总结了Spark SQL Rollback, Hudi CLI 也能实现 Rollback,本文总结下 Hudi CLI 安装配置以及遇到的问题。 官方文档 https://hudi.apache.org/cn/docs/cli/ 版本 Hudi 0.13.0(发现有bug)、(然后升级)0.14.1Spark 3.2.3打包 mvn clean package -DskipTes…

使用 Django 构建动态网页

文章目录 创建 Django 项目和应用程序创建 HTML 模板创建视图函数配置 URL 路由运行 Django 服务器使用 Django 模板语言 Django 是一个流行的 Python Web 框架,它能够帮助开发人员快速构建强大的 Web 应用程序。在 Django 中,HTML 是用于呈现网页内容的…

Spring Boot 复习

2 3 5(不考) 9 (1)RestController 注解是一个组合注解,等同于Controller 和ResponseBody 两个注解结合使用的效果。主要作用是将当前类作为控制层的组件添加到 Spring 容器中,同时该类的方法无法返回 JSP 页面,而且…

Flutter 中的 RenderObjectToWidgetAdapter 小部件:全面指南

Flutter 中的 RenderObjectToWidgetAdapter 小部件:全面指南 Flutter 是一个功能强大的 UI 框架,由 Google 开发,允许开发者使用 Dart 语言构建跨平台的移动、Web 和桌面应用。在 Flutter 的渲染体系中,RenderObjectToWidgetAdap…

MyBatis面试题系列三

1、#{}和${}的区别是什么? #{}是预编译处理,${}是字符串替换。 Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值; Mybatis 在处理${}时,就是把${}替换成变量的值…

SpringBoot项目启动时“jar中没有主清单属性”异常

资料参考 Spring Boot 启动时 “jar中没有主清单属性” 异常 - spring 中文网 (springdoc.cn) 实际解决 更详细的参考以上&#xff0c;我这边的话只需要在 pom文件 中加上 spring-boot-maven-plugin 插件就能解决该异常&#xff0c;具体如下&#xff1a; <build><p…

1. 计算机系统概述

1. 计算机系统概述 文章目录 1. 计算机系统概述1.1 计算机的发展硬件的发展软件的发展 1.2.1 计算机硬件的基本组成早期冯诺依曼的结构现代计算机的结构 1.2.2 各个硬件的工作原理主存储器运算器控制器计算机工作过程 1.2.3 计算机系统的多级层次结构1.3 计算机的性能指标存储器…

GD32如何配置中断优先级分组以及中断优先级

使用GD32 MCU的过程中&#xff0c;大家可能会有以下疑问&#xff1a;中断优先级如何配置和使用&#xff1f; 本文将会为大家解析中断优先级分组以及中断优先级的配置使用&#xff1a; 中断优先级分组配置 一个GD32 MCU系统需要大家明确系统中使用的中断优先级分组&#xff0…

代驾公司在市场竞争中如何保持优势?

在竞争激烈的市场中&#xff0c;代驾公司可以通过多种策略保持其竞争优势&#xff0c;包括利用市场潜力、创新服务模式、提高服务效率以及加强品牌建设等。以下是具体的策略&#xff1a; 利用市场潜力 汽车产业空间巨大&#xff1a;随着汽车保有量的增加&#xff0c;代驾行业…

扫地机器人:卷价格,不如卷技术

扫地机器人内卷的终点是技术和价值&#xff0c;价格只是附属品。 一路上涨的价格&#xff0c;一路下跌的销量 从价格飙升&#xff0c;到重新卷回价格&#xff0c;尴尬的背后是扫地机器人在骨感现实下的无奈抉择。 根据数据显示&#xff0c;2020中国扫地机器人线上市场零售均价…

通过可识别性和深度学习重建大脑功能网络

摘要 本研究提出了一种新的方法来重建代表大脑动力学的功能网络&#xff0c;该方法基于两个脑区在同一认知任务中的共同参与会导致其可识别性或其动力学特性降低的观点。这种可识别性是通过深度学习模型在监督分类任务中获得的分数来估计的&#xff0c;因此不需要对这种协同参…

零、测试开发前置知识

文章目录 1、什么是冒烟测试、回归测试&#xff1f;2、设计测试用例的方法有哪些&#xff1f;3、对于404或500&#xff0c;你会如何分析定位&#xff1f;4、什么是敏捷开发&#xff1f;敏捷开发流程是怎么样的&#xff1f;5、做接口测试过程中&#xff0c;下游接口需要上游数据…

Flink端到端的精确一次(Exactly-Once)

目录 状态一致性 端到端的状态一致性 端到端精确一次&#xff08;End-To-End Exactly-Once&#xff09; Flink内部的Exactly-Once 输入端保证 输出端保证 幂等写入 事务写入 Flink和Kafka连接时的精确一次保证 整体介绍 需要的配置 案例 状态一致性 流式计算本身就…

Java工作学习笔记

1、ConfigurationProperties注解是什么意思&#xff1f; ConfigurationProperties 可以将属性文件与一个Java类绑定&#xff0c;将属性文件中的变量值注入到该Java类的成员变量中 示例代码&#xff1a; /*** SSP配置** author mua*/ Component Data ConfigurationProperties…

如何提高接口响应速度

在非大数据&#xff08;几万以上记录&#xff09;的情况下&#xff0c;影响接口响应速度的因素中最大的是查询数据库的次数&#xff0c;其次才是数组遍历和简单数据处理&#xff08;如根据已有字段增加新的属性&#xff0c;或计算值&#xff09;。 一般一次数据库查询需要50毫秒…

Java Web应用,IPv6问题解决

在Java Web程序中&#xff0c;如果使用Tomcat并遇到了IPv6相关的问题&#xff0c;可以通过以下几种方式来解决&#xff1a; 1. 配置Tomcat以使用IPv4 默认情况下&#xff0c;Java可能会优先使用IPv6。如果你希望Tomcat使用IPv4&#xff0c;最简单的方法是通过设置系统属性来强…

无线麦克风哪个牌子性价比高?一文告诉你无线领夹麦克风怎么挑选

​当我们谈论到演讲、表演或者录制视频时&#xff0c;一个高质量的无线麦克风能够使得整个体验提升至一个全新的水平。它不仅能够保证声音的清晰度和真实度&#xff0c;还能够让使用者在演讲或者表演时更加自信和舒适。基于对市场的深入研究和用户体验的考量&#xff0c;我挑选…

TypeScript 中的 tsconfig.json

什么是 tsconfig.json&#xff1f; tsconfig.json 是 TypeScript 编译器的配置文件&#xff0c;用于指导编译器如何编译 TypeScript 代码。在 TypeScript 项目中&#xff0c;如果存在这个文件&#xff0c;那么在执行 tsc 命令时&#xff0c;编译器将会使用该文件中定义的配置选…