mysql中连接查询的成本

news2025/2/5 12:50:34

大家好。上篇文章我们讲了mysql中成本的含义以及单表查询如何计算成本。现在我们接着讲讲mysql中连接查询的成本。

在讲之前,我们先创建两张一样的表single_table和single_table2,并在表中插入10000条数据。在下面的讲解中,我们称single_table为s1,single_table2为s2。

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) 
);
CREATE TABLE single_table2 ( 
   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) 
);

一、条件过滤(Condition filtering)

我们知道,MySQL中连接查询采用的是嵌套循环连接算法,驱动表会被访问一次,被驱动表可能会被访问多次,所以对于两表连接查询来说,它的查询成本由下边两个部分构成:单次查询驱动表的成本和多次查询被驱动表的成本(具体查询多少次取决于对驱动表查询的结果集中有多少条记录)。

我们把对驱动表进行查询后得到的记录条数称之为驱动表的扇出(fanout)。很显然驱动表的扇出值越小,对被驱动表的查询次数也就越少,连接查询的总成本也就越低。当查询优化器想计算整个连接查询所使用的成本时,就需要计算出驱动表的扇出值,下面我们看一下各种情况下的sql驱动表的扇出值如何计算:

查询1:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2;

假设使用s1 表作为驱动表,很显然对驱动表的单表查询只能使用全表扫描的方式执行,驱动表的扇出值也就是驱动表中所有的记录数。我们再通过SHOW TABLE STATUS语句来查看s1表的统计信息:
在这里插入图片描述
统计数据中s1表的记录行数是 10033,所以s1表的扇出值就是10033。

查询2:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2 WHERE s1.key2 >10 AND s1.key2 < 1000;

假设s1表是驱动表的话,对驱动表的单表查询可以使用idx_key2索引执行查询。此时 idx_key2 的范围区间 (10, 1000)中有多少条记录,那么扇出值就是多少。假若idx_key2 的范围区间(10, 1000) 的记录数是990条,也就是说本查询中优化器会把990当作驱动表s1的扇出值。

下面我们再来看一下复杂一点的查询:

查询三:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2  WHERE s1.common_field > 'xyz';

本条sql查询驱动表s1时有一个common_field > ‘xyz’ 的搜索条件。查询优化器不会真正的去执行查询,所以它只能猜这10033记录里有多少条记录满足common_field > ‘xyz’ 条件。

查询四:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2 WHERE s1.key2 > 10 AND s1.key2 < 1000 AND s1.common_field > 'xyz';

本查询和查询二类似,只是驱动表s1多了一个common_field > ‘xyz’ 的搜索条件。不过因为本查询可以使用idx_key2 索引,所以只需要从符合二级索引范围区间的记录中猜有多少条记录符合 common_field > ‘xyz’ 条件,也就是需要猜在990条记录中有多少符合 common_field > ‘xyz’ 条件。

查询五:

SELECT * FROM single_table AS s1 INNER JOIN single_table2 AS s2 WHERE s1.key2 > 10 AND s1.key2 < 1000 AND s1.key1 IN ('a', 'b', 'c') AND s1.common_field > 'xyz';

本查询和查询二类似,不过在驱动表s1选取idx_key2 索引执行查询后,优化器需要从符合二级索引范围区间的记录中猜有多少条记录符合下边两个条件:key1 IN (‘a’, ‘b’, ‘c’) 和 common_field > ‘xyz’ 也就是优化器需要猜在990条记录中有多少符合上述两个条件的。

我们总结一下:在以下这两种情况下计算驱动表扇出值时需要靠猜:

  1. 如果使用的是全表扫描的方式执行的单表查询,那么计算驱动表扇出时需要猜满足搜索条件的记录到底有多少条。
  2. 如果使用的是索引执行的单表扫描,那么计算驱动表扇出的时候需要猜满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。
    这个猜的过程我们称之为condition filtering 。这个过程可能会使用到索引,也可能使用到统计数据,也可能通过启发式规则(heuristic)进行瞎猜,大家对启发式规则有兴趣的话可以自行了解一下。

二、两表连接的成本分析

连接查询的成本计算公式是这样的:

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

