《MySQL高级篇》八、索引优化与查询优化(二)

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 《MySQL高级篇》八、索引优化与查询优化

3.2 采用内连接

1. 删除现有的索引,换成 inner join(MySQL自动选择驱动表)

drop index X on type;
drop index Y on book;# (如果已经删除了可以不用再执行该操作)
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

cf312aa7bfcb9ddc110570d56c206fb6.png


2. 为book表添加索引优化

ALTER TABLE book ADD INDEX Y (card);
EXPLAIN  SELECT SQL_NO_CACHE * FROM  type INNER JOIN book ON type.card=book.card;

f456b78d5a8be6a95e48b5ecc7fa5484.png


3. 向type表中再增加20条数据,为type表增加索引优化,观察情况


# 再向type表中插入20条数据,此时type:40条数据,book:20条数据 (过程省略)
ALTER TABLE type ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

06d040a0518dd8e8c710cbc539dfb1ee.png


上面的两次查询中,第一次是使用type作为驱动表,book作为被驱动表。而第二次是使用book作为驱动表,type作为被驱动表。对于内连接来说,查询优化器可以决定谁作为驱动表,谁作为被驱动表出现的

4. 接着,删除被驱动表的索引


DROP INDEX X ON `type`;
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

fcd137d00ba262d885fbf4f39934cad0.png


被驱动表进行了反转。这是因为内连接优化器可以决定(被)驱动表。在只有一个表存在索引的情况下,会选择存在索引的表作为被驱动表(因为被驱动表查询次数更多)

5. 再加上索引,观察结果

ALTER TABLE `type` ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

abec8ae5b82a64d48f07c3dd48cdde2b.png

被驱动表又进行了反转~

结论:对于内连接来说,在两个表的连接条件都存在索引的情况下,会选择小表作为驱动表。“小表驱动大表

3.3 join 语句原理

oin 方式连接多表,本质就是各个表之间数据的循环匹配。MySQL 5.5 版本之前,MySQL 只支持一种表间关联方式,就是嵌套循环。如果关联表的数据量很大,则 join 关联的执行时间会非常漫长。在 MySQL 5.5 以后的版本中,MySQL 通过引入 BNLJ 算法来优化嵌套执行。


3.3.1 驱动表和被驱动表


驱动表就是主表,被驱动表就是从表、非驱动表。

  • 对于内连接来说:
SELECT * FROM A JOIN B ON ...

A 并不一定就是驱动表,优化器会根据你的查询语句做优化,决定先查哪张表。先查询的哪张表就是驱动表,反之就是被驱动表。通过 explain 关键字可以查看。


3.2 已经总结了优化器选择的规律:两表中一个表有索引,一个表没有索引,则没有索引的为驱动表,有索引的为被驱动表;两个表都没有索引,则小表驱动大表。


💡Q:上面的规律是一成不变的吗?如果一个表有索引,但是数据量很小,一个表没有索引,但是数据量很大,情况会是怎样的呢?

我们要明白优化器的优化原理:对于内连接mysql会选择扫描次数比较少的作为驱动表,因此实际生产中最好使用Explain测试验证。

  • 对于外连接来说:
SELECT * FROM A LEFT JOIN B ON ...
# 或
SELECT * FROM B RIGHT JOIN A ON ...

通常,大家会认为 A 就是驱动表,B 就是被驱动表。但也未必。测试如下:


