1024程序员节|【MySQL从入门到精通】【高级篇】(二十七)外连接和内连接如何进行查询优化呢?join的原理了解一波

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 【MySQL从入门到精通】【高级篇】(二十六)建了索引就能用么?我看未必。来看看几种索引失效的情况吧 上篇文章我们将来学习索引失效的几种情况。有时候并不是说加了索引,就一定能用上索引,还是要具体情况具体分析。本文将介绍一下MySQL优化器如何对外连接和内连接进行查询优化的以及介绍Join语句的底层原理。

1. 简介

上一篇文章我们介绍了

【MySQL从入门到精通】【高级篇】(二十六)建了索引就能用么?我看未必。来看看几种索引失效的情况吧 上篇文章我们将来学习索引失效的几种情况。有时候并不是说加了索引,就一定能用上索引,还是要具体情况具体分析。本文将介绍一下MySQL优化器如何对外连接和内连接进行查询优化的以及介绍Join语句的底层原理。

2. 数据准备&索引优化说明

下面准备了两张数据表,分别是分类表category和图书表book,然后分别在category表和book表中插入17条数据。

-- 分类表
CREATE TABLE IF NOT EXISTS category(
  id INT NOT NULL AUTO_INCREMENT,
  card INT NOT NULL,
  PRIMARY KEY(id)
) ENGINE=INNODB;
-- 图书表
CREATE TABLE IF NOT EXISTS book(
    bookid INT NOT NULL AUTO_INCREMENT,
  card INT NOT NULL,
  PRIMARY KEY(bookid)
) ENGINE=INNODB;
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO category(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));

在MySQL中的连接分为外连接和内连接。外连接又分为左连接和右连接。

内连接就是取两个表的交集数据。

左连接以左表为准,取左表的全部关联数据

右连接以右表为准,取右表的全部关联数据


2.1. 左连接说明

2.1.1. 没有索引的情况下

EXPLAIN SELECT SQL_NO_CACHE * FROM category LEFT JOIN book ON category.card=book.car

没有索引的情况下,直接关联两个数据表查看执行计划可以看出两个表的type都是ALL。

2.1.2. 给左表添加索引

在左连接中,左表作为驱动表,如果给左表添加索引的话,则左表可以使用到索引,这里需要注意的是左表和右表的关联字段的类型要一致呢!

ALTER TABLE category ADD INDEX idx_category_card(card);
EXPLAIN SELECT SQL_NO_CACHE * FROM category LEFT JOIN book ON category.card=book.card;

2.1.3. 给右表添加索引

给右表添加索引也是同理,添加完索引之后,右表作为被驱动表也是可以使用到该索引的,从而避免全表扫描。

ALTER TABLE book ADD INDEX idx_book_card(card);
EXPLAIN SELECT SQL_NO_CACHE * FROM category LEFT JOIN book
ON category.card=book.card;

从上图可以看出,在左连接中左表都是作为驱动表,右表作为被驱动表。

2.2. 内连接说明

EXPLAIN SELECT SQL_NO_CACHE * FROM category INNER JOIN book ON category.card=book.card;

对于内连接来说,查询优化器可以决定谁作为驱动表,谁作为被驱动表出现。虽然两个表都有索引,虽然category表在前面,但是最终查询优化器决定将book表作为驱动表。

下面我们试下删除category表中的索引,接着看下效果如何:

EXPLAIN SELECT SQL_NO_CACHE * FROM category INNER JOIN book ON category.card=book.card;

我们可以看出对于内连接来讲,如果表的连接条件中只有一个字段有索引,则有索引的字段所在的表会被作为被驱动表出现的。

3. JOIN的原理

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


驱动表和被驱动表

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

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

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


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

开销统计 SNLJ
外表扫描次数 1
内表扫描次数 A
读取记录数 A+B*A
JOIN比较次数 B*A
回表读取记录次数 0
当然mysql肯定不会那么粗暴的去进行表的连接,所以就出现了后面的两种对Nested-Loop Join优化算法。

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

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

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

开销统计 SNLJ INLJ
外表扫描次数 1 1
内表扫描次数 A 0
读取记录数 A+B*A A+B(match)
JOIN比较次数 B*A A*index(Height)
回表读取记录次数 0 B(match)(if possible)
如果被驱动表加索引,效率是非常高的,但如果索引不是主键索引,还得进行一次回表查询,所以,如果被驱动表的索引是主键索引,效率会更高。

