單表查詢優化
案例:有一張表爲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類型。因此可以得出以下的結論。
結論:連接查詢時,一般在從表中建立索引
建議:
- left join時,選擇小表作爲驅動表,大表作爲被驅動表
- 保證被驅動表的join字段已經被索引
- inner join時,mysql會自動將小結果集的表選爲驅動表
- 子查詢儘量不要放在被驅動表,有可能使用不到索引
小表驅動大表
優化原則:小表驅動大表,即用小的數據集驅動大的數據集。
原理分析:
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不然會產生索引失效的問題。