CREATE TABLE a(f1 INT,f2 INT,INDEX(f1)) ENGINE=INNODB;
CREATE TABLE b(f1 INT,f2 INT) ENGINE=INNODB;
INSERT INTO a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6);
INSERT INTO b values(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
# 测试1
EXPLAIN SELECT * FROM a LEFT JOIN b ON(a.f1=b.f1) WHERE (a.f2=b.f2);

8b7f42a9fa2e1be05aeddf7c95664f5f.png

明明我们写的是a LEFT JOIN b,但是我们执行sql查询时,却是b作为了驱动表,a作为了被驱动表。


实际上,查询优化器会帮你把外连接改造为内连接,然后根据其优化策略选择驱动表与被驱动表


3.3.2 Simple Nested-Loop Join(简单嵌套循环连接)


算法相当简单,从表 A 取出一条数据 1,遍历表 B,将匹配到的数据放到 result。以此类推,驱动表 A 中的每一条记录与被动驱动表 B 的记录进行判断:


50d97f7ca0dc9c2073c5bd7a345eaaa8.png

可以看到这种方式效率是非常低的,以上述表 A 数据 100 条,表 B 数据 1000 条,则 A*B=10 万次。开销统计如下:


image.png


当然 MySQL 肯定不会这么粗暴的进行表的连接,所以就出现了后面的两种其的优化算法。


另外,从读取记录数来看:A+B*A中,驱动表A对性能的影响权重更大。因此我们优化器会选择小表驱动大表。


3.3.3 Index Nested-Loop Join(索引嵌套循环连接)


Index Nested-Loop Join 其优化的思路主要是为了 减少内层表数据的匹配次数,所以要求被驱动表上必须 有索引 才行。通过外层表匹配条件直接与内层索引进行匹配,避免和内层表的每条记录进行比较,这样极大地减少了对内层表的匹配次数。


223ad6ae2d6f4b14055ab6981401e79c.png


驱动表中的每条记录通过被驱动表的索引进行访问,因为索引查询的成本是比较固定的,故 MySQL 优化器都倾向于使用记录数少的表作为驱动表(外表)。


image.png


如果被驱动表加索引,效率是非常高的,如果索引不是主键索引,所以还得进行一次回表查询。相比,被驱动表的索引是主键索引,效率会更高


3.3.4 Block Nested-Loop Join(快嵌套循环连接)


如果存在索引,那么会使用 index 的方式进行 join,如果 join 的列没有索引,被驱动表要扫描的次数太多了。每次访问被驱动表,其表中的记录都会被加载到内存中,然后再从驱动表中取一条与其匹配,匹配结束后清除内存,然后再从驱动表中加载一条记录,然后把驱动表的记录再加载到内存匹配,这样周而复始,大大增加了 IO 次数。为了减少被驱动表的 IO 次数,就出现了 Block Nested-Loop Join的方式


不再是逐条获取驱动表的数据,而是一块一块的获取,引入了 join buffer 缓冲区,将驱动表 join 相关的部分数据列(大小受 join buffer 的限制)缓存到 join buffer 中,然后全表扫描被驱动表,被驱动表的每一条记录一次性和 join buffer 中的所有驱动表记录进行匹配(内存中操作),将简单嵌套循环中的多次比较合并成一次,降低了被动表的访问频率。


注意:


这里缓存的不只是关联表的列,select 后面的列也会缓存起来


在一个有 N 个 join 关联的 SQL 中会分配 N-1 个 join buffer。所以查询的时候尽量减少不必要的字段,可以 让 join buffer 中存放更多的列。


7fdcbbc0ecb443f3c93a86f3189a6078.png


5169d82efc2b0494decc0630dfc33c6e.png


参数设置:


block_nested_loop


通过 show variables like '%optimizer_switch%' 查看 block_nested_loop 状态。默认是开启的。


join_buffer_size


驱动表能不能一次加载完,要看 join buffer 能不能存储所有的数据,默认情况下 join_buffer_size = 256K。


join buffer size 的最大值在 32 位系统可以申请 4G,而在 64 位操做系统下可以申请大于 4G 的 join_buffer空间(64 位 Windows 除外,其大值会被截断为 4GB并发出警告)。


3.3.5 小结


保证被驱动表的 JOIN 字段已经创建了索引(减少内层表的循环匹配次数)

需要 JOIN 的字段,数据类型保持绝对一致。

LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。

INNER JOIN 时,MySQL 会自动将小结果集的表选为驱动表 。选择相信 MySQL 优化策略。

能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)

不建议使用子查询,建议将子查询 SQL 拆开结合程序多次查询,或使用 JOIN 来代替子查询。

衍生表建不了索引

默认效率比较:INLJ > BNLJ > SNLJ

正确理解小表驱动大表:大小不是指表中的记录数,而是永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)。 比如A表有100条记录,B表有1000条记录,但是where条件过滤后,B表结果集只留下50个记录,A表结果集有80条记录,此时就可能是B表驱动A表。其实上面的例子还是不够准确,因为结果集的大小也不能粗略的用结果集的行数表示,而是表行数 * 每行大小。其实要理解你只需要结合Join Buffer就好了,因为表行数 * 每行大小越小,其占用内存越小,就可以在Join Buffer中尽量少的次数加载完了。


3.3.6 Hash Join


从 MySQL 8.0.20 版本开始将废弃 BNLJ,因为加入了 hash join 默认都会使用 hash join


Nested Loop:


对于被连接的数据子集较小的情况,Nested Loop 是个较好的选择。


Hash Join 是做 大数据集连接 时的常用方法,优化器使用两个表中较小(相对较小)的表利用 join key 在内存中建立 散列表,然后扫描较大的表并探测散列表,找出与 Hash 表匹配的行。


这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和

在表很大的情况下并不能完全放入内存,这时优化器会将它分割成 若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高 I/O 的性能。

它能够很好的工作于没有索引的大表和并行查询的环境中,并提供最好的性能。大多数人都说它是 Join 的重型升降机。Hash Join 只能应用于等值连接(如 WHERE A.COL1 = B.COL2),这是由 Hash 的特点决定的。

image.png

4. 子查询优化


MySQL 从 4.1 版本开始支持子查询,使用子查询可以进行 SELECT 语句的嵌套查询,即一个 SELECT 查询的结果作为另一个 SELECT 语句的条件。子查询可以一次性完成很多逻辑上需要多个步骤才能完成的操作 。


子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询。但是,子查询的执行效率不高。 通常我们可以将其优化成一个连接查询~


原因:


① 执行子查询时,MySQL 需要为内层查询语句的查询结果建立一个临时表 ,然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表 。这样会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。


② 子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都 不会存在索引 ,所以查询性能会受到一定的影响。


③ 对于返回结果集比较大的子查询,其对查询性能的影响也就越大。


