谈谈in常量查询的设计与优化

简介: 如标题所示,这是一篇介绍in常量查询的源码解读文章,但又不限于in常量查询,因为其中涉及的很多设计与优化对于大多数查询都是普适的。 一如往常一样,我们首先会过一遍整体的执行流程,梳理一个大致的框架。紧接着,同时也是更重要的,我们会通过一系列在真实场景中遇到的问题(说白了就是性能优化),来对各种细节处理进行增强。

作者:越寒

介绍

如标题所示,这是一篇介绍in常量查询的源码解读文章,但又不限于in常量查询,因为其中涉及的很多设计与优化对于大多数查询都是普适的。 一如往常一样,我们首先会过一遍整体的执行流程,梳理一个大致的框架。紧接着,同时也是更重要的,我们会通过一系列在真实场景中遇到的问题(说白了就是性能优化),来对各种细节处理进行增强。

温馨提醒:建议有条件有兴趣的同学可以对照着本篇文章边调试(我基本上把重要的断点位置都截了图)边学习边思考,这样印象和理解应该会更加深刻。

希望大家在读完之后,可以尝试着回答以下一些问题来进行某种测验:

  • 什么是分片裁剪?为什么要进行分片裁剪?
  • 为什么要对物理SQL中值进行裁剪?
  • 什么是plan cache?为什么需要?
  • 为什么需要post planner ?
  • XPlan是什么?为什么Xplan比物理SQL更优?
  • 为什么要有一个ToDrdsRelVisitor?
  • 什么是全局二级索引?如何利用?
  • 以及其他散落于文章中或者阅读时的问题

从大致的流程说起

注:详细的执行流程请参考文章,https://zhuanlan.zhihu.com/p/457450880,我们这里只介绍其中几个比较重要的环节。 我们拿一个非常简单的场景来看一下吧,一个简单的表如下,create table t(c1 int, c2 int, c3 int) dbpartition by hash(c1) tbpartition by hash(c1) tbpartitions 2,一条最简单的SQL如下:select c3 from t where c1 in (1,2)。挑了五个阶段进行了并不太详尽的说明,如果你感觉比较抽象时,也可以动手调试一下,一些概念应该就会更加清晰了。

阶段一

我们需要将SQL文本解析为语法树,如果不合法,则报错,关键断点如下图,其中sql为输入的查询语句,statement为经过解析后的语法树。
20230103151040.jpg
20230103151103.jpg

需要注意的是,在这个地方,我们是只进行语法解析,而不进行语义解析。什么意思呢,比如你现在输入的SQL为select c1 from tt,此时虽然我们没有tt这张表,但是断点处还是会正常解析出一个SQLSelectStatement,有兴趣的同学可以打个断点试一下。

阶段二

如上分析,我们现在要进行语义的校验了,比如我怎么知道这张表存不存在,以及是否含有这个列呢?
20230103151141.jpg
20230103151159.jpg
20230103151216.jpg

阶段三

构建执行计划,在toRel时将由 SqlNode 构成的 AST 转换为由 RelNode 组成的逻辑计划。

20230103151250.jpg
20230103151300.jpg

埋一个坑把,有兴趣的同学可以结合代码思考一下,既然我们已经拿到了逻辑执行计划,那么ToDrdsRelVisitor的作用是什么呢?

阶段四
对执行计划进行优化,以期获得较为优异的执行效果。
20230103151346.jpg

阶段五
拿到执行计划之后,紧接着我们来看一下是在哪里执行的,以及是如何执行的。
20230103151420.jpg
20230103151428.jpg

我们可以简单看一下这个plan,这是一个非常简单的plan,最上层是一个Gather用来聚合下层多个logicalView的结果,而logicalView中包含了如何与存储节点进行交互的信息。 根据plan拿到相应的handler,然后进行调用就可以了。

20230103151609.jpg
20230103151631.jpg
在这个场景中,我们会递归调用logicalView的handler。
20230103151700.jpg
OK,以上就是一个大概的执行流程,接下来我们来真正深入到一些细节看一下,我们如何将这个大致的流程进行丰富以使其能够满足工业生产的需求。

现实中的使用场景

In查询列表中的值不固定,个数亦不固定。

优化思路

单条SQL的优化,比如分片裁剪,物理SQL中in值的裁剪,使用XPlan代替物理SQL。
大量执行相似的SQL时,避免重复性且不必要的工作,如避免每次重新生成plan。
对其中一些特殊场景进行更加极致的优化,比如单分片直接下推。
通过添加索引进行优化,在这里我们主要讨论全局二级索引。

具体的优化

