mysql使用索引掃描來做排序


  mysql有兩種方式可以生成有序的結果,通過排序操作或者按照索引順序掃描,如果explain的type列的值為index,則說明mysql使用了索引掃描來做排序(不要和extra列的Using index搞混了,那個是使用了覆蓋索引查詢)。掃描索引本身是很快的,因為只需要從一條索引記錄移動到緊接着的下一條記錄,但如果索引不能覆蓋查詢所需的全部列,那就不得不掃描一條索引記錄就回表查詢一次對應的整行,這基本上都是隨機IO,因此按索引順序讀取數據的速度通常要比順序地全表掃描慢,尤其是在IO密集型的工作負載時。

  mysql可以使用同一個索引既滿足排序,又用於查找行,因此,如果可能,設計索引時應該盡可能地同時滿足這兩種任務,這樣是最好的。只有當索引的列順序和order by子句的順序完全一致,並且所有列的排序方向(倒序或升序,創建索引時可以指定ASCDESC)都一樣時,mysql才能使用索引來對結果做排序,如果查詢需要關聯多張表,則只有當order by子句引用的字段全部為第一個表時,才能使用索引做排序,order by子句和查找型查詢的限制是一樣的,需要滿足索引的最左前綴的要求,否則mysql都需要執行排序操作,而無法使用索引排序。

 

示例:

有表rental,表結構如下:

mysql > CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`rental_id`),
  UNIQUE KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`),
  KEY `idx_fk_inventory_id` (`inventory_id`),
  KEY `idx_fk_customer_id` (`customer_id`),
  KEY `idx_fk_staff_id` (`staff_id`),
  CONSTRAINT `fk_rental_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_inventory` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`inventory_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8;
 
表數據量如下:
mysql > select count(*) from rental;
+----------+
| count(*) |
+----------+
|    16044 |
+----------+
1 row in set (0.04 sec)
 
索引信息如下: 
mysql > show index from rental;
 

 

mysql可以使用rental_date索引為下面的SQL查詢做排序,從explain中可以看到沒有出現文件排序操作:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date = '2005-05-25' order by inventory_id,customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.01 sec)

 

注意:where條件列可以不按照索引定義的順序出現,不管按照什么順序出現索引列,只要出現的索引列在索引定義順序的列上能連起來就行,但是order by列不同,出現順序一定得按照索引定義的順序,否則無法使用索引進行排序,如,把inventory_idcostomer_id交換一下,就會出現filesort,因為索引是按照定義時的順序排序,order by列打亂這個排序順序就無法使用索引進行排序了

mysql > explain select rental_id,staff_id from sakila.rental where rental_date = '2005-05-25' order by customer_id,inventory_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

從上面示例中可以看到,order by子句不滿足索引的最左前綴要求(rental_date列沒有出現,只出現了索引的后邊兩列),不能用rental_date來排序,但可以用於rental_date列查詢,這是因為索引的第一個列被指定為常數。

 

還有一些可以使用索引排序的查詢示例:

下面這個索引第一列在where上,第二列在order by

mysql > explain select rental_id,staff_id from sakila.rental where rental_date='2005-05-05' order by inventory_id desc\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.00 sec)

 

下面這個查詢因為order by使用的兩列就是索引的最左前綴

mysql > explain select rental_id,staff_id from sakila.rental where rental_date='2005-05-05' order by rental_date,inventory_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where

1 row in set (0.00 sec)

 

下面是一些不能使用索引做排序的查詢:

下面這個查詢使用了兩種不同的排序方向,但是索引列最左前綴是符合的

mysql > explain select rental_id,staff_id from sakila.rental where rental_date='2005-05--25' rder by inventory_id desc,customer_id asc\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面這個查詢的order by子句中使用了一個不在索引中的列