在 MySQL 中,可以使用连接(JOIN)查询来替代子查询。 连接查询 不需要建立临时表,其 速度比子查询要快,如果查询中使用索引的话,性能就会更好。


举例1:查询学生表中是班长的学生信息


使用子查询

#创建班级表中班长的索引
CREATE INDEX idx_monitor ON class(monitor);
#查询班长的信息
EXPLAIN SELECT * FROM student stu1
    WHERE stu1.`stuno` IN (
    SELECT monitor
    FROM class c
    WHERE monitor IS NOT NULL
);
  • 推荐:使用多表查询
EXPLAIN SELECT stu1.* FROM student stu1 JOIN class c 
ON stu1.`stuno` = c.`monitor`
WHERE c.`monitor` IS NOT NULL;


举例2:取所有不为班长的同学

  • 不推荐
#查询不为班长的学生信息
EXPLAIN SELECT SQL_NO_CACHE a.* 
FROM student a 
WHERE  a.stuno  NOT  IN (
            SELECT monitor FROM class b 
            WHERE monitor IS NOT NULL);

推荐

# 转换成左连接查询
EXPLAIN SELECT SQL_NO_CACHE a.*
FROM  student a LEFT OUTER JOIN class b 
ON a.stuno =b.monitor
WHERE b.monitor IS NULL;

结论:尽量不要使用 NOT IN 或者 NOT EXISTS,用 LEFT JOIN xxx ON xx WHERE xx IS NULL 替代


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1天前
|
存储 关系型数据库 MySQL
MySQL bit类型增加索引后查询结果不正确案例浅析
【8月更文挑战第17天】在MySQL中,`BIT`类型字段在添加索引后可能出现查询结果异常。表现为查询结果与预期不符,如返回错误记录或遗漏部分数据。原因包括索引使用不当、数据存储及比较问题,以及索引创建时未充分考虑`BIT`特性。解决方法涉及正确运用索引、理解`BIT`的存储和比较机制,以及合理创建索引以覆盖各种查询条件。通过`EXPLAIN`分析执行计划可帮助诊断和优化查询。
|
3天前
|
SQL 存储 关系型数据库
mysql加索引真的会锁表吗?揭秘背后的技术细节与规避策略
【8月更文挑战第16天】在数据库管理中,添加索引能大幅提升查询效率。MySQL执行此操作时的锁定行为常引起关注。文章详细解析MySQL中索引添加时的锁定机制及其原理。不同存储引擎及SQL语句影响锁定策略:MyISAM需全表锁定;InnoDB提供更灵活选项,如使用`ALTER TABLE... LOCK=NONE`可在加索引时允许读写访问,尽管可能延长索引构建时间。自MySQL 5.6起,在线DDL技术可进一步减少锁定时间,通过`ALGORITHM=INPLACE`和`LOCK=NONE`实现近乎无锁的表结构变更。合理配置这些选项有助于最小化对业务的影响并保持数据库高效运行。
14 4
|
4天前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
6天前
|
缓存 NoSQL Redis
一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这篇文章是关于Java面试中可能会遇到的五个问题,包括MySQL索引类型及其对数据库性能的影响、Redis的RDB和AOF持久化机制、Redis的过期键删除策略、Redis的单线程模型为何高效,以及缓存雪崩、缓存穿透和缓存击穿的概念及其解决方案。
|
8天前
|
存储 关系型数据库 MySQL
MySQL 上亿大表,如何深度优化?
【8月更文挑战第11天】随着大数据时代的到来,MySQL 作为广泛使用的关系型数据库管理系统,经常需要处理上亿级别的数据。当数据量如此庞大时,如何确保数据库的查询效率、稳定性和可扩展性,成为了一个亟待解决的问题。本文将围绕 MySQL 上亿大表的深度优化,分享一系列实用的技术干货,帮助你在工作和学习中应对挑战。
25 1
|
3天前
|
存储 SQL 关系型数据库
探索MySQL的执行奥秘:从查询执行到数据存储与优化的深入解析
探索MySQL的执行奥秘:从查询执行到数据存储与优化的深入解析
|
10天前
|
运维 关系型数据库 MySQL
"MySQL运维精髓:深入解析数据库及表的高效创建、管理、优化与备份恢复策略"
【8月更文挑战第9天】MySQL是最流行的开源数据库之一,其运维对数据安全与性能至关重要。本文通过最佳实践介绍数据库及表的创建、管理与优化,包括示例代码。涵盖创建/删除数据库、表结构定义/调整、索引优化和查询分析,以及数据备份与恢复等关键操作,助您高效管理MySQL,确保数据完整性和系统稳定运行。
25 0
|
13天前
|
SQL 缓存 关系型数据库
MySQL配置简单优化与读写测试
MySQL配置简单优化与读写测试
|
6天前
|
存储 关系型数据库 MySQL
MySQL——数据库备份上传到阿里云OSS存储
MySQL——数据库备份上传到阿里云OSS存储
20 0
|
1天前
|
SQL 存储 关系型数据库
数据库-MySQL-01(一)
数据库-MySQL-01(一)
12 4

热门文章

最新文章