单条SQL的优化
分片裁剪:只访问必须访问的分片
Q:select from t where c1 in (1,2) 会向所有分片下发物理SQL么? A:不会的。通过上面的分析,我们下发的物理SQL为select from t_physical_table where c1 in (1,2),t_physical_table为逻辑表t所对应的物理表。而由于表t的分库键和分表键均为c1,因此显然我们只需要向两张可能存在匹配记录的物理表下发物理SQL即可,获取裁剪后的分表信息如下图。

20230103151949.jpg
分片裁剪是一定需要调用分片计算,分片计算的逻辑在这里。
20230103152021.jpg

物理SQL中in值的裁剪:只留下有用的in值
Q:下发的物理SQL中,是否会对in的列表进行裁剪呢? A:会的,而且对下发的物理SQL中的in列表中的值进行裁剪,主要有两个好处,一是尽可能避免下发的物理SQL导致不必要的全表扫描,二是减少下发物理SQL的长度。
20230103152134.jpg
20230103152143.jpg

上图中PruneRaw即代表裁剪后的in查询列表。

使用XPlan代替物理SQL:避免DN节点进行物理SQL的解析优化

注:详情可参考链接文章中的执行计划传输部分,https://zhuanlan.zhihu.com/p/308173106#:~:text=PolarDB-,%E8%BF%9B%E8%A1%8C%E4%BA%86%E7%89%B9%E6%AE%8A%E4%BC%98%E5%8C%96%E3%80%82 Notice:in查询其实暂时是不支持传输执行计划的。 但我觉得可能没什么特别特殊的地方,像传输其他的plan一样,我们需要在计算层指定数据的访问方式(即指定索引),然后进行适配和对接。

避免每次重新构建plan
避免参数值不同而反复构建plan
Q:每次都进行plan的构建,看起来并不是非常有必要,比如select from t where id in (1,2) 和select from t where id in (2,3)。
A: 是的,所以我们对plan进行了缓存,这就是PlanCache组件,可以理解为Map。很自然的,我们需要对上述两条SQL进行参数化以便从map中进行查找,即参数化为select * from t where id in (?,?)的形式,代码在
20230103152309.jpg
20230103152317.jpg

避免参数个数不同而反复构建plan
Q:细心的同学可能感觉有点奇怪,上面的select c1,c3 from t where c1 in (1,2) 参数化后为 select c1,c3 from t where c1 in (?),而非select c1,c3 from t where c1 in (?,?),这是为什么?
A:这样做是为了避免plan cache的膨胀,因为这样参数化之后,select c1,c3 from t where c1 in (1,2) 和select c1,c3 from t where c1 in (1,2,3,4)就是共用一个plan cache了;此外,这样还可以减少参数化SQL占用的内存,想象一下,有些SQL中in列表中的值多达几十万个呢。

单分片场景优化
Q:对于某些场景,是否有更近一步的优化,毕竟TP是需要尽可能的高性能的。
A:有的,比如单分片的场景,in列表中的值会落在同一个物理分表上。 我们可以思考下此时下面的执行计划是否可以简化?
20230103152413.jpg
答案是显然的,在单分片场景下,上层的Gather是完全不需要的,否则我们在执行时会有额外的执行开销。 引申:我们可以再结合前面的参数化与plan cache来理解这个问题,即参数不同的SQL的最优执行计划其实并非总是相同的,但我们为了避免每次重复生成plan,又会缓存一个plan,于是我们需要一个能够对plan进行优化的能力。 我们大概可以把这种情况分成两种,一种是参数不同导致选择的join算法不同,比如是选择bka join还是hash join,为了解决这个问题,我们引入了执行计划管理模块(SPM);另一种则跟我们的架构有非常大的关系,因为我们下层的DN(可以简单理解为mysql)显然是具备执行各种SQL的能力的,而如果在某些参数下,经过裁剪后只剩下一个分片了,则该SQL经过物理表名的替换后可直接下发到DN执行,计算层只需要等待结果返回即可,无需做任何其他的操作。为了实现第二种效果,我们在planner阶段增加了一个阶段,叫做post planner,在post planner中会判断是否能够下推到某个分片,默认为打开,上图中为了演示需要,特意使用hint进行了关闭。

20230103152502.jpg
20230103152510.jpg
20230103152519.jpg

添加全局二级索引

注:索引,本质是一种修改与查询的权衡,需要用户谨慎考虑,尤其写入全局索引会带来较大的分布式事务开销。 Q:分片建已经确定了,in查询的字段没有跟分片对齐,是不是无法做分片裁剪了,还能优化么?
A:可以考虑增加全局二级索引。 我们来举个例子吧,比如table: t3(c1 int, c2 int, c3 int) dbpartition by hash(c1); SQL为select c3 from t3 where c2 in (1,2),由执行计划可知我们无法进行分片裁剪,因此需要访问所有8个分片,如下:
20230103152615.jpg
现在让我们来考虑一下如何优化? 我们的目的是希望减少访问的分片数,而之所以无法进行分片的裁剪,是因为in查询的字段和分片键没有对齐。于是解决方案也很简单,我们增加一个拆分键与in查询字段对齐的全局的二级索引即可,有关全局二级索引的介绍,可参考链接,https://help.aliyun.com/document_detail/182179.html。 比如,我们执行如下添加全局二级索引的SQL,alter table t3 add global index g_c2(c2) covering(c1, c3) dbpartition by hash(c2),然后我们再来看下此时的执行计划,发现此时已经如我们所料进行了基于全局二级索引的分片裁剪,现在只需要扫描两个分片即可。
20230103152644.jpg

