一文带你了解MySQL之基于成本的优化

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 我们之前老说MySQL执行一个查询可以有不同的执行方案,它会选择其中成本最低,或者说代价最低的那种方案去真正的执行查询,怎么就带大家详细了解一下

一、连接查询的成本

1.1 准备数据

连接查询至少是要有两个表的,只有一个demo8表是不够的,所以为了故事的顺利发展,我们直接构造二个和demo8表一模一样的s1表和s2表


mysql> create table s1 (    

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));

Query OK, 0 rows affected (0.04 sec)

mysql> create table s2 (    

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));

Query OK, 0 rows affected (0.04 sec)

mysql> insert into s1 select * from demo8;

Query OK, 20000 rows affected (0.83 sec)

Records: 20000  Duplicates: 0  Warnings: 0

mysql> insert into s2 select * from demo8;

Query OK, 20000 rows affected (0.89 sec)

Records: 20000  Duplicates: 0  Warnings: 0


1.2 Condition filtering介绍

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


单次查询驱动表的成本

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

我们把对驱动表进行查询后得到的记录条数称之为驱动表的扇出(英文名:fanout)。很显然驱动表的扇出值越小,对被驱动表的查询次数也就越少,连接查询的总成本也就越低。当查询优化器想计算整个连接查询所使用的成本时,就需要计算出驱动表的扇出值,有的时候扇出值的计算是很容易的,比如下边这两个查询:


查询一:


select * from s1 inner join s2;

假设使用s1表作为驱动表,很显然对驱动表的单表查询只能使用全表扫描的方式执行,驱动表的扇出值也很明确,那就是驱动表中有多少记录,扇出值就是多少。统计数据中s1表的记录是数是20250,也就是说优化器就直接会把20250当作在s1表的扇出值。


查询二:


select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000;


仍然假设s1表是驱动表的话,很显然对驱动表的单表查询可以使用idx_key2索引执行查询。此时idx_key2的范围区间(10, 1000)中有多少条记录,那么扇出值就是多少。我们前边计算过,满足idx_key2的范围区间(10, 1000)的记录数是989条,也就是说本查询中优化器会把95当作驱动表s1的扇出值。


当然,事情不会总是一帆风顺的,要不然剧情就太平淡了。有的时候扇出值的计算就变得很棘手,比方说下边这个查询:


查询三:


select * from s1 inner join s2 where s1.common_field > 'xyz'


本查询和查询一类似,只不过对于驱动表s1多了一个common_field > 'xyz'的搜索条件。查询优化器又不会真正的去执行查询,所以它只能猜这20250记录中有多少条记录满足common_field > 'xyz’条件


查询四:


select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000 and s1.common_field > 'xyz'


不过因为本查询可以使用idx_key2索引,所以只需要从符合二级索引范围区间的记录中猜有多少条记录符合common_field > 'xyz'条件,也就是只需要猜在989条记录中有多少符合common_field > 'xyz'条件


查询五:


select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000 and s1.key1 in('aa','bb','cc') and s1.common_field > 'xyz'

1

本查询和查询二类似,不过在驱动表s1选取idx_key1索引执行查询后,优化器需要从符合二级索引范围区间的记录中猜有多少条记录符合下边两个条件:


key2 > 10 and key2 < 1000

common_field > ‘xyz’

也就是优化器需要猜在230条记录中有多少符合上述两个条件的。


说了这么多,其实就是想表达在这两种情况下计算驱动表扇出值时需要靠猜:


如果使用的是全表扫描的方式执行的单表查询,那么计算驱动表扇出时需要猜满足搜索条件的记录到底有多少条

如果使用的是索引执行的单表扫描,那么计算驱动表扇出的时候需要猜满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条

MySQL把这个猜的过程称之为condition filtering。当然,这个过程可能会使用到索引,也可能使用到统计数据,也可能就是MySQL单纯的瞎猜,整个评估过程挺复杂的,所以我们就跳过了哈。


1.3 多表连接的成本分析

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


