MySQL 性能优化 - 多表查询优化

上一篇讲到了单表数据查询的性能优化,已经掌握了简单的查询优化,这一篇主要针对两表、三表以及多表联查的情况进行分析,看看该如何进行调优。

老样子,先准备两张测试表,脚本如下:

CREATE TABLE IF NOT EXISTS `test_class`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);
CREATE TABLE IF NOT EXISTS `test_book`(
`bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_class(card)VALUES(FLOOR(1+(RAND()*20)));
 
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_book(card)VALUES(FLOOR(1+(RAND()*20)));
  • LEFT JOIN

先进行简单的连表查询:

SELECT * FROM test_class a LEFT JOIN test_book b on a.card = b.card;

使用 Explain 进行查询分析,结果如下图:
在这里插入图片描述分析结果,可以看出,两张表的执行 type 均为 ALL,需要优化,我们来进行索引的创建优化。

因为是 left join 两张表联合查询,那么这个时候,我们需要思考一个问题,索引要怎么建?建到哪一张表上面? 因为我们还不清楚,况且,目前就两张表,不是左表就是右表,我们先再右表上创建一个索引来进行分析,看一下效果如何,如下:

CREATE INDEX idx_book_card on test_book(card);

再次执行 Explain 进行分析,结果如下:
在这里插入图片描述可以看到,我们在右表 test_book 加的索引已经被使用了,且 type 为 ref,Extra 为 Using index,rows的数量为 1,效果还是很不错的。

那么我们,将右表的索引删掉,重新创建索引在左表上面,试试效果。

drop index idx_book_card on test_book;
create index idx_class_card on test_class(card);

我们再次执行 Explain 进行分析结果查看,结果如下:
在这里插入图片描述通过分析,我们看到,加到左表的索引,也被实际使用了,type 为 index,很明显,没有加到右表的 ref 效果好,另外,通过 rows 中的数量来看,明显比 上一次的结果要大,所以说,加到左表的效果,不如加到右表的效果理想。

那么,这到底是为什么呢?这是由于左连接的特性决定的。left join 是以左表为主表,需要查询出左表里面的所有数据,而右表只是当做联合条件进行匹配,并非全表数据都需要,因此,右表的数据量相对较小,这也满足了小表驱动大表的规范。所以,右边是我们的关键点,一定要加索引。

  • RIGHT JOIN

分析完了 left join ,我们来分析 right join,此时你会发现,他们的结果有共同之处,这里就不进行过多的分析,基本分析流程和上面的一样。

  • 多表查询分析
    上面分析了两张表联查的优化,在工作中,我们也是经常使用的两种 join方式,下面我们再来加入第三张表,进行三张表的联合查询优化。

准备第三张表及相关数据:

CREATE TABLE IF NOT EXISTS `test_phone`(
`phoneid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
)ENGINE = INNODB;

INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO test_phone(card)VALUES(FLOOR(1+(RAND()*20)));

先来写一个简单的三表联查语句,如下:

EXPLAIN SELECT * FROM test_class a 
	LEFT JOIN test_book b on a.card = b.card
	LEFT JOIN test_phone p on p.card = a.card;

通过上面的 SQL 分析,结果如下:
在这里插入图片描述因为我们新建的 test_phone 表中,并没有任何索引,所以现在type 是 ALL,进行了全表扫描。像这种三张表连接查询的,可以先把前两张的联合进行优化,优化后,当做一张新表,再与下一张表进行联合处理,还是通过两两联合查询进行分析,逐步进行处理。

下面,我们针对新建的 test_phone 表进行索引的创建:

create index idx_phone_card on test_phone(card);

再来执行上面的Explain 分析语句,结果如下:
在这里插入图片描述可以看到,已经非常的完美了。

最终结论
JOIN 语句的优化:

LEFT JOIN 时,索引要建到右表;
RIGHT JOIN时,索引要建到左表;
如果都存在索引,但是分析时,索引性能不佳,可以考虑根据已有的索引,来重新调整两张表的位置,比如 a left join b,换成 b right join a,这个需要针对具体的业务场景来进行分析了。
尽可能减少Join 语句中的NestedLoop的循环总次数,永远用小结果集驱动大的结果集。
优先优化NestedLoop的内层循环;
保证Join语句中被驱动表上的Join 条件字段已经被索引;
当无法保证被驱动表的Join条件字段被索引且内存资源充足的情况下,不要太吝啬JoinBuffer 的设置;

你可能感兴趣的:(MySQL,mysql,数据库,memcached)