一个小练习
In列表中包含大量重复值时,可以如何优化?(我们现在的版本没有考虑这种情况) 比如,有一个很简单的做法,在参数化时加一个去重,如下图。
20230103152719.jpg

然后大家可以思考一下,需要注意什么,以及有什么问题么?

One More:横向对比与思考
大家有兴趣,有时间的,可以对比其他友商数据库进行比较与分析。

总结

其实我在这篇文章里面,抛了挺多问题,有些给了一种便于叙述却未必全面的答案,有些则完全没有回答。最后的这个总结我觉得也留给大家来写了。

更多技术好文,请关注PolarDB-X知乎号

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
SQL 缓存 Java
SQL Query Plan在Presto中的缓存设计与实现
阿里云日志服务(SLS)提供一站式数据采集、加工、查询分析、告警、可视化与投递等功能,其中查询分析以简单统一的接口提供大规模数据的查询、计算和分析能力,深受用户喜爱。 目前,分析系统每天接收5+亿次SQL查询请求,在底层,分析系统基于Presto内核,其中Coordinator节点上负载尤其严重,其...
796 0
SQL Query Plan在Presto中的缓存设计与实现
|
7月前
|
SQL 人工智能 数据挖掘
Apache Doris 4.0 AI 能力揭秘(二):为企业级应用而生的 AI 函数设计与实践
Apache Doris 4.0 原生集成 LLM 函数,将大语言模型能力深度融入 SQL 引擎,实现文本处理智能化与数据分析一体化。通过十大函数,支持智能客服、内容分析、金融风控等场景,提升实时决策效率。采用资源池化管理,保障数据一致性,降低传输开销,毫秒级完成 AI 分析。结合缓存复用、并行执行与权限控制,兼顾性能、成本与安全,推动数据库向 AI 原生演进。
696 0
Apache Doris 4.0 AI 能力揭秘(二):为企业级应用而生的 AI 函数设计与实践
|
机器学习/深度学习 监控 Web App开发
SLS机器学习最佳实战:根因分析(一)
通过算法,快速定位到某个宏观异常在微观粒度的具体表现形式,能够更好的帮助运营同学和运维同学分析大量异常,降低问题定位的时间。
13407 0
|
人工智能 自然语言处理 开发工具
Agent调研--19类Agent框架对比(下)
Agent调研--19类Agent框架对比(下)
4591 0
|
存储 关系型数据库 Linux
centos7 安装PostgreSQL
PostgreSQL Red Hat系列产品包括Red Hat Enterprise Linux,CentOS,Fedora,Scientific Linux,Oracle Linux等。
3202 0
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤
【10月更文挑战第7天】本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据自身需求选择合适的方法。
561 3
|
SQL 关系型数据库 测试技术
Mysql数据库Slow_log中的lock_Time和Query_time
主要用简单的例子来说明,slow log里的lock_time和query_time的关系,得出的结论是: 当一个sql的执行时间(排除lock_time)大于long_query_time的时候,才会被记录到slow log中,并且query_time是累计了lock_time的(如果有lock.
11487 0
|
SQL 关系型数据库 MySQL
mysql查看被锁住的表
mysql查看被锁住的表
740 0
|
SQL 存储 算法
MySQL 8.0 新的火山模型执行器
# MySQL的总体架构 通常我们认为MySQL的整体架构如下, ![1.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/46a99cb928955a5c5759115e2e6ba1fe.png) 官方10年前开始就一直在致力于优化器代码的重构工作,目的是能确保在SQL的执行过程中有清晰的阶段,包括分离Parse和Resol
2889 0
MySQL 8.0 新的火山模型执行器
|
SQL NoSQL 关系型数据库
MySQL · 源码分析 · MySQL Range (Min-Max Tree)结构分析
概述条件查询被广泛的使用在SQL查询中,复杂条件是否能在执行过程中被优化,比如恒为true或者false的条件,可以合并的条件。另外,由于索引是MySQL访问数据的基本方式,已经追求更快的访问方式,SARGable这个概念已经被我们遗忘了,因为他已经成为默认必要的方法(Search ARGument ABLE)。MySQL如何组织复杂条件并计算各个Ranges所影响到的对应可以使用的索引的代价和使
975 0