支付宝工程师如何搞定关系数据库的“大脑”——查询优化器

前言

查询优化器是关系数据库系统的核心模块,是数据库内核开发的重点和难点,也是衡量整个数据库系统成熟度的“试金石”。

查询优化理论诞生距今已有四十来年,学术界和工业界其实已经形成了一套比较完善的查询优化框架(System-R 的 Bottom-up 优化框架和 Volcano/Cascade 的 Top-down 优化框架),但围绕查询优化的核心难题始终没变——如何利用有限的系统资源尽可能为查询选择一个“好”的执行计划。

近年来,新的存储结构(如 LSM 存储结构)的出现和分布式数据库的流行进一步加大了查询优化的复杂性,本文章结合 OceanBase 数据库过去近十年时间的实践经验,与大家一起探讨查询优化在实际应用场景中的挑战和解决方案。

查询优化器简介

SQL 是一种结构化查询语言,它只告诉数据库”想要什么”,但是它不会告诉数据库”如何获取”这个结果,这个"如何获取"的过程是由数据库的“大脑”查询优化器来决定的。在数据库系统中,一个查询通常会有很多种获取结果的方法,每一种获取的方法被称为一个"执行计划"。给定一个 SQL,查询优化器首先会枚举出等价的执行计划。

其次,查询优化器会根据统计信息和代价模型为每个执行计划计算一个“代价”,这里的代价通常是指执行计划的执行时间或者执行计划在执行时对系统资源(CPU + IO + NETWORK)的占用量。最后,查询优化器会在众多等价计划中选择一个"代价最小"的执行计划。下图展示了查询优化器的基本组件和执行流程。

查询优化器面临的挑战

查询优化自从诞生以来一直是数据库的难点,它面临的挑战主要体现在以下三个方面:

挑战一:精准的统计信息和代价模型

统计信息和代价模型是查询优化器基础模块,它主要负责给执行计划计算代价。精准的统计信息和代价模型一直是数据库系统想要解决的难题,主要原因如下:

1、统计信息:在数据库系统中,统计信息搜集主要存在两个问题。首先,统计信息是通过采样搜集,所以必然存在采样误差。其次,统计信息搜集是有一定滞后性的,也就是说在优化一个 SQL 查询的时候,它使用的统计信息是系统前一个时刻的统计信息。

2、选择率计算和中间结果估计:选择率计算一直以来都是数据库系统的难点,学术界和工业界一直在研究能使选择率计算变得更加准确的方法,比如动态采样,多列直方图等计划,但是始终没有解决这个难题,比如连接谓词选择率的计算目前就没有很好的解决方法。

3、代价模型:目前主流的数据库系统基本都是使用静态的代价模型,比如静态的 buffer 命中率,静态的 IO RT,但是这些值都是随着系统的负载变化而变化的。如果想要一个非常精准的代价模型,就必须要使用动态的代价模型。

挑战二:海量的计划空间

复杂查询的计划空间是非常大的,在很多场景下,优化器甚至没办法枚举出所有等价的执行计划。下图展示了星型查询等价逻辑计划个数(不包含笛卡尔乘积的逻辑计划),而优化器真正的计划空间还得正交上算子物理实现,基于代价的改写和分布式计划优化。在如此海量的计划空间中,如何高效的枚举执行计划一直是查询优化器的难点。

挑战三:高效的计划管理机制

计划管理机制分成计划缓存机制和计划演进机制。

1、计划缓存机制:计划缓存根据是否参数化,优化一次/总是优化以及是否缓存可以划分成如下图所示的三种计划缓存方法。每个计划缓存方法都有各自的优缺点,不同的业务需求会选择不同的计划缓存方法。在蚂蚁/阿里很多高并发,低时延的业务场景下,就会选择参数化+优化一次+缓存的策略,那么就需要解决不同参数对应不同计划的问题(parametric query optimization),后面我们会详细讨论。

2、计划演进机制:计划演进是指对新生成计划进行验证,保证新计划不会造成性能回退。在数据库系统中, 新计划因为一些原因(比如统计信息刷新,schema版本升级)无时无刻都在才生,而优化器因为各种不精确的统计信息和代价模型始终是没办法百分百的保证新生成的计划永远都是最优的,所以就需要一个演进机制来保证新生成的计划不会造成性能回退。

OceanBase 查询优化器工程实践

下面我们来看一下 OceanBase 根据自身的框架特点和业务模型如何解决查询优化器所面临的挑战。