对于两表连接,比如表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!


二、调节成本的常数

我们之前介绍了两个成本常数:


读取一个页面花费的成本默认是:0.25

检测一条记录是否符合搜索条件的成本默认是:0.1

其实除了这两个成本常数外,MySQL还支持好多,他们被存储到了mysql数据库(这是一个系统数据库,我们之前介绍过)的两个表中:


mysql> show tables from mysql like '%cost%';

+--------------------------+

| Tables_in_mysql (%cost%) |

+--------------------------+

| engine_cost              |

| server_cost              |

+--------------------------+

2 rows in set (0.06 sec)


我们在之前就说过,一条语句执行其实是分为两层的:


server层

存储引擎层

在server层进行连接管理、查询缓存、语法解析、查询优化等操作,在存储引擎层执行具体的数据存取操作。也就是说一条语句在server层中执行的成本是和它操作的表使用的存储引擎是没关系的,所以关于这些操作对应的成本常数就存储在了server_cost表中,而依赖于存储引擎的一些操作对应的成本常数就存储在了engine_cost表中


2.1 server_cost 表

server_cost表中在server层进行的一些操作对应的成本常数,具体内容如下:


mysql> select * from mysql.server_cost;

+------------------------------+------------+---------------------+---------+---------------+

| cost_name                    | cost_value | last_update         | comment | default_value |

+------------------------------+------------+---------------------+---------+---------------+

| disk_temptable_create_cost   |       NULL | 2023-04-24 19:39:12 | NULL    |            20 |

| disk_temptable_row_cost      |       NULL | 2023-04-24 19:39:12 | NULL    |           0.5 |

| key_compare_cost             |       NULL | 2023-04-24 19:39:12 | NULL    |          0.05 |

| memory_temptable_create_cost |       NULL | 2023-04-24 19:39:12 | NULL    |             1 |

| memory_temptable_row_cost    |       NULL | 2023-04-24 19:39:12 | NULL    |           0.1 |

| row_evaluate_cost            |       NULL | 2023-04-24 19:39:12 | NULL    |           0.1 |

+------------------------------+------------+---------------------+---------+---------------+

6 rows in set (0.00 sec)


我们先看一下server_cost各个列都分别是什么意思:


cost_name:表示成本常数的名称

cost_value:表示成本常数对应的值。如果该列的值为NULL的话,意味着对应的成本常数会采用默认值

last_update:表示最后更新记录的时间

comment:注释

default_value :默认值

从server_cost中的内容可以看出来,目前在server层的一些操作对应的成本常数有以下几种:

成本常数名称

默认值

描述

disk_temptable_create_cost

40.0

创建基于磁盘的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表

disk_temptable_row_cost

1.0

向基于磁盘的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表

key_compare_cost

0.1

两条记录做比较操作的成本,多用在排序操作上,如果增大这个值的话会提升filesort的成本,让优化器可能更倾向于使用索引完成排序而不是filesort

memory_temptable_create_cost

2.0

建基于内存的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表

memory_temptable_row_cost

0.2

向基于内存的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表

row_evaluate_cost

0.2

这个就是我们之前一直使用的检测一条记录是否符合搜索条件的成本,增大这个值可能让优化器更倾向于使用索引而不是直接全表扫描


小提示:

MySQL在执行入如DISTINCT查询、分组查询、Union查询以及某些特殊条件下的排序查询都可能在内部先创建一个临时表,使用这个临时表来辅助完成查询(比如对于DISTINCT查询可以建一个带有UNIQUE索引的临时表,直接把需要去重的记录插入到这个临时表中,插入完成之后的记录就是结果集了)。在数据量大的情况下可能创建基于磁盘的临时表,也就是为该临时表使用MyISAM、InnoDB等存储引擎,在数据量不大时可能创建基于内存的临时表,也就是使用品那个Memory存储引擎。这里大家知道创建临时表和对这个临时表进行写入和读取的操作代价还是很高的就行了。


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


步骤一: 对我们感兴趣的成本常数做更新操作


