算法|为什么不让用join?《死磕MySQL系列 十六》( 二 )


索引嵌套循环连接是基于索引进行连接的算法 , 索引是基于被驱动表的 , 通过驱动表查询条件直接与被驱动表索引进行匹配 , 防止跟被驱动表的每条记录进行比较 , 利用索引的查询减少了对被驱动表的匹配次数 , 从而提升join的性能 。
使用前提
使用索引嵌套查询的前提是驱动表与被驱动表关联字段上有设置索引 。
接下来使用一个案例来详细解析索引嵌套查询的具体执行流程 , 以下SQL是所有的表和数据 , 直接复制就可以用


CREATE TABLE `article` (`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT 'ID'`author_id` INT (11) NOT NULLPRIMARY KEY (`id`)) ENGINE=INNODB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci COMMENT='文章表';

CREATE PROCEDURE idata () BEGIN DECLARE i INT; SET i=1; WHILE (i<=1000) DO INSERT INTO article VALUES (ii); SET i=i+1; END WHILE; END;

call idata();

CREATE TABLE `article_comment` (`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT 'ID'`article_id` INT (11) NOT NULL COMMENT '文章ID'`user_id` INT (11) NOT NULL COMMENT '用户ID'PRIMARY KEY (`id`)INDEX `idx_article_id` (`article_id`)) ENGINE=INNODB CHARSET=utf8mb4 COLLATE utf8mb4_german2_ci COMMENT='用户评论表';

DROP PROCEDURE idata;

CREATE PROCEDURE idata () BEGIN DECLARE i INT;
SET i=1; WHILE (i<=1000)
DO
INSERT INTO article_comment VALUES (iii);
SET i=i+1; END WHILE; END;

CALL idata ();

可以看到 , 此时article表和article_comment , 数据都是1000行
需求是查看文章的所有评论信息 , 执行SQL如下
SELECT*FROM article STRAIGHT_JOIN article_comment ON article.id=article_comment.article_id;

现在 , 我们来看一下这条语句的explain结果 。

死磕MySQL系列
可以看到 , 在这条语句中 , 被驱动表article_comment的字段article_id使用了索引 , 因此这个语句的执行流程是这样的
  • 从article表读取一行数据R
  • 从R中去除id字段到表article_comment去查找
  • 取出article_comment中满足条件的行 , 跟R组成一行
  • 重复前三个步骤 , 直到表article满足条件的数据扫描结束
在这个流程中我们简单的梳理一下扫描行数
  • 对article表需要做全表扫描 , 扫描行数为1000
  • 没行R数据 , 根据article表的id去表article_comment查找 , 走的是树搜索 , 因此每次的搜索的结果都是一一对应的 , 也就是说每次只会扫描到一行数据 , 共需要扫描1000
  • 所以 , 这个执行流程 , 总扫描行数为2000行
若在代码中如何实现
  • 全表扫描article数据 , 这里是1000行
  • 循环这1000行数据
  • 使用article的id作为条件 , 在循环中进行查询
执行过程扫描行数也是2000行 , 先不涉及这样写性能如何 , 光与MySQL进交互就进行了1001次 。
结论
显然这么做还不如直接使用join好
三、Simple Nested-Loop Join简单嵌套循环连接查询是表连接使用不上索引 , 然后就粗暴的使用嵌套循环 , article、article_comment表都有1000行数据 , 那么扫描数据的行数就是1000*1000=1千万 , 这种查询效率可想而知是怎么样的 。