从统计信息和代价模型的维度看,OceanBase 发明了基于 LSM-TREE 存储结构的基表访问路径选择。从计划空间的角度看,因为 OceanBase 原生就是一个分布式关系数据库系统,它必然要面临的一个问题就是分布式计划优化。从计划管理的角度看,OceanBase 有一整套完善的计划管理机制。

1.基于 LSM - TREE 的基表访问路径选择
基表访问路径选择方法是指优化器选择索引的方法,其本质是要评估每一个索引的代价并选择代价最小的索引来访问数据库中的表。对于一个索引路径,它的代价主要由两部分组成,扫描索引的代价和回表的代价(如果一个索引对于一个查询来说不需要回表,那么就没有回表的代价)。

通常来说,索引路径的代价取决于很多因素,比如扫描/回表的行数,投影的列数,谓词的个数等。为了简化我们的讨论,在下面的分析中,我们从行数这个维度来介绍这两部分的代价。

  • 扫描索引的代价
    扫描索引的代价跟扫描的行数成正比,而扫描的行数则是由一部分查询的谓词来决定,这些谓词定义了索引扫描开始和结束位置。理论上来说扫描的行数越多,执行时间就会越久。扫描索引的代价是顺序 IO。
  • 回表的代价
    回表的代价跟回表的行数也是正相关的,而回表的行数也是由查询的谓词来决定,理论上来说回表的行数越多,执行时间就会越久。回表的扫描是随机 IO,所以回表一行的代价通常会比顺序扫描索引一行的代价要高。

在传统关系数据库中,扫描索引的行数和回表的行数都是通过优化器中维护的统计信息来计算谓词选择率得到(或者通过一些更加高级的方法比如动态采样)。

举个简单的例子,给定联合索引(a,b)和查询谓词 a > 1 and a < 5 and b < 5, 那么谓词 a > 1 and a < 5 定义了索引扫描开始和结束的位置,如果满足这两个条件的行数有 1w 行,那么扫描索引的代价就是 1w 行顺序扫描,如果谓词 b < 5 的选择率是 0.5,那么回表的代价就是 5k 行的随机扫描。

那么问题来了:传统的计算行数和代价的方法是否适合基于 LSM-TREE 的存储引擎?

LSM-TREE 存储引擎把数据分为了两部分(如下图所示),静态数据(基线数据)和动态数据(增量数据)。其中静态数据不会被修改,是只读的,存储于磁盘;所有的增量修改操作(增、删、改)被记录在动态数据中,存储于内存。静态数据和增量数据会定期的合并形成新的基线数据。在 LSM-TREE 存储引擎中,对于一个查询操作,它需要合并静态数据和动态数据来形成最终的查询结果。

考虑下图中 LSM-TREE 存储引擎基线数据被删除的一个例子。在该图中,基线中有 10w 行数据,增量数据中维护了对这 10w 行数据的删除操作。在这种场景下,这张表的总行数是 0 行,在传统的基于 Buffer-Pool 的存储引擎上,扫描会很快,也就是说行数和代价是匹配的。但是在 LSM-TREE 存储引擎中,扫描会很慢(10w 基线数据 + 10w 增加数据的合并),也就是行数和代价是不匹配的。

这个问题的本质原因是在基于 LSM-TREE 的存储引擎上,传统的基于动态采样和选择率信息计算出来的行数不足以反应实际计算代价过程中需要的行数。

举个简单的例子,在传统的关系数据库中,我们插入 1w 行,然后删除其中 1k 行,那么计算代价的时候会用 9k 行去计算,在 LSM-TREE 的场景下,如果前面 1w 行是在基线数据里面,那么内存中会有额外的 1k 行,在计算代价的时候我们是需要用 11k 行去计算。

为了解决 LSM-TREE 存储引擎的计算代价行数和表中真实行数不一致的行为,OceanBase 提出了“逻辑行”和“物理行”的概念以及计算它们的方法。其中逻辑行可以理解为传统意义上的行数,物理行主要用于刻画 LSM-TREE 这种存储引擎在计算代价时需要真正访问的行数。

再考虑上图中的例子,在该图中,逻辑行是 0 行,而物理行是 20w 行。给定索引扫描的开始/结束位置,对于基线数据,因为 OceanBase 为基线数据维护了块级别的统计信息,所以能很快的计算出来基线行数。对于增量数据,则通过动态采样方法获取增/删/改行数,最终两者合并就可以得到逻辑行和物理行。下图展示了 OceanBase 计算逻辑行和物理行的方法。

相比于传统的基表访问路径方法,OceanBase 的基于逻辑行和物理行的方法有如下两个优势:

优势一:实时统计信息