比如说我们想把检测⼀条记录是否符合搜索条件的成本增大到0.3,那么就可以这样写更新语句:


update mysql.server_cost set cost_value = 0.4 where cost_name = 'row_evaluate_cost';

1

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


flush optimizer_costs;

1

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


2.2 engine_cost 表

engine_cost表表中在存储引擎层进行的一些操作对应的成本常数,具体内容如下:


mysql> select * from mysql.engine_cost;

+-------------+-------------+------------------------+------------+---------------------+---------+---------------+

| engine_name | device_type | cost_name              | cost_value | last_update         | comment | default_value |

+-------------+-------------+------------------------+------------+---------------------+---------+---------------+

| default     |           0 | io_block_read_cost     |       NULL | 2023-04-24 19:39:12 | NULL    |             1 |

| default     |           0 | memory_block_read_cost |       NULL | 2023-04-24 19:39:12 | NULL    |          0.25 |

+-------------+-------------+------------------------+------------+---------------------+---------+---------------+

2 rows in set (0.01 sec)


与server_cost相比,engine_cost多了两个列:


engine_name列:指成本常数适用的存储引擎名称。如果该值为default,意味着对应的成本常数适用于所有的存储引擎

device_type列:指存储引擎使⽤的设备类型,这主要是为了区分常规的机械硬盘和固态硬盘,不过在MySQL 5.7.21这个版本中并没有对机械硬盘的成本和固态硬盘的成

本作区分,所以该值默认是0

我们从engine_cost表中的内容可以看出来,目前支持的存储引擎成本常数只有两个:

成本常数名称

默认值

描述

io_block_read_cost

1.0

从磁盘上读取一个块对应的成本。请注意我使用的是块,而不是页这个词。对于InnoDB存储引擎来说,一个页就是一个块,不过对于MyISAM存储引擎来说,默认是以4096字节作为一个块的。增大这个值会加重I/O成本,可能让优化器更倾向于选择使用索引执行查询而不是执行全表扫描

memory_block_read_cost

0.25

与上一个参数类似,只不过衡量的是从内存中读取一个块对应的成本

大家看完这两个成本常数的默认值是不是有些疑惑,怎么从内存中和从磁盘上读取一个块的默认成本是不一样的?这主要是因为随着MySQL的发展,MySQL可以准确预测哪些块在磁盘上,那些块在内存中。


与更新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;


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4天前
|
缓存 监控 关系型数据库
如何优化MySQL查询速度?
如何优化MySQL查询速度?【10月更文挑战第31天】
15 3
|
6天前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
24 1
|
13天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:百万级数据统计优化实践
【10月更文挑战第21天】 在处理大规模数据集时,传统的单体数据库解决方案往往力不从心。MySQL和Redis的组合提供了一种高效的解决方案,通过将数据库操作与高速缓存相结合,可以显著提升数据处理的性能。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
42 9
|
13天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
38 5
|
18天前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
23 1
|
18天前
|
缓存 关系型数据库 MySQL
一文彻底弄懂MySQL优化之深度分页
【10月更文挑战第24天】本文深入探讨了 MySQL 深度分页的原理、常见问题及优化策略。首先解释了深度分页的概念及其带来的性能和资源问题。接着介绍了基于偏移量(OFFSET)和限制(LIMIT)以及基于游标的分页方法,并分析了它们的优缺点。最后,提出了多种优化策略,包括合理创建索引、优化查询语句和使用数据缓存,帮助提升分页查询的性能和系统稳定性。
|
8天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
40 0
|
8天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
35 0
|
17天前
|
存储 监控 关系型数据库
MySQL并发控制与管理:优化数据库性能的关键
【10月更文挑战第17天】MySQL并发控制与管理:优化数据库性能的关键
74 0
|
20天前
|
监控 关系型数据库 MySQL
mysql8索引优化
综上所述,深入理解和有效实施这些索引优化策略,是解锁MySQL 8.0数据库高性能查询的关键。
27 0