一、單表
1、初步查詢
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
);
insert into article(author_id, category_id, views, comments, title, content)
VALUES (1, 1, 1, 1, '1', '1'), (2, 2, 2, 2, '2', '2'), (1, 1, 3, 3, '3', '3');
執行查詢
select * from article;
結果如下
現有以下要求
查詢category_id爲1且comments大於1的情況下,views最多的article_id。
查詢語句如下
select id, author_id from article where category_id = 1 and comments > 1 order by views DESC limit 1;
結果如下
sql語句加上explain,執行如下
explain select id, author_id from article where category_id = 1 and comments > 1 order by views DESC limit 1;
很顯然,type是all,即最壞的情況。Extra裏還出現了Using filesort,也是最壞的情況。優化是必須的。
2、優化查詢
創建索引
create index idx_article_ccv on article(category_id, comments, views);
查看索引
執行之前的explain
explain select id, author_id from article where category_id = 1 and comments > 1 order by views DESC limit 1;
結果如下
可見type變成了range,這個是可以忍受的。但是extra裏使用Using filesort仍是無法接受的。
但是我們修改查詢語句,把comments > 1改爲comments = 1
explain select id, author_id from article where category_id = 1 and comments = 1 order by views DESC limit 1;
結果如下
會發現,range進一步優化成爲了ref,Extra中的Using filesort也不見了。不過這條sql就違背了需求。(不過也可以得出結論,一般sql以等於爲主,可以達到更好的優化。)
根據BTree索引的工作原理,先排序category_id,如果遇到相同的category_id則再排序comments,如果遇到相同的comments再排序views。
當comments字段在聯合索引裏處於中間位置時,因comments > 1條件是一個範圍值(所謂range),MySQL無法利用索引再對後面的views部分進行檢索,即range類型查詢字段後面的索引無效,這也是爲什麼還有Using filesort的原因。
3、進一步優化查詢
刪除之前的索引,創建新的索引
drop index idx_article_ccv on article;
create index idx_article_cv on article(category_id, views);
show index from article;
查看新創建索引
執行
explain select id, author_id from article where category_id = 1 and comments > 1 order by views DESC limit 1;
結果如下
可以看到,type變味了ref,Extra中的Using filesort也消失了,結果非常理想。
二、雙表
創建數據表
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 (
book_id int(10) unsigned not null auto_increment,
card int(10) unsigned not null,
primary key (book_id)
);
# 執行20次
insert into class(card) values (floor(1 + (rand() * 20)));
# 執行20次
insert into class(book) values (floor(1 + (rand() * 20)));
1、左連接(left join)
1)初步查詢
執行,class相當於book的左表。
explain select * from class left join book on class.card = book.card;
結果如下
看到了type中的all就需要優化了。
1)優化查詢
刪除book表索引
drop index Y on book;
給class的card添加索引(給左表添加索引)
alter table class add index Y (card);
執行
explain select * from class left join book on class.card = book.card;
結果如下
可見第一行type變成了index。
2)進一步優化查詢
給book的card添加索引(給右表添加索引)
alter table book add index Y (card);
執行
explain select * from class left join book on class.card = book.card;
結果如下
可以看見第二行的type變爲了ref,rows、Extra也被優化了,整體優化比較明顯。
這個是由左連接特性決定的(左表全都有),left join條件用於確定如何從右表搜索行,左表一定是都有的。所以右表是關鍵點,一定要建立索引。
3)細節
對於給class的card添加索引優化不如給book的card添加索引優化效果明顯。如何不更改索引,就能達到右表優化呢?
其實查詢sql語句中兩表位置互換下即可。
explain select * from book left join class on class.card = book.card;
結果如下
同樣可以達到效果。
2、右連接(right join)
思路和左連接一樣
right join條件用於確定如何從左表查詢,右表一定都有,所以左表是我們的關鍵點,一定要建立索引。
三、三表
新增新表
create table if not exists phone (
phone_id int(10) unsigned not null auto_increment,
card int(10) unsigned not null,
primary key (phone_id)
);
# 執行20次
insert into phone(card) values (floor(1 + (rand() * 20)));
執行
explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
結果如下
給phone和book創建索引
alter table phone add index Z (card);
alter table book add index Y (card);
執行
explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
結果如下
後兩行的type都是ref且總rows優化很好,效果不錯。同樣的道理,這裏的phone相當於book的右表,而book相當於class的右表。
四、結論
Join語句優化
1)儘可能減少Join語句中的嵌套循環總次數,永遠用小結果集驅動大的結果集(例如書的類別是小結果集,書的名稱是大結果集)。
2)優先優化嵌套循環的內層循環。
3)保證Join語句中被驅動表上Join條件字段已經被索引。
4)當無法保證被驅動表的Join條件字段被索引且內存資源充足的前提下,不要太吝嗇JoinBuffer的設置。