做者本人从事it 行业多年,没进过什么大企业。摸爬滚打多年,多少有些经验。抗的了服务器,写的了前端。前端
熟悉的语言:PHP,JavaScript,Python 等。mysql
熟悉的框架:Laravel,Django,Scrapy,React,Vue 等。sql
数据库:Mysql,Redis,Es等。数据库
早些年其实就想写些东西,但又无处下手。直到这几年,带领团队,给组员培训,分享。才想到为何不把要培训的内容,分享的知识写成博文呢,而不是简单几页PPT。bash
除了这篇 "Mysql 百问系列" 后续还会有更多系列跟你们见面吧。文中知识点都是查阅资料,亲自实践得出来的结论,若是理解有偏误,请不吝赐教。服务器
每同样 技术 或者 功能 都是为了解决某一个问题而诞生的。 搞清楚 为何 要这么作 比 搞清楚 具体怎么作 要重要。 因此我都会以问题开头,但愿你们能先想一想本身是否知道这些问题的答案,而后对比我给出的答案,若是不一致思考下到底谁的答案有问题,动手证实。 这种经过攻克一个个问题从而提高本身的能力,就是我但愿经过一篇篇文章给你们带来的价值。app
每篇文章 我会聚焦于一两个点,尽可能不扩散太多。固然技术类文章没法避免一个知识点牵连另外一个知识点。这种碰到遇到不懂知识点的时候我建议你们知道有这么个东西,把精力集中于当前问题,而不要太扩散。固然后续我也会根据反馈,进行填坑。框架
CREATE TABLE `school` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`school_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_school` (`school_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
DECLARE
i INT;
SET i = 1;
WHILE
( i <= 1000 ) DO
INSERT INTO school
VALUES
( i, 'school' );
SET i = i + 1;
END WHILE;
END;;
delimiter;
CALL idata ( );
drop procedure idata;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
DECLARE
i INT;
SET i = 1;
WHILE
( i <= 1000 ) DO
INSERT INTO student
VALUES
( i, i%2+1, 'stu');
SET i = i + 1;
END WHILE;
END;;
delimiter;
CALL idata ( );复制代码
如今有两张表,一张 shool表,一张 student表。 各插入1000条数据oop
EXPLAIN select * from student STRAIGHT_JOIN school on school.id = student.school_id复制代码
(使用STRAIGHT_JOIN) 是防止Mysql 优化器自动优化,具体优化逻辑此处不展开,后续有机会详聊。优化
执行结果:
能够看到student 为 驱动表, school 为 被驱动表.
执行流程:
找到匹配的数据
,放入结果集中。 因为school 的id 为主键,因此使用主键索引可以迅速定位到那一行。上面使用的是join 也就是内链接
,若是换成Left join 或者 Right join 外链接有什么区别呢?区别就在于上面第二个步骤中,内链接须要找到匹配的数据放到结果集中, 而外链接是无论有没有找到数据,都须要把驱动表
的那行数据加入到结果集中。
那么无论内链接仍是外链接, 这种拿到驱动表
数据 再去循环遍历查询被驱动表,而且被驱动表查询过程当中使用到了索引
的方式称为 Index Nested-Loop Join
。(从英文其实也能够看出意思了, Index 索引,Nested-loop 嵌套循环)
那么若是 被驱动表上的搜索的字段没有索引呢?
EXPLAIN select * from student STRAIGHT_JOIN school on school.name = student.name复制代码
(虽然现实场景中不会存在 用学校名称 字段关联学生名称这种状况,这里只是说明若是 学校的name 这个没有索引的字段被拿来作关联条件时的状况)
能够看到 school 由于没法用到索引,因此必需要进行全表扫表。
那么咱们猜想的
执行流程是:
这个过程 咱们能够看到 须要扫描的行数为 1000 * 1000 。 若是school 或者student 数量再大必定,1万的学校,几百万的学生,那么这个查询的效率可想而知会很是低。
因此针对这种被驱动表
没法使用索引
的join,Mysql 作出了优化,并非咱们上面所猜想的流程。
首先,mysql 引入了join buffer 的概念。这个join buffer的大小是能够经过启动参数或者系统变量join_buffer_size进行配置,默认大小是256K,最小能够设置为128字节。
因此真实的流程为:
驱动表
student,取出1000行 放入join buffer。因此上面的情形是 school 中的数据,一行行的取出来 去与 join_buffer 中的数据作对比,查找到 符合条件的结果。那么 须要扫描的行数是 school 的1000 行, 每一行又要与join_buffer 中的1000行进行对比。
这种状况 须要扫描的行数为 student 的1000行,用来放入join_buffer , 和 school 的1000行。也就是扫描了2000行。 单内存中进行判断的次数是 1000 * 1000;
能够看到经过引入join buffer 将 磁盘的扫描 变成了内存的判断,以此来提高效率
若是驱动表
数据很大,致使join buffer 放不下呢? 例如 student 有 10000万行,join buffer 每次只能放1000行。
那么流程就会变为:
1.扫描 student ,直到join buffer 放满。
2.扫描 school ,取出其中一行, 与 join_buffer 中的数据作对比, 若是知足了join 的条件,那么加入到结果集中。
这种按 一块块 进行读取放入join buffer 并比较的方式 就是Block Nested-Loop Join
了。
能够看到若是 join 的时候使用了Block Nested-Loop Join
,那么它的执行效率与join buffer 的大小相关,join buffer 越大,分块的次数越小, 效率越高。
固然充分利用join buffer 空间也是一种方式, 放入join buffer 空间的数据是 驱动表的 查询字段 和 搜索字段。因此避免使用 *
,列清楚须要查询的字段也有利于 join buffer 空间的利用。
固然最理想的优化方式是添加索引,把 Block Nested-Loop Join
转为 Index Nested-Loop Join
。
索引也不是随便添加,若是业务场景中不多拿这个字段来进行搜索,单单为了某个join 而添加索引也得不偿失,还须要根据具体业务来考量。
根据join 过程当中 被驱动表
能不能用到索引 将查询方式分为 Index Nested-Loop Join
和 Block Nested-Loop Join
Index Nested-Loop Join
的效率高,能够在业务中使用。Block Nested-Loop Join
虽然mysql 引入join buffer 来进行优化,可是效率仍是偏低,应该尽可能避免。ps: 我的认为在实际业务场景中即便多个表格进行join 过程当中都使用到了Index Nested-Loop Join
的方式,可是因为每次join 都是进行笛卡尔积的过程,若是其中一张或多张表随着业务发展数据量增长,会致使 整个sql 查询效率也不是很理想。 因此仍是尽可能避免多表join 的场景。最多容许2 到 3个表格进行join 。
喜欢,支持请关注个人我的公众号