因为同时考虑了增量数据和基线数据,相当于统计信息是实时的,而传统方法的统计信息搜集是有一定的滞后性的(通常是一张表的增/删/修改操作到了一定程度,才会触发统计信息的重新搜集)。

优势二:解决了索引列上的谓词依赖关系

考虑索引(a,b)以及查询条件 a = 1 and b = 1 , 传统的方法在计算这个查询条件的选择率的时候必然要考虑的一个问题是 a 和 b 是否存在依赖关系,然后再使用对应的方法(多列直方图或者动态采样)来提高选择率计算的正确率。OceanBase 目前的估行方法默认能够解决 a 和 b 的依赖关系的场景。

2.OceanBase 分布式计划优化

OceanBase 原生就有分布式的属性,那么它必然要解决的一个问题就是分布式计划优化。很多人认为分布式计划优化很难,无从下手,那么分布式计划优化跟本地优化到底有什么区别?分布式计划优化是否需要修改现有的查询优化框架来做优化?

在笔者看来,现有的查询优化框架完全有能力处理分布式计划优化,但是分布式计划优化会大大增加计划的搜索空间,主要原因如下:

1、在分布式场景下,选择的是算子的分布式算法,而算子的分布式算法空间比算子本地算法的空间要大很多。下图展示了一个 Hash Join 在分布式场景下可以选择的分布式算法。

2、在分布式场景下,除了序这个物理属性之外,还增加了分区信息这个物理属性。分区信息主要包括如何分区以及分区的物理信息。分区信息决定了算子可以采用何种分布式算法。

3、在分布式场景下,分区裁剪/并行度优化/分区内(间)并行等因素也会增大分布式计划的优化复杂度。

OceanBase 目前采用两阶段的方式来做分布式优化。在第一阶段,OceanBase 基于所有表都是本地的假设生成一个最优本地计划。在第二阶段,OceanBase 开始做并行优化, 用启发式规则来选择本地最优计划中算子的分布式算法。下图展示了 OceanBase 二阶段分布式计划的一个例子。

OceanBase 二阶段的分布式计划优化方法能减少优化空间,降低优化复杂度,但是因为在第一阶段优化的时候没有考虑算子的分布式信息,所以可能导致生成的计划次优。目前 OceanBase 正在实现一阶段的分布式计划优化:

1、在 System-R 的 Bottom-up 的动态规划算法中,枚举所有算子的所有分布式实现并且维护算子的物理属性。

2、在 System-R 的 Bottom-up 的动态规划算法中,对于每一个枚举的子集, 保留代价最小/有 Interesting order/有 Interesting 分区的计划。

一阶段的分布式计划优化可能会导致计划空间增长很快,所以必须要有一些 Pruning 规则来减少计划空间或者跟本地优化一样在计划空间比较大的时候,使用遗传算法或者启发式规则来解决这个问题。

3.OceanBase 计划管理机制

OceanBase 基于蚂蚁/阿里真实的业务场景,构建了一套完善的计划缓存机制和计划演进机制。

OceanBase 计划缓存机制
如下图所示,OceanBase 目前使用参数化计划缓存的方式。这里涉及到两个问题:为什么选择参数化以及为什么选择缓存?

1、参数化:在蚂蚁/阿里很多真实业务场景下,为每一个参数缓存一个计划是不切实际的。考虑一个根据订单号来查询订单信息的场景,在蚂蚁/阿里高并发的场景下,为每一个订单号换成一个计划是不切实际的,而且也不需要,因为一个带订单号的索引能解决所有参数的场景。

2、计划缓存:计划缓存是因为性能的原因,对于蚂蚁/阿里很多真实业务场景来说,如果命中计划,那么一个查询的性能会在几百 us,但是如果没有命中计划,那么性能大概会在几个 ms。对于高并发,低时延的场景,这种性能优势是很重要的。

OceanBase 使用参数化计划缓存的方式,但是在很多蚂蚁真实的业务场景下,对所有的参数使用同一个计划并不是最优的选择。考虑一个蚂蚁商户域的业务场景,这个场景以商户的维度去记录每一笔账单信息,商户可以根据这些信息做一些分析和查询。这种场景肯定会存在大小账号问题,如下图所示,淘宝可能贡献了 50% 的订单,LV 可能只贡献了 0.1% 的订单。考虑查询“统计一个商户过去一年的销售额”,如果是淘宝和美团这种大商户,那么直接主表扫描会是一个合理的计划,对于 LV 这种小商户,那么走索引会是一个合理的计划。