3.3. 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中可以存放更多的列。

开销统计 SNLJ INLJ BNLJ
外表扫描次数 1 1 1
内表扫描次数 A 0 A*used_column_size/join_buffer_size+1
读取记录数 A+B*A A+B(match) A+B(A*used_column_size/join_buffer_size)
JOIN比较次数 B*A A*index(Height) B*A
回表读取记录次数 0 B(match)(if possible) 0

参数设置:

block_nested_loop

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

join_buffer_size

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

join_buffer_size的最大值在32位系统可以申请4G,而在64位操作系统中可以申请大于4G的Join Buffer空间(64位)

4.总结

整体效率比较:INLJ>BNLJ>SNLJ

永远用小结果集驱动大结果集(其本质是减少外层循环的数据数量)(这里的小结果集并不一定指数据量少的表,而是值通过筛选条件得到的 表行数*每行大小)

SELECT SQL_NO_CACHE * FROM category INNER JOIN book ON category.card=book.card WHERE book.bookid<100;   //推荐
SELECT SQL_NO_CACHE * FROM category INNER JOIN book ON category.card=book.card WHERE category.id<100;   //不推荐

为被驱动表匹配的条件增加索引(减少内层表的循环匹配次数)

增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫描次数就越少)

减少驱动表不必要的字段查询(字段越少,join buffer所缓存的数据就越多)


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
关系型数据库 MySQL 网络安全
DBeaver连接MySQL提示Access denied for user ‘‘@‘ip‘ (using password: YES)
“Access denied for user ''@'ip' (using password: YES)”错误通常与MySQL用户权限配置或网络设置有关。通过检查并正确配置用户名和密码、用户权限、MySQL配置文件及防火墙设置,可以有效解决此问题。希望本文能帮助您成功连接MySQL数据库。
16 4
|
2月前
|
存储 关系型数据库 MySQL
MySQL主从复制原理和使用
本文介绍了MySQL主从复制的基本概念、原理及其实现方法,详细讲解了一主两从的架构设计,以及三种常见的复制模式(全同步、异步、半同步)的特点与适用场景。此外,文章还提供了Spring Boot环境下配置主从复制的具体代码示例,包括数据源配置、上下文切换、路由实现及切面编程等内容,帮助读者理解如何在实际项目中实现数据库的读写分离。
MySQL主从复制原理和使用
|
20天前
|
安全 关系型数据库 MySQL
【赵渝强老师】MySQL的连接方式
本文介绍了MySQL数据库服务器启动后的三种连接方式:本地连接、远程连接和安全连接。详细步骤包括使用root用户登录、修改密码、创建新用户、授权及配置SSL等。并附有视频讲解,帮助读者更好地理解和操作。
|
2月前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
|
2月前
|
SQL 关系型数据库 MySQL
Mysql中搭建主从复制原理和配置
主从复制在数据库管理中广泛应用,主要优点包括提高性能、实现高可用性、数据备份及灾难恢复。通过读写分离、从服务器接管、实时备份和地理分布等机制,有效增强系统的稳定性和数据安全性。主从复制涉及I/O线程和SQL线程,前者负责日志传输,后者负责日志应用,确保数据同步。配置过程中需开启二进制日志、设置唯一服务器ID,并创建复制用户,通过CHANGE MASTER TO命令配置从服务器连接主服务器,实现数据同步。实验部分展示了如何在两台CentOS 7服务器上配置MySQL 5.7主从复制,包括关闭防火墙、配置静态IP、设置域名解析、配置主从服务器、启动复制及验证同步效果。
Mysql中搭建主从复制原理和配置
|
2月前
|
SQL JavaScript 关系型数据库
node博客小项目:接口开发、连接mysql数据库
【10月更文挑战第14天】node博客小项目:接口开发、连接mysql数据库
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
Java 关系型数据库 MySQL
【编程基础知识】Eclipse连接MySQL 8.0时的JDK版本和驱动问题全解析
本文详细解析了在使用Eclipse连接MySQL 8.0时常见的JDK版本不兼容、驱动类错误和时区设置问题,并提供了清晰的解决方案。通过正确配置JDK版本、选择合适的驱动类和设置时区,确保Java应用能够顺利连接MySQL 8.0。
195 1
|
2月前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
109 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库
|
2月前
|
SQL JavaScript 关系型数据库
Node.js 连接 MySQL
10月更文挑战第9天
20 0