mysql - 查询成本 - 优化器

查询成本

我们之前老说MySQL执行一个查询可以有不同的执行方案,它会选择其中成本最低,或者说代价最低的那种方案去真正的执行查询。不过我们之前对成本的描述是非常模糊的,其实在MySQL中一条查询语句的执行成本是由下边这两个方面组成的:

  • I/O成本

    我们的表经常使用的MyISAMInnoDB存储引擎都是将数据和索引都存储到磁盘上的,当我们想查询表中的记录时,需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为I/O成本。

  • CPU成本

    读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为CPU成本。

对于InnoDB存储引擎来说,页是磁盘和内存之间交互的基本单位,设计MySQL的大叔规定读取一个页面花费的成本默认是1.0,读取以及检测一条记录是否符合搜索条件的成本默认是0.21.0

我们还是选创建一个表,并插入一万条数据

CREATE TABLE single_table (id INT NOT NULL AUTO_INCREMENT,key1 VARCHAR(100),key2 INT,key3 VARCHAR(100),key_part1 VARCHAR(100),key_part2 VARCHAR(100),key_part3 VARCHAR(100),common_field VARCHAR(100),PRIMARY KEY (id),KEY idx_key1 (key1),UNIQUE KEY idx_key2 (key2),KEY idx_key3 (key3),KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;

单表查询成本

基于成本的优化步骤

在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案,这个成本最低的方案就是所谓的执行计划,之后才会调用存储引擎提供的接口真正的执行查询,这个过程总结一下就是这样:

  1. 根据搜索条件,找出所有可能使用的索引
  2. 计算全表扫描的代价
  3. 计算使用不同索引执行查询的代价
  4. 对比各种执行方案的代价,找出成本最低的那一个

下边我们就以一个实例来分析一下这些步骤,单表查询语句如下:

SELECT * FROM single_table WHERE key1 IN ('a', 'b', 'c') AND key2 > 10 AND key2 < 1000 AND key3 > key2 AND key_part1 LIKE '%hello%' ANDcommon_field = '123';
1.对于上述查询操作,可以使用到的索引有idx_key1idx_key2,可以使用到的索引称为possible keys
2.对于InnoDB存储引擎来说,全表扫描的意思就是把聚簇索引中的记录都依次和给定的搜索条件做一下比较,把符合搜索条件的记录加入到结果集,所以需要将聚簇索引对应的页面加载到内存中,然后再检测记录是否符合搜索条件。由于查询成本=I/O成本+CPU成本,所以计算全表扫描的代价需要两个信息:
  • 聚簇索引占用的页面数(I/O成本)
  • 该表中的记录数(CPU成本)

我们可以通过 SHOW TABLE STATUS语句来查询single_table表的统计信息,TABLE STATUS中记录了表的行数(ROWS)和表占用的存储空间字节数(Data_length)

页面数为 Data_length/每个页面占用的字节数

 --  Rows: 9693(不是精确值;计算方式:按照一定算法(并不是纯粹随机的)选取几个叶子节点页面,计算每个页面中主键值记录数量,然后计算平均一个页面中主键值的记录数量乘以全部叶子节点的数量就算是该表的n_rows值。), Data_length: 1589248-- 页面数,一个页面占用16kb(这个其实不精确,全表扫描只会用到内节点的最左节点,然后遍历全部叶节点)1589248 ÷ 16 ÷ 1024 = 97-- i/o成本,1.1为偏差常数97 x 1.0 + 1.1 = 98.1-- cpu成本,1.0为偏差常数9693 x 0.2 + 1.0 = 1939.6-- 总成本98.1 + 1939.6 = 2037.7
3. 计算使用不同索引执行查询的代价
idx_key2执行查询的成本分析

image_1d6cb8nolj1714dimrf1iu64l99.png-124.3kB

-- 查询二级索引I/O成本,一个范围查询默认一个页面读取
1 x 1.0 = 1.0
-- 范围查询中有95条数据,该数据是通过范围边界计算得到的(或通过父节点的目录项记录计算),0.01是偏差常数
95 x 0.2 + 0.01 = 19.01
-- 回表的I/O成本
95 x 1.0 = 95.0
-- 回表后要将筛选
95 x 0.2 = 19.0
-- 总成本
96.0 + 38.01 = 134.01
使用idx_key1执行查询的成本分析

idx_key1对应的搜索条件是:key1 IN ('a', 'b', 'c'),也就是说相当于3个单点区间:

  • ['a', 'a']
  • ['b', 'b']
  • ['c', 'c']

image_1cubvsars1i0rvdc11b3118th9830.png-124.1kB

-- 一个范围查询等于一个I/O操作
3 x 1.0 = 3.0
-- 范围查询中的数据数
35 + 44 + 39 = 118
-- 读取符合的数据,cup成本
118 x 0.2 + 0.01 = 23.61
-- 回表
118 x 1.0 = 118.0
-- 回表后判断
118 x 0.2 = 23.6
-- 总和
3.0 + 118 x 1.0 + 118 x 0.2 + 0.01 + 118 x 0.2 = 168.21
4. 对比各种执行方案的代价,找出成本最低的那一个

下边把执行本例中的查询的各种可执行方案以及它们对应的成本列出来:

  • 全表扫描的成本:2037.7
  • 使用idx_key2的成本:134.01
  • 使用idx_key1的成本:168.21

很显然,使用idx_key2的成本最低,所以当然选择idx_key2来执行查询喽。

单点范围查询中的成本计算

有时候使用索引执行查询时会有许多单点区间,比如使用IN语句就很容易产生非常多的单点区间,比如下边这个查询(下边查询语句中的...表示还有很多参数):

sql
复制代码SELECT * FROM single_table WHERE key1 IN ('aa1', 'aa2', 'aa3', ... , 'zzz');

很显然,这个查询可能使用到的索引就是idx_key1,由于这个索引并不是唯一二级索引,所以并不能确定一个单点区间对应的二级索引记录的条数有多少,需要我们去计算。计算方式我们上边已经介绍过了,就是先获取索引对应的B+树的区间最左记录区间最右记录,然后再计算这两条记录之间有多少记录(记录条数少的时候可以做到精确计算,多的时候只能估算)。设计MySQL的大叔把这种通过直接访问索引对应的B+树来计算某个范围区间对应的索引记录条数的方式称之为index dive

小贴士:

dive直译为中文的意思是跳水、俯冲的意思,原谅我的英文水平捉急,我实在不知道怎么翻译 index dive,索引跳水?索引俯冲?好像都不太合适,所以压根儿就不翻译了。不过大家要意会index dive就是直接利用索引对应的B+树来计算某个范围区间对应的记录条数。

有零星几个单点区间的话,使用index dive的方式去计算这些单点区间对应的记录数也不是什么问题,可是你架不住有的孩子憋足了劲往IN语句里塞东西呀,我就见过有的同学写的IN语句里有20000个参数的🤣🤣,这就意味着MySQL的查询优化器为了计算这些单点区间对应的索引记录条数,要进行20000次index dive操作,这性能损耗可就大了,搞不好计算这些单点区间对应的索引记录条数的成本比直接全表扫描的成本都大了。所以对于太多的index dive操作要使用所谓的索引统计数据来进行估算。

索引统计数据就是通过索引的行数/索引基数(索引基数为索引中的不重复值,若基数为1这索引中的值都是一样的),这样可以获得一个单点范围查询的大概数量。

若索引的行数为10000,索引基数1000,则相同索引有10条重复的,单点范围就是10

多表查询成本

准备工作

连接查询至少是要有两个表的,只有一个single_table表是不够的,所以为了故事的顺利发展,我们直接构造一个和single_table表一模一样的single_table2表。为了简便起见,我们把single_table表称为s1表,把single_table2表称为s2表。

我们前边说过,MySQL中连接查询采用的是嵌套循环连接算法,驱动表会被访问一次,被驱动表可能会被访问多次,所以对于两表连接查询来说,它的查询成本由下边两个部分构成:

  • 单次查询驱动表的成本
  • 多次查询被驱动表的成本(具体查询多少次取决于对驱动表查询的结果集中有多少条记录)

我们把对驱动表进行查询后得到的记录条数称之为驱动表的扇出(英文名:fanout

计算公式

连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本

对于左(外)连接和右(外)连接查询来说,它们的驱动表是固定的,所以想要得到最优的查询方案只需要:

  • 分别为驱动表和被驱动表选择成本最低的访问方法。

可是对于内连接来说,驱动表和被驱动表的位置是可以互换的,所以需要考虑两个方面的问题:

  • 不同的表作为驱动表最终的查询成本可能是不同的,也就是需要考虑最优的表连接顺序。
  • 然后分别为驱动表和被驱动表选择成本最低的访问方法。

很显然,计算内连接查询成本的方式更麻烦一些,下边我们就以内连接为例来看看如何计算出最优的连接查询方案。

比如对于下边这个查询来说:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2 ON s1.key1 = s2.common_field WHERE s1.key2 > 10 AND s1.key2 < 1000 AND s2.key2 > 1000 AND s2.key2 < 2000;

可以选择的连接顺序有两种:

  • s1连接s2,也就是s1作为驱动表,s2作为被驱动表。
  • s2连接s1,也就是s2作为驱动表,s1作为被驱动表。
第一种s1作为驱动表,s2作为被驱动表
  • 分析对于驱动表的成本最低的执行方案

    首先看一下涉及s1表单表的搜索条件有哪些:

    • s1.key2 > 10 AND s1.key2 < 1000

    所以这个查询可能使用到idx_key2索引,从全表扫描和使用idx_key2这两个方案中选出成本最低的那个,这个过程我们上边都唠叨过了,很显然使用idx_key2执行查询的成本更低些。

  • 然后分析对于被驱动表的成本最低的执行方案

    此时涉及被驱动表s2的搜索条件就是:

    • s2.common_field = 常数(这是因为对驱动表s1结果集中的每一条记录,都需要进行一次被驱动表s2的访问,此时那些涉及两表的条件现在相当于只涉及被驱动表s2了。)
    • s2.key2 > 1000 AND s2.key2 < 2000

    很显然,第一个条件由于common_field没有用到索引,所以并没有什么卵用,此时访问s2表时可用的方案也是全表扫描和使用idx_key2两种,显然使用idx_key2的成本更小。

所以此时使用s1作为驱动表时的总成本就是(暂时不考虑使用join buffer对成本的影响):

使用idx_key2访问s1的成本 + s1的扇出 × 使用idx_key2访问s2的成本
第二种 s2作为驱动表,s1作为被驱动表
  • 分析对于驱动表的成本最低的执行方案

    首先看一下涉及s2表单表的搜索条件有哪些:

    • s2.key2 > 1000 AND s2.key2 < 2000

    所以这个查询可能使用到idx_key2索引,从全表扫描和使用idx_key2这两个方案中选出成本最低的那个,使用idx_key2执行查询的成本更低些。

  • 然后分析对于被驱动表的成本最低的执行方案

    此时涉及被驱动表s1的搜索条件就是:

    • s1.key1 = 常数
    • s1.key2 > 10 AND s1.key2 < 2000

    使用idx_key1可以进行ref方式的访问,使用idx_key2可以使用range方式的访问。这是优化器需要从全表扫描、使用idx_key1、使用idx_key2这几个方案里选出一个成本最低的方案。一般情况下,ref的访问方式要比range成本更低,这里假设使用idx_key1进行对s1的访问。

所以此时使用s2作为驱动表时的总成本就是:

使用idx_key2访问s2的成本 + s2的扇出 × 使用idx_key1访问s1的成本

最后优化器会比较这两种方式的最优访问成本,选取那个成本更低的连接顺序去真正的执行查询。从上边的计算过程也可以看出来,连接查询成本占大头的其实是驱动表扇出数 x 单次访问被驱动表的成本,所以我们的优化重点其实是下边这两个部分:

  • 尽量减少驱动表的扇出
    成本更低,这里假设使用idx_key1进行对s1的访问。

所以此时使用s2作为驱动表时的总成本就是:

使用idx_key2访问s2的成本 + s2的扇出 × 使用idx_key1访问s1的成本

最后优化器会比较这两种方式的最优访问成本,选取那个成本更低的连接顺序去真正的执行查询。从上边的计算过程也可以看出来,连接查询成本占大头的其实是驱动表扇出数 x 单次访问被驱动表的成本,所以我们的优化重点其实是下边这两个部分:

  • 尽量减少驱动表的扇出
  • 对被驱动表的访问成本尽量低

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

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

相关文章

分布式搜索引擎elasticsearch专栏二

上一篇的传送门&#xff1a; 分布式搜索引擎elasticsearch专栏一-CSDN博客 这一篇博文主要讲解elasticsearch的数据搜索功能。下面会分别使用DSL和RestClient实现搜索。 1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsea…

[PwnThyBytes 2019]Baby_SQL

[PwnThyBytes 2019]Baby_SQL 查看源码发现 下载源码&#xff0c;首先观察index.php 首先进入index.php&#xff0c;会执行session_start();启动session这里通过foreach将所有的环境变量的值都遍历了一遍&#xff0c;并且都使用了addslashes()进行转义&#xff0c;然后就定义了…

GO语言:函数、方法、面向对象

本文分享函数的定义、特性、defer陷阱、异常处理、单元测试、基准测试等以及方法和接口相关内容 1 函数 函数的定义 func 函数名(参数列表) (返回值列表) { // 函数体&#xff08;实现函数功能的代码&#xff09; } 匿名函数的定义就是没有函数名&#xff0c;可以当做一个函…

使用华为云HECS服务器+nodejs开启web服务

简介: 在华为云HECS服务器上使用nodejs开启一个web服务。 目录 1.开通华为云服务器 2.远程登录 2.1 使用华为官方的网页工具登录 ​编辑 2.2 使用MobaXterm登录 3 安装node 3.1 下载 2. 配置环境变量 4. 安装express模块 5.开启外网访问 1.开通华为云服务器 这…

MySQL与金蝶云星空对接集成SELECT语句连通销售订单新增(销售订单集成测试)

MySQL与金蝶云星空对接集成SELECT语句连通销售订单新增(销售订单集成测试) ​​ ​​ 数据源系统:MySQL MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQLAB公司开发&#xff0c;属于Oracle旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在WEB应用方…

ASPICE规范之系统追溯矩阵

系统追溯矩阵的需求来自 ISO26262 举例在描述系统追溯矩阵时&#xff1a;客户需求->系统需求&#xff1b;系统需求->客户需求&#xff1b;系统需求->软件需求&#xff1b;系统需求->硬件需求

【LabVIEW FPGA入门】使用FPGA实现串行同步接口(SSI)

SSI&#xff08;串行同步接口&#xff09;是连接绝对位置传感器和控制器的广泛应用的串行接口。SSI利用控制器发出一个时钟脉冲序列&#xff0c;初始化传感器的门限输出。 传感器不断更新位置数据&#xff0c;并传送到移位寄存器中。在每一个时钟脉冲序列之间&#xff…

在Ubuntu20.04(原为cuda12.0, gcc9.几版本和g++9.几版本)下先安装cuda9.0后再配置gcc-5环境

因为自己对Linux相关操作不是很熟悉&#xff0c;所以因为之前的代码报错之后决定要安cuda9.0&#xff0c;于是先安装了cuda9.0。里面用到的一些链接&#xff0c;链接文件夹时直接去copy它的路径&#xff0c;就不那么容易错了。 今天运行程序之后发现gcc环境不太匹配cuda9.0&am…

FX-数组的使用

1一维数组 1.1一维数组的创建和初始化 1.1.1数组的创建 //代码1 int arr1[10]; char arr2[10]; float arr3[1]; double arr4[20]; //代码2 //用宏定义的方式 #define X 3 int arr5[X]; //代码3 //错误使用 int count 10; int arr6[count];//数组时候可以正常创建&#xff1…

【十三】【算法分析与设计】二分查找(1)

704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4…

win10笔记本在显示设置中不慎将主显示器禁用掉导致开机黑屏的解决方案

因为笔记本电脑的显示扩展接口有问题&#xff0c;所以在电脑开机之后&#xff0c;会误识别出几个不存在的扩展屏幕&#xff0c;所以我就想从显示设置中将这几个误识别出来的扩展屏幕禁用掉&#xff08;不然鼠标总是移动到主屏幕边界之外的地方&#xff09;&#xff0c;在显示设…

2024年腾讯云GPU服务器价格表_1小时费用_一个月价格和一年优惠

腾讯云GPU服务器怎么收费&#xff1f;GPU服务器1小时多少钱&#xff1f;一个月收费价格表和一年费用标准&#xff0c;腾讯云百科txybk.com分享腾讯云GPU服务器GPU计算型GN10Xp、GPU服务器GN7、GPU渲染型 GN7vw等GPU实例费用价格&#xff0c;以及NVIDIA Tesla T4 GPU卡和V100详细…

【SZU计算机网络实验】实现流式视频传输

前言 一百年没有更新博客了&#xff0c;都怪开学一堆杂活&#xff08;x 那就顺手把实验报告转到这边吧owo 本实验为SZU原创实验&#xff0c;实验开发团队的老师和助教们都很有耐心。。大赞&#xff0c;环境没配好去群里问是秒回的 相关资料&#xff1a; 实验文档&#xff…

k8s详细教程

Kubernetes详细教程 1. Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点…

JavaScript高级(十八)---进程和线程,宏任务和微任务

进程和线程 进程&#xff08;process&#xff09;&#xff1a;计算机已经运行的程序&#xff0c;是操作系统管理程序的一种方式&#xff0c;我们可以认为&#xff0c;启动一个应用程序&#xff0c;就会默认启动一个进程&#xff08;也可能是多个进程&#xff09;。 线程&…

行业模板|DataEase制造行业大屏模板推荐

DataEase开源数据可视化分析平台于2022年6月发布模板市场&#xff08;https://templates-de.fit2cloud.com&#xff09;&#xff0c;并于2024年1月新增适用于DataEase v2版本的模板分类。模板市场旨在为DataEase用户提供专业、美观、拿来即用的大屏模板&#xff0c;方便用户根据…

智能合约 之 ERC-721

ERC-721&#xff08;Non-Fungible Token&#xff0c;NFT&#xff09;标准 ERC-721是以太坊区块链上的一种代币标准&#xff0c;它定义了一种非同质化代币&#xff08;Non-Fungible Token&#xff0c;NFT&#xff09;的标准。NFT是一种加密数字资产&#xff0c;每个代币都具有独…

【计算机网络_网络层】IP协议

文章目录 1. IP的基本概念1.1 什么是IP协议1.2 为什么要有IP协议 2. IP的协议格式3. 网段划分&#xff08;重要&#xff09;3.1 为什么要进行网段划分3.2 网段划分的规则3.2.1 古老的划分方案3.2.2 现代的划分方案 4. 特殊的IP地址5. 解决IP地址的数量限制问题6. 私有IP和公网I…

深入浅出Reactor和Proactor模式

Reactor模式和Proactor模式是两种常见的设计模式&#xff0c;用于处理事件驱动的并发编程。它们在处理IO操作时有着不同的工作方式和特点。 对于到来的IO事件&#xff08;或是其他的信号/定时事件&#xff09;&#xff0c;又有两种事件处理模式&#xff1a; Reactor模式&…

HarmonyOS NEXT应用开发之元素超出List区域

介绍 本示例介绍在List组件内实现子组件超出容器边缘的布局样式的实现方法。 List组件clip属性默认为true&#xff0c;超出容器边缘的子组件会按照List的布局范围被裁剪。为此&#xff0c;可以在List组件内部添加一个占位的ListItem&#xff0c;以达到预期的布局效果。List占…