为了解决不同参数对应不同计划的问题,OceanBase 实现了如下图所示的自适应计划匹配。该方法会通过直方图和执行反馈来监控每一个缓存的计划是否存在不同参数需要对应不同计划的问题。一旦存在,自适应计划匹配会通过渐进式的合并选择率空间来达到把整个选择率空间划分成若干个计划空间(每个空间对应一个计划)的目的。

OceanBase 计划演进机制
在蚂蚁/阿里很多高并发,低时延的业务场景下,OceanBase 必须要保证新生成的计划不会导致性能回退。下图展示了 OceanBase 对新计划的演进过程。不同于传统的数据库系统采用定时任务和后台进程演进的方式,OceanBase 会使用真实的流量来进行演进,这样的一个好处是可以及时的更新比较优的计划。比如当业务新建了一个更优的索引时,传统数据库系统并不能立刻使用该索引,需要在演进定时任务启动后才能演进验证使用,而 OceanBase 可以及时的使用该计划。

总结

OceanBase 查询优化器的实现立足于自身架构和业务场景特点,比如 LSM-TREE 存储结构、Share-Nothing 的分布式架构和大规模的运维稳定性。OceanBase 致力于打造基于 OLTP 和 OLAP 融合的查询优化器。从 OLTP 的角度看,我们立足于蚂蚁/阿里真实业务场景,完美承载了业务需求。从 OLAP 的角度看,我们对标商业数据库,进一步打磨我们 HTAP 的优化器能力。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

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

相关文章

SpringBoot2 集成 xxl-job任务调度中心

接上一篇&#xff1a; 搭建xxl-job任务调度中心 https://gblfy.blog.csdn.net/article/details/113809843 文章目录一、SpringBoot 配置1. maven依赖2. 执行器配置 application.yml3. 执行器组件配置4. 部署执行器项目二、xxl-job任务调度中心2.1. 执行器管理2.2. 任务管理三、…

HelloWorld

HelloWorld 创建一个Java文件 文件后缀名为.javaHello.java 编写代码 public class Hello{public static void main(String[] args){System.out.print("Hello, world!");} }编译java文件 javac Hello.java会多出一个Hello.class 文件 运行class文件 java Hell…

运行Java程序时 Tomcat出错 显示端口被占用

解决方法&#xff1a;命令提示符&#xff08;管理员&#xff09; 输入netstat -ano | findstr 8080 检查8080端口有哪些进程 输入taskkill -pid 11728 -f 关闭11728的进程

从开源小白到 Apache Member,我的成长之路

我们走过的每一步路&#xff0c;都会留下印记&#xff0c;越坚实&#xff0c;越清晰。 近日&#xff0c;Apache 软件基金会&#xff08;ASF&#xff09;官方 Blog 宣布全球新增 40 位 Apache Member&#xff0c;张乎兴有幸成为其中一位。 目前&#xff0c;全球共有771位 ASF …

当你打开天猫的那一刻,推荐系统做了哪些工作?

当年打开天猫的那一刻&#xff0c;它为你完成了华丽的变身&#xff0c;成为世上独一无二的“天猫”&#xff0c;这就是智能推荐的力量。今天&#xff0c;来自阿里巴巴搜索推荐事业部的算法工程师陈启伟为你介绍天猫如何玩转首页个性化推荐&#xff0c;揭开搜索推荐的神秘面纱。…

百万人学AI:CSDN重磅共建人工智能技术新生态

站在AI发展的新十年起点上&#xff0c;CSDN将发挥开发者优势&#xff0c;与中国AI各行业和企业共建“百万人学AI”新技术生态。作者 | CSDN新媒体事业部8年前&#xff0c;现图灵奖得主Hinton团队在ImageNet竞赛中首次使用深度学习完胜Google等其它团队&#xff0c;顿时让工业界…

牛客网SQL篇刷题篇(38-47)

1.视图&#xff1a;视图是可视化的表。 视图的作用&#xff1a; 第一点&#xff1a;使用视图&#xff0c;可以定制用户数据&#xff0c;聚焦特定的数据。 解释&#xff1a; 在实际过程中&#xff0c;公司有不同角色的工作人员&#xff0c;我们以销售公司为例的话&#xff0…

SpringBoot2 集成 xxl-job任务调度中心_参数传递

文章目录一、xxl-job任务调度中心1. 调度中心创建任务2. 调度中心创建执行器二、执行器任务编码2.1. 单参数2.2. 多参数三、调度中心参数传递测试3.1. 单个参数传递3.2. 多个参数传递前提&#xff1a;执行器和xxl-job任务调度中心启动完毕 一、xxl-job任务调度中心 1. 调度中心…

