order by、group by 效率分析


前提:數據准備

drop table if exists t1; /* 如果表t1存在則刪除表t1 */

CREATE TABLE `t1` ( /* 創建表t1 */
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(20) DEFAULT NULL,
`b` int(20) DEFAULT NULL,
`c` int(20) DEFAULT NULL,
`d` datetime NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_a_b` (`a`,`b`),
KEY `idx_c` (`c`)
) ENGINE=InnoDB CHARSET=utf8mb4 ;

drop procedure if exists insert_t1; /* 如果存在存儲過程insert_t1,則刪除 */
delimiter ;;
create procedure insert_t1() /* 創建存儲過程insert_t1 */
begin
declare i int; /* 聲明變量i */
set i=1; /* 設置i的初始值為1 */
while(i<=10000)do /* 對滿足i<=10000的值進行while循環 */
insert into t1(a,b,c) values(i,i,i); /* 寫入表t1中a、b兩個字段,值都為i當前的值 */
set i=i+1; /* 將i加1 */
end while;
end;;
delimiter ;
call insert_t1(); /* 運行存儲過程insert_t1 */

update t1 set a=1000 where id >9000; /* 將id大於9000的行的a字段更新為1000 */

1.1mysql排序方式:

按照排序原理分,MySQL 排序方式分兩種:

  • 通過有序索引直接返回有序數據
  • 通過 Filesort 進行的排序

怎么確定某條排序的 SQL 所使用的排序方式?

explain select id,c from t1 order by c;

如果該字段里顯示是 Using index,則表示是通過有序索引直接返回有序數據

explain select id,d from t1 order by d;

如果該字段里顯示是 Using filesort,則表示該 SQL 是通過 Filesort 進行的排序

 

1.2 Filesort 是在內存中還是在磁盤中完成排序的?

MySQL 中的 Filesort 並不一定是在磁盤文件中進行排序的,也有可能在內存中排序,內存排序還是磁盤排序取決於排序的數據大小和 sort_buffer_size 配置的大小。

  • 如果 “排序的數據大小” < sort_buffer_size: 內存排序
  • 如果 “排序的數據大小” > sort_buffer_size: 磁盤排序

1.3 Filesort 下的排序模式

  • < sort_key, rowid >雙路排序(又叫回表排序模式):是首先根據相應的條件取出相應的排序字段和可以直接定位行數據的行 ID,然后在 sort buffer 中進行排序,排序完后需要再次取回其它需要的字段;
  • < sort_key, additional_fields >單路排序:是一次性取出滿足條件行的所有字段,然后在sort buffer中進行排序;
  • < sort_key, packed_additional_fields >打包數據排序模式:與單路排序相似,區別是將 char 和 varchar 字段存到 sort buffer 中時,更加緊縮。

MySQL 通過比較系統變量 max_length_for_sort_data 的大小和需要查詢的字段總大小來判斷使用哪種排序模式。

  • 如果 max_length_for_sort_data 比查詢字段的總長度大,那么使用 < sort_key, additional_fields >排序模式;
  • 如果 max_length_for_sort_data 比查詢字段的總長度小,那么使用 <sort_key, rowid> 排序模式。

2 order by 優化

2.1排序字段添加索引:

首先我們看下對 d 字段(沒有索引)進行排序的執行計划:

再看些對 c 字段(有索引)進行排序的執行計划:

 explain select c,id from t1 order by c;

 

 可以看到,根據有索引的字段排序,在 Extra 中顯示的就為 Using index,表示使用的是索引排序。如果數據量比較大,顯然通過有序索引直接返回有序數據效率更高

2.2多個字段排序優化

對 a、c 兩個字段進行排序的執行計划:

explain select id,a,c from t1 order by a,c;

 

 再看對 a、b(a、b 兩個字段有聯合索引)兩個字段進行排序:

發現使用的是索引排序。

多個字段排序的情況,如果要通過添加索引優化,得注意排序字段的順序聯合索引中列的順序要一致。

因此,如果多個字段排序,可以在多個排序字段上添加聯合索引來優化排序語句

 2.3 先等值查詢再排序的優化

我們更多的情況是會先根據某個字段條件查出一部分數據,然后再排序,而這類 SQL 應該如果優化呢?

explain select id,a,d from t1 where a=1000 order by d;

 

explain select id,a,b from t1 where a=1000 order by b; 

 

 因此,對於先等值查詢再排序的語句,可以通過在條件字段和排序字段添加聯合索引來優化此類排序語句。

2.4 去掉不必要的返回字段

有時,我們其實並不需要查詢出所有字段,但是可能因為習慣問題,就寫成查所有字段的數據了。我們看下下面兩條 SQL 的執行計划:

select * from t1 order by a,b;  

explain select id,a,b from t1 order by a,b; 

 

根據執行計划的結果,可以看到,查詢所有字段的這條 SQL 是 filesort 排序,而只查 id、a、b 三個字段的 SQL 是 index 排序,為什么查詢所有字段會不走索引?

這個例子中,查詢所有字段不走索引的原因是:掃描整個索引並查找到沒索引的行的成本比掃描全表的成本更高,所以優化器放棄使用索引。

2.5修改參數

  • max_length_for_sort_data:如果覺得排序效率比較低,可以適當加大 max_length_for_sort_data 的值,讓優化器優先選擇全字段排序。當然不能設置過大,可能會導致 CPU 利用率過低或者磁盤 I/O 過高;
  • sort_buffer_size:適當加大 sort_buffer_size 的值,盡可能讓排序在內存中完成。但不能設置過大,可能導致數據庫服務器 SWAP。

3.幾種無法利用索引排序的情況

3.1ASC 和 DESC 混合使用將無法使用索引

explain select id,a,b from t1 order by a asc,b desc;

3.2 查詢范圍索引問題

在前面講過:對於先等值過濾再排序的語句,可以通過在條件字段和排序字段添加聯合索引來優化;但是如果聯合索引中前面的字段使用了范圍查詢,對后面的字段排序是否能用到索引排序呢?

explain select id,a,b from t1 where a>9000 order by b;

這里對上面執行計划做下解釋:首先條件 a>9000 使用了索引(關注 key 字段對應的值為 idx_a_b);在 Extra 中,看到了“Using filesort”,表示使用了 filesort 排序,並沒有使用索引排序。所以聯合索引中前面的字段使用了范圍查詢,對后面的字段排序使用不了索引排序。

3.3 最后說到 group by 語句的優化,如果只要分組,沒有排序需求的話,可以加 order by null 禁止排序。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM