MySQL高级(五)、查询优化

单表查询优化

案例:有一张表为article表(文章表),表中的字段有文章id、category_id、评论数comments以及被看次数views,建表语句如下所示。现有需求如下,查询 category_id 为1 且 comments 大于 1 的情况下,views 最多的 article_id与author_id。

CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL,
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL
);

第一次写的查询语句如下所示:

SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;

通过explain分析该sql语句,结果如下图
在这里插入图片描述分析:该语句有什么缺点呢?很显然,查询的类型为全表查询,并且使用了文件排序。显然性能极差。
基于索引的思想,我们在做如下的改进。
改进一:使用索引,在要进行查询的字段上面创建索引

create index idx_article_ccv on article(category_id, comments, views);

创建好索引后,再次用explain进行分析,结果如下图
在这里插入图片描述
分析:可以看到我们这里不再是全表扫描,而是范围,并且用到了刚创建的索引,但有一个缺点:仍然使用文件内排序,造成文件的开销。原因是范围之后索引失效,我们这里只用到了category_id和comments索引,因此做如下的改进:
改进二:删除之前建的索引,重新创建索引

drop index idx_article_ccv on article;
create index idx_article_cv on article(category_id, views);

在这里插入图片描述
此时,我们看到类型为ref,并且在Extra中没有了文件排序。所以此时的查询性能是满足条件的。

关联查询优化

案例:有两张表,一张book表,一张class表,book表为书籍表,class表为类别表,两表的关联字段为card,现需要查询两表对应的信息。两表的结构如下,并且分别有20条记录:

CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);

简单的查询语句为:

SELECT * FROM class LEFT JOIN book ON class.card = book.card;

对该语句用执行计划分析:
在这里插入图片描述
可见,两表的执行行数都是20,并且类型都是ALL,而且用到了连接缓存,性能不佳。
改进:因为查询使用的是左连接,左表的记录都要使用,我们在右表上创建索引

create index idx_book_card on book(card);

然后用执行计划分析:
在这里插入图片描述
可以看到其中的一个类型已经由ALL变为ref,并且查询的行数为1,总体而言性能有了提高。
对比:若我们删除刚创建的索引,并在class表上创建索引,然后进行分析:

drop index idx_book_card on book;
create index idx_class_card on class(card);

在这里插入图片描述
可见其中的一个ALL变为了index,但是rows都是20行,并且使用到了连接缓存。而且,我们知道index类型劣于ref类型。因此可以得出以下的结论。
结论:连接查询时,一般在从表中建立索引

建议

  1. left join时,选择小表作为驱动表,大表作为被驱动表
  2. 保证被驱动表的join字段已经被索引
  3. inner join时,mysql会自动将小结果集的表选为驱动表
  4. 子查询尽量不要放在被驱动表,有可能使用不到索引

小表驱动大表

优化原则:小表驱动大表,即用小的数据集驱动大的数据集。
原理分析:

select * from A where id in (select id from B);
等价于:
for select id from B
for select * from A where A.id = B.id

即:当B表的数据集必须小于A表的数据集时,用in优于exists

select * from A where exists (select 1 from B where B.id = A.id)
等价于:
for select * from A
for select * from B where B.id = A.id

即:当A表的数据集小于B表的数据集时,用exists优于in。
注意:A表与B表的ID字段应建立索引。

OrderBy排序优化

因为我们在排序时,在没有创建索引的情况下会出现文件内排序(using filesort)这对mysql的性能是极为不利的,因此需要创建索引并进行排序优化。
案例
我们有一张员工表,其中有两个字段age和birth,我们在这两个字段上创建了一个复合索引,现需要判断通过我们指定的排序方式,是否会产生文件排序。

CREATE TABLE tblA(
  id int primary key not null auto_increment,
  age INT,
  birth TIMESTAMP NOT NULL,
  name varchar(200)
);

#创建复合索引
CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);

注意:在我们给出的案例中,我们关注的点是order by 后面的字段,以及是否产生了using filesort。
在这里插入图片描述
在这里插入图片描述
由上面的案例我们可以得到:
MySQL支持二种方式的排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序。FileSort方式效率较低。
使用Index方式排序的情况:

  • ORDER BY 语句使用索引最左前列
  • 使用Where子句与Order BY子句条件列组合满足索引最左前列
  • where子句中如果出现索引的范围查询(即explain中出现range)会导致order by 索引失效。

filesort排序方式:
filesort有双路排序和单路排序

  • 双路排序:从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
  • 单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
  • MySQL4.1之前使用的是双路排序,单路排序是后面出现的,优于双路排序,但也会存在一定的问题:单路排序把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再排……从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
  • 优化策略:增大sort_buffer_size参数的设置(单路排序内存大小);增大max_length_for_sort_data参数的设置(单次排序字段大小);去掉select 后面不需要的字段

去重优化

尽量不要使用 distinct 关键字去重,可以使用group by 实现去重,可以用上索引。
在这里插入图片描述
在这里插入图片描述
举得这个例子是因为创建的索引用的是复合索引,所以加上了age,birth不然会产生索引失效的问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章