对于左(外)连接和右(外)连接查询来说,它们的驱动表是固定的,所以想要得到最优的查询方案只需要: 分别为驱动表和被驱动表选择成本最低的访问方法。 可是对于内连接来说,驱动表和被驱动表的位置是可以互换的,所以需要考虑两个方面的问题:

  1. 不同的表作为驱动表最终的查询成本可能是不同的,也就是需要考虑最优的表连接顺序。

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

下边我们就以内连接为例来看看如何计算出最优的连接查询方案。

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;

这条sql可以选择的连接顺序有两种:s1连接s2 ,也就是s1作为驱动表,s2作为被驱动表。s2连接s1,也就是s2作为驱动表,s1作为被驱动表。查询优化器需要分别考虑这两种情况下的最优查询成本,然后选取那个成本更低的连接顺序以及该连接顺序下各个表的最优访问方法作为最终的查询计划。我们分别来看一下这两种情况:

使用s1作为驱动表的情况: 我们看到涉及s1表单表的搜索条件是:s1.key2 > 10 AND s1.key2 < 1000。所以这个查询可能使用到idx_key2 索引,从全表扫描和使用 idx_key2 这两个方案中选出成本最低的那个,很显然使用idx_key2 执行查询的成本更低些。

然后分析对于被驱动表的成本最低的执行方案。此时涉及被驱动表idx_key2 的搜索条件就是:

s2.common_field = 常数 和 s2.key2 > 1000 AND s2.key2 < 2000。很显然,第一个条件由于common_field 没有用到索引,所以并没有用,此时访问 single_table2 表时可用的方案也是全表扫描和使用 idx_key2 两种,很显然使用 idx_key2 的成本更小。

所以此时使用s1作为驱动表时的总成本就是:使用idx_key2访问s1的成本 + s1的扇出 × 使用idx_key2访问s2的成本。

使用s2作为驱动表的情况: 我们看到涉及s2表单表的搜索条件是:s2.key2 > 10 AND s2.key2 < 1000。所以这个查询可能使用到idx_key2 索引,从全表扫描和使用 idx_key2 这两个方案中选出成本最低的那个,很显然使用idx_key2 执行查询的成本更低些。

然后分析对于被驱动表的成本最低的执行方案。此时涉及被驱动表idx_key2 的搜索条件就是:s1.key1 = 常数 和 s1.key2 > 1000 AND s1.key2 < 2000。这时使用idx_key1 可以进行 ref 方式的访问,使用 idx_key2可以使用 range 方式 的访问。这时优化器需要从全表扫描、使用idx_key1、使用 idx_key2 这几个方案里选出一个成本最低的方案。一般情况下,ref的访问方式要比range成本最低,这里假设使用idx_key1进行对 s2 的访问。

所以此时使用single_table作为驱动表时的总成本就是:使用idx_key2访问s2的成本 + s2的扇出 × 使用idx_key1访问s1的成本。

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

这一点对于我们实际书写连接查询语句时十分有用,我们需要尽量在被驱动表的连接列上建立索引,这样就可以使用ref 访问方法来降低访问被驱动表的成本了。如果可以,被驱动表的连接列最好是该表的主键或者唯一二级索引列,这样就可以把访问被驱动表的成本降到更低了。

三、多表连接的成本分析

首先我们要考虑一下多表连接时可能产生出多少种连接顺序:

对于两表连接,比如表A和表B连接。只有 AB、BA这两种连接顺序。其实相当于2 × 1 = 2 种连接顺序。

对于三表连接,比如表A、表B、表C进行连接 有ABC、ACB、BAC、BCA、CAB、CBA这么6种连接顺序。其实相当于 3 × 2 × 1 = 6 种连接顺序。

对于四表连接的话,则会有4 × 3 × 2 × 1 = 24 种连接顺序。

对于n表连接的话,则有 n × (n-1) × (n-2) × ··· × 1 种连接顺序,就是n的阶乘种连接顺序, 也就是n! 。

有n个表进行连接,MySQL 查询优化器要每一种连接顺序的成本都计算一遍吗?答案肯定是否定的。MySQL通过很多办法减少计算非常多种连接顺序的成本的方法:

  1. 提前结束某种顺序的成本评估

