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不然會產生索引失效的問題。

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