Java-用IDEA创建Java项目

1. 创建项目 2. 创建空项目 3. 输入项目名 &#xff14;.配置JDK 点击Project Structure 配置JDK 点击Apply->OK 5. 新建模块 https://www.bilibili.com/video/BV12J41137hu?p21&spm_id_frompageDriver

DevOps:从「蒸汽时代」到「高铁时代」,SUNMI DevOps转型之路 | 原力计划

作者 | 文振熙、刘文沣责编 | 徐威龙封图| CSDN 下载于视觉中国商米科技成立于 2013 年&#xff0c;总部位于上海市杨浦区创智天地&#xff0c;是一家具有产品创新基因和互联网基因的公司。商米在短时间内迅速成长为一家近1000人的企业&#xff0c;产品研发人数占比一度超过70%…

SpringBoot2 集成 xxl-job任务调度中心_路由策略

文章目录一、简述二、故障转移演示2.1. 启动2个执行器2.2. 添加执行器ip2.3. 故障转移策略2.4. 启动任务2.5. 模拟8081执行器宕机2.6. 结论三、轮训策略演示3.1. 启动2个执行器3.2. 添加执行器ip3.3. 轮训策略3.4. 启动任务3.5. 日志分析3.6. 故障转移3.7. 重新启动8082执行器四…

Uniapp组件之间传参

1.父组件内引入子组件&#xff0c;并且子组件使用父组件内的数据 将子组件引入到父组件&#xff1a; <uni-pop :opts"defaultOptions"></uni-pop> import uniPop from /components/uniPop/uniPop.vue 子组件使用父组件内的数据&#xff1a; 2------创建…

基于大数据的舆情分析系统架构 - 架构篇

前言 互联网的飞速发展促进了很多新媒体的发展&#xff0c;不论是知名的大V&#xff0c;明星还是围观群众都可以通过手机在微博&#xff0c;朋友圈或者点评网站上发表状态&#xff0c;分享自己的所见所想&#xff0c;使得“人人都有了麦克风”。不论是热点新闻还是娱乐八卦&am…

Java-标识符和关键字

关键字 标识符 https://www.bilibili.com/video/BV12J41137hu?p22&spm_id_frompageDriver

SpringBoot2 集成 xxl-job任务调度中心_阻塞策略

阻塞处理策略&#xff1a;调度过于密集执行器来不及处理时的处理策略&#xff0c;策略包括&#xff1a;单机串行&#xff08;默认&#xff09;、丢弃后续调度、覆盖之前调度 阻塞处理策略说明单机串行&#xff08;默认&#xff09;任务依次排队执行丢弃后续调度当上一个任务没…

反转!Python再次卫冕2020年编程榜,Java和C回落,你怎么看?​

2020年转眼Q1季度快要结束&#xff0c;在近几个月的榜单中&#xff0c;Python持续19年的火爆&#xff0c;走在在卫冕的道路&#xff0c;并且与老牌语言Java、C的差距拉得更远了一些。近期Udemy 制作了一份《2020 年职场学习趋势报告》&#xff0c;指出了哪些技能最受职场人关注…

HTTP状态码415 springboot项目

1.415报错&#xff0c;有可能是parameter写错了&#xff0c;前台不接收这种形式 controller写RequestBody&#xff0c;前台url写&#xff1f;name1&number1就会报错415

Windows下安装ab

文章目录1. 官网地址2. 传送门3. 发起压测1. 官网地址 https://www.apachelounge.com/download/ https://www.apachelounge.com/download/ 2. 传送门 快速下载 httpd-2.4.46-win32 快速下载 httpd-2.4.46-win64 3. 发起压测 进入bin目录 模拟100个请求 10个并发 请求慕…

Hive 热门数据分析面试题解析

作者 | 数据管道责编 | 徐威龙封图| CSDN 下载于视觉中国SQL中有一类函数叫聚合函数&#xff0c;比如count、sum、avg、min、max等&#xff0c;这些函数的可以将多行数据按照规整聚集为一行&#xff0c;一般聚集前的数据行要大于聚集后的数据行。而有时候我们不仅想要聚集前的数…

不改代码也能全面 Serverless 化,阿里中间件如何破解这一难题?

Serverless 话题涉及范围极广&#xff0c;几乎包含了代码管理、测试、发布、运维和扩容等与应用生命周期关联的所有环节。在线应用如何不改代码也能迁移到 Serverless 架构&#xff1f;今天&#xff0c;我们来揭秘阿里巴巴成千上万在线应用的Serverless 演进过程。 AWS Lambda …