MySQL 在计算各种链接顺序的成本之前,会维护一个全局的变量,这个变量表示当前最小的连接查询成本。如果在分析某个连接顺序的成本时,该成本已经超过当前最小的连接查询成本,那就不对该连接顺序继续往下分析了。比方说A、B、C三个表进行连接,已经得到连接顺序ABC是当前的最小连接成本,比方说10.0 ,在计算连接顺序BCA 时,发现B和C的连接成本就已经大于10.0时,就不再继续往后分析BCA 这个连接顺序的成本了。

  1. 系统变量optimizer_search_depth

为了防止无穷无尽的分析各种连接顺序的成本,MySQL设计了一个optimizer_search_depth 系统变量,如果连接表的个数小于该值,那么就继续穷举分析每一种连接顺序的成本,否则只对与optimizer_search_depth 值相同数量的表进行穷举分析。该值越大,成本分析的越精确,越容易得到好的执行计划,但是消耗的时间也就越长。

  1. 根据某些规则压根儿就不考虑某些连接顺序

即使是有上边两条规则的限制,但是分析多个表不同连接顺序成本花费的时间还是会很长,所以就有了所谓的启发式规则(就是根据以往经验指定的一些规则),凡是不满足这些规则的连接顺序压根儿就不分析,这样可以极大的减少需要分析的连接顺序的数量,但是也可能造成错失最优的执行计划。MySQL提供了一个系统变量optimizer_prune_level 来控制到底是不是用这些启发式规则。

四、 调节成本常数

MySQL中有很多调节成本的常数,它们被存储到了mysql数据库的engine_cost和server_cost表中。
一条语句的执行其实是分为两层的:server层和存储引擎层。在server 层进行连接管理、查询缓存、语法解析、查询优化等操作,在存储引擎层执行具体的数据存取操作。也就是说一条语句在server 层中执行的成本是和它操作的表使用的存储引擎是没关系的,所以关于这些操作对应的成本常数就存储在了server_cost 表中,而依赖于存储引擎的一些操作对应的成本常数就存储在了 engine_cost 表中。

1、server_cost 表

server_cost表中存有以下几种成本常数 :

成本常数名称默认值描述
disk_temptable_create_cost40.0创建基于磁盘的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表。
disk_temptable_row_cost1.0向基于磁盘的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表。
key_compare_cost0.1两条记录做比较操作的成本,多用在排序操作上,如果增大这个值的话会提升 filesort 的成本,让优化器可能更倾向于使用索引完成排序而不是filesort 。
memory_temptable_create_cost2.0创建基于内存的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于内 存的临时表。
memory_temptable_row_cost0.2向基于内存的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽 量少的创建基于内存的临时表。
row_evaluate_cost0.2检测一条记录是否符合搜索条件的成本,增大这个值可 能让优化器更倾向于使用索引而不是直接全表扫描。

这些成本常数在server_cost 中的初始值都是 NULL ,意味着优化器会使用它们的默认值来计算某个操作的成本,如果我们想修改某个成本常数的值的话,需要做两个步骤:

对我们感兴趣的成本常数做更新操作。比如我们想把检测一条记录是否符合搜索条件的成本增大到0.4,那么就可以这样写更新语句:

UPDATE mysql.server_cost  SET cost_value = 0.4 WHERE cost_name = 'row_evaluate_cost';

让系统重新加载这个表的值。使用下边语句即可:

FLUSH OPTIMIZER_COSTS;

当然,在修改完某个成本常数后想把它们再改回默认值的话,可以直接把cost_value的值设置为NULL ,再重新加载它就好了。

2、engine_cost 表

engine_cost表中存有以下几种成本常数 :

成本常数名称默认值描述
io_block_read_cost1.0从磁盘上读取一个块对应的成本。请注意我使用的是块,而不是页这个词儿。对于 InnoDB 存储引擎来说,一个 页就是一个块,不过对于MyISAM存储引擎来说,默认是以 4096 字节作为一个块的。
memory_block_read_cost1.0与上一个参数类似,只不过衡量的是从内存中读取一个块对应的成本。

这两个成本常数一个是从内存中读取一个块,一个是和从磁盘上读取一个块,但是默认成本是一样的。这主要是因为在MySQL 目前的实现中,并不能准确预测某个查询需要访问的块中有哪些块已经加载到内存中,有哪些块还停留在磁盘上,所以MySQL不管这个块有没有加载到内存中,使用的成本都是1.0。