mysql > explain select rental_id,staff_id from sakila.rental where rental_date='2005-05--25' order by inventory_id,staff_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面這個查詢的whereorder by中的列無法組合成索引的最左前綴,中間缺失了inventory_id列:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date='2005-05-25' order by customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

下面這個查詢在索引列的第一列上是范圍查詢,所以mysql無法使用索引的其余列,這個范圍條件后邊的無論是查詢條件列還是排序列都無法使用到索引:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date >'2005-05-25' order by inventory_id,customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ALL

possible_keys: rental_date

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 16005

        Extra: Using where; Using filesort

1 row in set (0.00 sec)

 

面這個查詢在inventory_id列上有多個等於條件,對於排序來說,這也是一種范圍查詢,inventory_id條件列后面的無論是查詢還是排序都無法使用索引:

mysql > explain select rental_id,staff_id from sakila.rental where rental_date=2005-05-25 and inventory_id in (1,2) order by customer_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: rental

         type: ref

possible_keys: rental_date,idx_fk_inventory_id

          key: rental_date

      key_len: 5

          ref: const

         rows: 1

        Extra: Using index condition; Using where; Using filesort

1 row in set, 5 warnings (0.00 sec)

 

下面這個查詢理論上是可以使用索引進行關聯排序的,但是由於優化器在優化時將film_actor表當作關聯的第二個表,所以實際上無法使用索引,即排序列不是驅動表的列就無法使用索引排序:

mysql > explain select actor_id,title from sakila.film_actor join sakila.film using(film_id) order by actor_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: film

         type: index

possible_keys: PRIMARY

          key: idx_title

      key_len: 767

          ref: NULL

         rows: 1000

        Extra: Using index; Using temporary; Using filesort

*************************** 2. row ***************************

           id: 1

  select_type: SIMPLE

        table: film_actor

         type: ref

possible_keys: idx_fk_film_id

          key: idx_fk_film_id

      key_len: 2

          ref: sakila.film.film_id

         rows: 2

        Extra: Using index

2 rows in set (0.00 sec)

 

可以使用straight_join語句指定關聯表的順序:

mysql > explain select actor_id,title from sakila.film_actor as a straight_join sakila.film as b on a.film_id=b.film_id order by actor_id\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: a

         type: index

possible_keys: idx_fk_film_id

          key: PRIMARY

      key_len: 4

          ref: NULL

         rows: 5462

        Extra: Using index

*************************** 2. row ***************************

           id: 1

  select_type: SIMPLE

        table: b

         type: eq_ref

possible_keys: PRIMARY

          key: PRIMARY

      key_len: 2

          ref: sakila.a.film_id

         rows: 1

        Extra: NULL

2 rows in set (0.00 sec)

 

  要注意:straight_join屬於非標准的語法,在mysql優化器能做出正確選擇的時候就盡量不要使用,只有在mysql優化器做出錯誤的選擇時才使用它,straight_join是一種hit提示關鍵字,使用straight_join關鍵字時,如果只有兩個表關聯要就只使用straight_join就可以了,不需要再去指定join,left join,right join等關鍵字,否則會報1066表或表別名重復的錯誤,另外,使用straight_join后指定關聯表的關聯字段時發現使用using(xx)報語法錯,改使用on a.xx=b.xx形式指定就不報錯,不知道是不是使用straight_join關鍵字時不支持using指定關聯字段

 

關於straight_join提示:

這個提示可放置在select關鍵字之后,也可以放置在任何兩個關聯表的表名之前,第一個用法是讓查詢中所有的表按照在語句中出現的順序進行關聯,第二個用法則是固定其前后兩個表的關聯順序。當mysql沒正確選擇關聯順序的時候,或者由於可能的順序太多導致mysql無法評估所有的關聯順序的時候,straight_join都會很有用,如果關聯表可能的順序太多,可能導致mysql花費大量時間在statistics狀態。可以使用explain語句來查看關聯順序,然后加上這個提示再用explain查看有沒有變化。

 


免責聲明!

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



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