一、單表索引優化
- 建表:
建一張表,SQL如下:
create table `tb_article`(
`id` int not null primary key auto_increment comment '主鍵',
`author_id` int not null comment '作者id',
`category_id` int not null comment '文章類別id',
`views` int not null comment '閱讀量',
`comments` int not null comment '評論量',
`title` varchar(200) not null comment '文章標題',
`content` text not null comment '文章內容'
) comment '文章表';
insert into tb_article(author_id,category_id, views, comments, title, content) values(1,1,1,1,1,1);
insert into tb_article(author_id,category_id, views, comments, title, content) values(2,2,2,2,2,2);
insert into tb_article(author_id,category_id, views, comments, title, content) values(1,1,3,3,3,3,);
- 需求:
查詢出 類別id爲1 且 評論量大於1的情況下,閱讀量最多的那篇文章的作者id。
SQL寫法如下:
select id, author_id
from tb_article
where category_id = 1 and comments > 1
order by views desc
limit 1;
用explain分析一下,發現這條SQL問題很大:
首先沒有使用索引,type是all,然後用了文件內排序,using filesort。這兩個都是嚴重影響性能的,那麼接下來就建索引。
之前說過,where後面的字段,以及order by後面的字段,最好都要用索引,所以建立如下索引:create index idx_ccv on tb_article(category_id, comments, views);
即用這三個字段建立了一個複合索引。接下來再看上面那條查詢SQL的執行計劃。
見鬼了,怎麼還有using filesort呢?我排序字段不是建了索引了嗎?假如,把comment 大於1改成等於1,看看什麼情況:
發現using filesort神奇地消失了。這是因爲,comment大於1是一個範圍,而comment等於1是常量,範圍後面的索引是會失效的,即使用comment大於1的時候,order by後面根本沒用到索引,因爲失效了。
那說明我們建的這個索引不太合適,幹掉它:drop index idx_ccv on tb_article;
既然comment大於1會導致後面的索引失效,那如果繞開它,只對category_id和views建索引會怎樣呢?即create index idx_cv on tb_article(category_id, views);
可以看到,用到了索引,也沒有文件內排序了。
- 結論:如果範圍查詢的字段跟其他字段一起建立了複合索引,那麼範圍查詢字段後面字段的索引會失效。解決辦法可以繞過該字段。
二、兩表索引優化
上面是單表,這裏來看看連接查詢的情況。
- 建表:
create table `tb_novel`(
`id` int not null primary key auto_increment comment '主鍵',
`title` varchar(100) not null comment '小說名'
) comment '小說';
create table `tb_character`(
`id` int not null primary key auto_increment comment '主鍵',
`name` varchar(100) not null comment '人物名',
`novel_id` int not null comment '歸屬於的小說id'
) comment '人物';
create table `tb_kongfu`(
`id` int not null primary key auto_increment comment '主鍵',
`kongfu_name` varchar(100) not null comment '功夫的名字',
`novel_id` int not null comment '小說的id'
)comment '功夫';
insert into tb_novel(title)values('天龍八部');
insert into tb_novel(title)values('射鵰英雄傳');
insert into tb_novel(title)values('神鵰俠侶');
insert into tb_novel(title)values('倚天屠龍記');
insert into tb_character(name, novel_id) values('喬峯',1);
insert into tb_character(name, novel_id) values('掃地僧',1);
insert into tb_character(name, novel_id) values('洪七公',2);
insert into tb_character(name, novel_id) values('郭靖',2);
insert into tb_character(name, novel_id) values('金輪法王',3);
insert into tb_character(name, novel_id) values('小龍女',3);
insert into tb_character(name, novel_id) values('趙敏',4);
insert into tb_character(name, novel_id) values('滅絕老尼',4);
insert into tb_kongfu(kongfu_name, novel_id) values('北冥神功', 1);
insert into tb_kongfu(kongfu_name, novel_id) values('六脈神劍', 1);
insert into tb_kongfu(kongfu_name, novel_id) values('落英神劍掌', 2);
insert into tb_kongfu(kongfu_name, novel_id) values('北斗七星陣', 2);
insert into tb_kongfu(kongfu_name, novel_id) values('黯然銷魂掌', 3);
insert into tb_kongfu(kongfu_name, novel_id) values('龍翔般若功', 3);
insert into tb_kongfu(kongfu_name, novel_id) values('乾坤大挪移', 4);
insert into tb_kongfu(kongfu_name, novel_id) values('九陰白骨爪', 4);
- 需求:
查詢出屬於同一部小說的人物名和功夫的名字。
SQL寫法如下:
select c.name,f.kongfu_name from tb_character c left join tb_kongfu f on c.novel_id = f.novel_id;
差不多就這個意思,反正就是兩表連接,但是不用主鍵去關聯。
來看一下這條sql的執行計劃:
可以看到,type都是all,因爲我們並沒有建索引。我們是用novel_id連接的,那麼,我是在tb_character表的novel_id上建索引還是在tb_kongfu表的novel_id上建索引呢?
首先在tb_character表的novel_id上建索引,create index idx_novel_id on tb_character(novel_id);
,然後再查看執行計劃。
可以看到,都是all,並沒有走索引。
那麼我們把tb_character的索引刪除,drop index idx_novel_id on tb_character;
,加在右表tb_kongfu上試試,create index idx_novel_id on tb_kongfu(novel_id);
,再看執行計劃:
可以看到,這次走索引了,首先左表是驅動表,左連接就是左邊都要查出來,所以左邊還是all,但是右邊是ref了。
我們不妨把tb_kongfu表的索引刪除,再把tb_character表的索引加上去,然後將left join改成right join,再看執行計劃:
可以看到,也走了索引。
- 結論:左連接的時候索引應該加在右表,右連接應該加在左表。
三、三表索引優化
三表和兩表其實沒什麼差別,比如:
select * from A left join B on A.key = B.key left join C on A.key = C.key;
這裏都是left join,且有三表,那麼首先應該在B表的key上加索引,A和B連接的結果看成是一個臨時表,再和C連接,因此C表的key也應該加上索引。
四、exists和in
連接查詢的時候,永遠要用小表驅動大表。比如下面的語句:
select * from A where id in (select id from B)
這條語句是先執行select id from B
的,然後用它去驅動A表的,當B表的數據少於A表時,用in的效率是更高的。
再看這條語句:
select * from A where exists (select 1 from B where B.id = A.id)
這條語句呢是先執行select * from A
,然後用查出來的結果集去驅動B表的,當A表的數據更少時,這樣寫的效率是比用in更高的。
五、優化結論
連接查詢,永遠要用小表驅動大表,即用數據少的表作爲驅動表。比如A表數據很少,B表很多,要左連接的話,那麼應該是 A left join B。
優先優化嵌套循環(nested loop)的內層循環。
保證join語句中被驅動表上的join條件字段加了索引。
無法保證被驅動表的join條件字段加索引且內存充足的情況下,可以加大joinBuffer的設置。