与更新server_cost 表中的记录一样,我们也可以通过更新 engine_cost 表中的记录来更改关于存储引擎的成本常数,我们也可以通过为engine_cost 表插入新记录的方式来添加只针对某种存储引擎的成本常数:

插入针对某个存储引擎的成本常数 比如我们想增大InnoDB 存储引擎页面 I/O 的成本,书写正常的插入语句即可:

INSERT INTO mysql.engine_cost VALUES ('InnoDB', 0, 'io_block_read_cost', 2.0, CURRENT_TIMESTAMP, 'increase Innodb I/O cost');

让系统重新加载这个表的值。使用下边语句即可:

FLUSH OPTIMIZER_COSTS

好了,到这里我们就讲完了,欢迎大家在评论区留言讨论,也希望大家能给作者点个关注,谢谢大家!最后依旧是请各位老板有钱的捧个人场,没钱的也捧个人场,谢谢各位老板!

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

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

相关文章

Ubuntu22.04之解决:忘记登录密码(二百三十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

OpenHarmony 实战开发——ArkUI中的线程和看门狗机制

一、前言 本文主要分析ArkUI中涉及的线程和看门狗机制。 二、ArkUI中的线程 应用Ability首次创建界面的流程大致如下&#xff1a; 说明&#xff1a; • AceContainer是一个容器类&#xff0c;由前端、任务执行器、资源管理器、渲染管线、视图等聚合而成&#xff0c;提供了生…

Transformers集成SwanLab实现AI训练可视化监控

&#x1f917;HuggingFace Transformers Hugging Face 的 Transformers 是一个非常流行的开源库&#xff0c;它提供了大量预训练的模型&#xff0c;主要用于自然语言处理&#xff08;NLP&#xff09;任务。这个库的目标是使最新的模型能够易于使用&#xff0c;并支持多种框架&…

Android低代码开发 - MenuPanel的源码剖析和基本使用

看了我上篇文章Android低代码开发 - 像启蒙和乐高玩具一样的MenuPanel 之后&#xff0c;本篇开始讲解代码。 源代码剖析 首先从MenuPanelItemRoot讲起。 package dora.widget.panelinterface MenuPanelItemRoot {/*** 菜单的标题。** return*/var title: String?fun hasTit…

第16篇:JTAG UART IP应用<三>

Q&#xff1a;如何通过HAL API函数库访问JTAG UART&#xff1f; A&#xff1a;Quartus硬件工程以及Platform Designer系统也和第一个Nios II工程--Hello_World的Quartus硬件工程一样。 Nios II软件工程对应的C程序调用HAL API函数&#xff0c;如open用于打开和创建文件&#…

链表经典题目—相交链表和链表倒数第k个节点

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

网络编程基础(一)

目录 前言 一、网络体系架构 1.1 OSI 1.2 TCP/IP协议簇体系架构 二、TCP和UDP的不同 1.TCP 2.UDP 三、网络编程基础相关概念 1.字节序 1.在计算机中有关多字节整数的存储方式&#xff0c;根据主机CPU处理数据的方式不同&#xff0c;我们将主机分为大端存储和小端存储…

一招解决Redis缓存穿透,缓存雪崩,缓存击穿问题【超详细版】

文章目录 小故事一、为什么要使用缓存?二、什么是缓存穿透&#xff1f;怎么解决&#xff1f;2.1解决方案2.2代码实现 三、什么是缓存击穿&#xff1f;怎么解决&#xff1f;3.1解决方案3.2代码实现 四、什么是缓存雪崩&#xff1f;怎么解决&#xff1f;4.1解决方案 五、Redis缓…

气泡水位计的安装方法详解(二)

气泡水位计的安装方法详解&#xff08;二&#xff09; 产品简介 气泡式水位计ZL-BWL-013是一款适用于水文、水利信息化建设领域的新一代水位测量类设备&#xff0c;产品执行GB/T 11828.2-2022标准。ZL-BWL-013气泡水位计&#xff0c;具有安装方便、易于操作&#xff0c;高精度…

VM中Ubuntu16.04的下载以及ROS—kinetic的版本下载

一、Ubuntu镜像地址 转载备份一下&#xff1b; 官方下载地址&#xff08;不推荐&#xff09; https://www.ubuntu.com/downloadhttps://www.ubuntu.com/download 中科大源 Index of /ubuntu-releases/16.04/http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/ 阿里云开…

音视频开发9 FFmpeg 解复用框架说明,重要知识点

一&#xff0c;播放器框架 二 常用音视频术语 容器&#xff0f;文件&#xff08;Conainer/File&#xff09;&#xff1a; 即特定格式的多媒体文件&#xff0c; 比如mp4、flv、mkv等。 媒体流&#xff08;Stream&#xff09;&#xff1a; 表示时间轴上的一段连续数据&#xff0…

JVM学习-javap解析Class文件

解析字节码的作用 通过反编译生成字节码文件&#xff0c;可以深入了解Java工作机制&#xff0c;但自己分析类文件结构太麻烦&#xff0c;除了第三方的jclasslib工具外&#xff0c;官方提供了javapjavap是jdk自带的反解析工具&#xff0c;它的作用是根据class字节码文件&#x…

【GateWay】自定义RoutePredicateFactory

需求&#xff1a;对于本次请求的cookie中&#xff0c;如果userType不是vip的身份&#xff0c;不予访问 思路&#xff1a;因为要按照cookie参数进行判断&#xff0c;所以根据官方自带的CookieRoutePredicateFactory进行改造 创建自己的断言类&#xff0c;命名必须符合 xxxRout…

在virtualbox中ubuntu如何利用mobaxterm来拖拽文件

首先得先利用ssh、ubuntu的ip 一、开启ssh 安装 openssh-server sudo apt-get install openssh-server 检查 ssh 服务是否启动成功 sudo ps -e | grep ssh 如果有 sshd 则说明 ssh 服务已启动&#xff0c;如果没有启动&#xff0c;输入下边命令启动 ssh 服务 sudo servi…

东子哥:从来不拼搏的人,不是我的兄弟!新一轮裁员潮即将来临!

今年初&#xff0c;包括微软、亚马逊、谷歌母公司Alphabet等在内的巨头先后宣布裁员计划&#xff0c;曾掀起了一轮裁员潮。 进入年中阶段&#xff0c;特斯拉、理想汽车、TikTok、安德玛等知名巨头&#xff0c;也先后宣布裁员计划&#xff0c;难道&#xff0c;新一轮裁员潮已经…

长江电力:“你们随意,我躺赢”

“只要长江不断流&#xff0c;我们就躺着挣钱。”这是某股股吧里的股东们喊出的。 今天说的这个公司“没什么意思”&#xff0c;十年来股价一直涨一直涨&#xff0c;涨了5倍&#xff0c; &#xff08;最&#xff09;重要的是&#xff0c;持有体验特别好&#xff0c;几乎没有什…

HTTP -- HTTP概述

HTTP概述 HTTP使用的是可靠的数据传输协议。 web内容都是存储在web服务器上的&#xff0c;web服务器所使用的是http协议&#xff0c;故被称为http服务器。 web服务器是web资源的宿主&#xff0c;web资源是web内容的源头。 因特网上有数以千种的数据类型&#xff0c;http仔细的…

在Bash中解析命令行参数的两种样例脚本

文章目录 问题回答以空格分隔选项和参数以等号分隔选项和参数 参考 问题 假设&#xff0c;我有一个脚本&#xff0c;它会被这样一行调用: ./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile或者这个&#xff1a; ./myscript -v -f -d -o /fizz/someOtherFile ./fo…

C++ ─── string的模拟实现

本博客将简单实现来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。 下期我们继续讲解完整版string的模拟实现&#xff08;将不再会是浅拷贝了&#xff09; 说明&#xff1a;下述string类没有显式定义其拷贝构造函数与赋值运…

30秒学会一个ChatGpt-4o小技巧 --- 照片漫画化

文章目录 选择照片修改图片 选择照片 先选择自己的一张照片 当然首先你得能够访问ChatGpt-4o, 图片生成能力只有ChatGpt-4才有 所以我们先登录到 国内能够使用的ChatGpt网站: 我要超级GPT 然后把图片上传&#xff0c;再写提示词&#xff1a;请帮我把这种照片按照日系动漫风…