mysql(四)索引失效分析

mysql索引會失效的情況要想分析MySQL查詢語句中的相關信息,如是全表查詢還是部分查詢,就要用到explain.關於explain,可以查看我另一篇文章

一、索引優缺點

索引的優點

  • 大大減少了服務器需要掃描的數據量
  • 可以幫助服務器避免排序或減少使用臨時表排序
  • 索引可以隨機I/O變爲順序I/O

索引的缺點

  • 需要佔用磁盤空間,因此冗餘低效的索引將佔用大量的磁盤空間
  • 降低DML性能,對於數據的任意增刪改都需要調整對應的索引,甚至出現索引分裂
  • 索引會產生相應的碎片,產生維護開銷

不使用索引的場景

  • 經常增刪改查的列不要建索引,因爲修改數據的同時,索引也是要修改的。
  • 有大量重複的列不要創建索引mysql查詢結果要小於30%纔會使用索引,不然會使用全表掃描。mysql優化器認爲全表掃描的成本小於索引,所以放棄索引,這是很多情況下沒使用索引的原因。下面會具體介紹。所以具有唯一性或者重複性很少的列建立索引會非常有效
  • 表記錄太少不要建立索引,只有當數據庫已經有列足夠多的數據時,它的性能纔會有實際參考價值。只有當記錄超過1000條數據時,數據總理也超過了mysql服務器上的內存總量時,數據庫的性能測試結果纔有意義。

 

二、數據庫不使用索引的情況

下面舉的例子中,GudiNo、StoreId列都有單獨的索引。

2.1 LIKE操作中,like name '%aaa%',則name索引會失效,但是like name ‘aaa%’是可以使用索引。

其它通配符同樣,也就是說,在查詢條件中使用正則表達式時,只有在搜索模板的第一個字符不是通配符的情況下才能使用索引。

下圖中第一句使用的%,沒有使用索引,從rows爲224147,使用索引rows爲1。

2.2 where語句中使用 <>和 != 會失效(主鍵索引除外),注意:在整形索引中使用<,>不會使索引失效。主鍵索引使用!=,>,<都不會失效

2.3 where語句中使用 or,但是沒有把or中所有字段加上索引。

這種情況,如果需要使用索引需要將or中所有的字段都加上索引。

2.4 where語句中對字段表達式或函數操作,會使索引失效

2.5 where語句中使用Not In 會導致索引失效

2.6 where 語句中使用 is null 或者 is not null,當查詢量達到總表的30%以上時,索引就會失效。

有說“應儘量避免在where 子句中對字段進行null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描”,實測沒有全表掃描。

在組合索引中某一索引列有null值,則索引失效。這句話其實是不對的,在單列索引中索引列有null值不會失效。在組合索引中索引列有null值也是可以使用組合索引的,MySQL難以優化引用了可空列的查詢,它會使索引、索引統計和值更加複雜。可空列需要更多的儲存空間,還需要在MySQL內部進行特殊處理。當可空列被索引的時候,每條記錄都需要一個額外的字節,還可能導致 MyISAM 中固定大小的索引(例如一個整數列上的索引)變成可變大小的索引,所以儘量避免null值。

應該儘量避免字段是null,儘量避免mysql where語句中出現null的判斷

2.7"對於多列索引,不是使用的第一部分,則不會使用索引", 實測即使多索引,沒有使用第一部分,也會命中索引,沒有全表掃描。

2.8 如果排序使用了索引,而select列未使用索引列,則該索引失效,這是因爲優化器執行直接執行全表掃描速度更快。主鍵索引除外,任何一張表都有一個唯一索引primary,索引列爲主鍵列。如下新建列一個password索引,索引列爲account。

mysql> explain select * from users order by account desc;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1  | SIMPLE      | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10   | 100.00   | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 行於數據集 (0.05 秒)

mysql> explain select account from users order by account desc;
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1  | SIMPLE      | users | NULL       | index | NULL          | password | 4       |   NULL | 10   | 100.00   | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
1 行於數據集 (0.12 秒) # 非聚簇索引和聚簇索引差別

mysql> explain select account from users order by id desc;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
 +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| 1  | SIMPLE      | users | NULL       | index | NULL          | PRIMARY | 4       | NULL | 10   | 100.00   | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 行於數據集 (0.05 秒)

mysql> explain select * from users order by id desc;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| 1  | SIMPLE      | users | NULL       | index | NULL          | PRIMARY | 4       |       NULL | 10   | 100.00   | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+

在查詢語句中where也使用了索引,order by 也使用了索引。這時where 中的索引生效,order by中的索引失效。當where中的索引失效後order by中的索引纔會生效。有一個merge的組合索引,向左原則包括account索引,id爲主鍵,爲primary索引。

mysql> explain select * from users where account='11' order by id desc;
+----+-------------+-------+------------+------+----------------+-------+---------+-------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys  | key   | key_len | ref   | rows | filtered | Extra                                 |
+----+-------------+-------+------------+------+----------------+-------+---------+-------+------+----------+---------------------------------------+
| 1  | SIMPLE      | users | NULL       | ref  | merge,password | merge | 4       |   const | 1    | 100.00   | Using index condition; Using filesort |
+----+-------------+-------+------------+------+----------------+-------+---------+-------+------+----------+---------------------------------------+
1 行於數據集 (0.11 秒)

mysql> explain select * from users where password='11' order by id desc;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1  | SIMPLE      | users | NULL       | index | NULL          | PRIMARY | 4       |       NULL | 10   | 10.00    | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

組合索引最左原則問題

網上說組合索引遵循最左原則。比如有一個組合索引有3個列,account,name,phone。這時相當於新建了3個索引,分別是accout、account和name。而查詢只能是以下3種纔會使用索引。

 

select * from users where account='1';
select * from users where account='1' and name='22';
select * from users where account='1' and name='22' and phone='15942075450';

而具體測試並不只是上面的那3種情況。現在索引還是上面那個索引測試,數據庫以上面的圖爲準。
如下情況也是可以使用索引的

  mysql> explain select * from users where name='22' and account='11';
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------+
  | id | select_type | table | partitions | type | possible_keys | key   | key_len | ref         | rows | filtered | Extra |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------+
  | 1  | SIMPLE      | users | NULL       | ref  | merge         | merge | 1027    | const,const | 1    | 100.00   | NULL  |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------+
  mysql> explain select * from users where name='22' and account='11' and phone='111';
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+
  | id | select_type | table | partitions | type | possible_keys | key   | key_len | ref               | rows | filtered | Extra |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+
  | 1  | SIMPLE      | users | NULL       | ref  | merge         | merge | 2050    | const,const,const | 1    | 100.00         | NULL  |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+
  1 行於數據集 (0.04 秒)
  mysql> explain select * from users where name='22' and phone='111' and account='11';
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+
  | id | select_type | table | partitions | type | possible_keys | key   | key_len | ref               | rows | filtered | Extra |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+
  | 1  | SIMPLE      | users | NULL       | ref  | merge         | merge | 2050    | const,const,const | 1    | 100.00     | NULL  |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------------------+------+----------+-------+

上面的這些情況順序是可以顛倒的,也就是最左原則失效了?其實是mysql優化器會優化where子句的條件順序,讓查詢符合索引順序,那麼你可能會有疑問順序顛倒後經過mysql優化器效率會不會降低,經測試是基本沒有什麼影響。最左原則是指:mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式,所以這兩條都是會命中索引的。要記住和順序是無關的

爲什麼下面這種情況還會用到merge索引呢?

 

  mysql> explain select * from users where phone='111' and account='11';
  +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
  | id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra                 |
  +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
  | 1  | SIMPLE      | users | NULL       | ref  | merge         | merge | 4       | const | 1    | 10.00    | Using index condition |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+

上面的where條件並沒有用到merge中的3個索引,爲什麼還是使用到索引了呢?由上面可以看到Extra值爲Using index condition,意義爲二次查詢。首先要知道最左原則是沒有順序的,上面的where中可以看到由account='11',也就是說這裏使用到了merge索引,然後在merge索引中找到對於數據行的id,根據該id在找到數據表中的數據行(回表),然後在這些數據行中篩選phone爲11的數據行,這就是Using index condition,大概意思就是即使用了索引,又得需要回表,最終得到數據
由此得出結論:只有where條件中有組合索引中的第一個字段就肯定會使用索引。

單列索引和組合索引優先級問題

組合索引是優先於單列索引的。比如現在有一個account索引爲account索引列,和一個merge組合索引擁有account,name,phone索引列。

mysql> explain select * from users where account='11';
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| 1  | SIMPLE      | users | NULL       | ref  | merge,account | merge | 4       | const | 1    | 100.00   | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
1 行於數據集 (0.04 秒)

從上述可以看出possible_keys有merge和account倆個索引,key爲merge。也就是組合索引優先級於單列索引。

聯合索引相對於單列索引的優點

  • 減少開銷。建一個聯合索引(col1,col2,col3),實際相當於建了(col1),(col1,col2),(col1,col2,col3)三個索引。每多一個索引,都會增加更新操作的開銷和磁盤空間的開銷。對於大量數據的表,使用聯合索引會大大的減少開銷!
  • 覆蓋索引。對聯合索引(col1,col2,col3),如果有如下的sql: select col1,col2,col3 from test where col1=1 and col2=2。那麼MySQL可以直接通過遍歷索引表取得數據,而無需回表,這減少了很多的隨機io操作。減少io操作,特別的隨機io其實是dba主要的優化策略。所以,在真正的實際應用中,覆蓋索引是主要的提升性能的優化手段之一
  • 效率高。索引列越多,通過索引篩選出的數據越少。有1000W條數據的表,有如下sql:select from table where col1=1 and col2=2 and col3=3,假設假設每個條件可以篩選出10%的數據,如果只有單值索引,那麼通過該索引能篩選出1000W10%=100w條數據,然後再回表從100w條數據中找到符合col2=2 and col3= 3的數據,然後再排序,再分頁;如果是聯合索引,通過索引篩選出1000w10% 10% *10%=1w,效率提升可想而知!

 

sql查詢常用的優化方法

mysql是存儲在磁盤上的一個個文件,減少了mysql的交互也就減少i/o的交互,i/o交互是阻塞的。所以要減少mysql的交互。

  • 減少索引長度:設置索引時可能的話應該指定一個前綴長度。例如,如果有一個CHAR(255)的 列,如果在前10 個或20 個字符內,多數值是惟一的,那麼就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。在navicat中設置索引長度如下。

     

    添加索引長度

  • 覆蓋索引:查詢列也就是select的字段要被所使用的索引覆蓋,查詢列中可包括主鍵覆蓋索引其實就是減少了一個回表的過程,直接在索引表裏面就獲得了自己想要的字段和數據。索引是一張表,這個列表存儲着索引字段和索引字段對應的索引值以及索引值所在行對應的物理地址。索引可以不用掃描全表來定位數據,而是直接找到該值對應的物理地址然後訪問相應的數據。

    不是所有類型的索引都可以成爲覆蓋索引。覆蓋索引必須要存儲索引的列,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引。以下爲幾種覆蓋索引的案例。

    • 還是以上面的數據庫爲準,無where條件查詢時

      mysql> explain select account from users;
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
      | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
      | 1  | SIMPLE      | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10   | 100.00   | NULL  |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
      

      可以看出type爲all,爲全表掃描,接下來我們可以新建一個索引爲account,索引列爲account

      mysql> explain select account from users;
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      | 1  | SIMPLE      | users | NULL       | index | NULL          | account | 4       | NULL | 10   | 100.00   | Using   index |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      

      可以看出type使用了index,不是全表掃描了,然後使用了索引account,在Extra列中的值爲using index,using index是🈯️查詢時不需要回表查詢,直接通過索引就可以獲取查詢的數據。

    • 二次檢索優化
      前提是有一個account索引,索引列爲account爲整數類型。

      mysql> explain select name from users where account >5;
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
      | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                 |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
      | 1  | SIMPLE      | users | NULL       | range | account       | account | 4       | NULL | 1    | 100.00   | Using index condition |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
      

      可以看出Extra爲Using index condition,即代表二次檢索。這裏查詢列是name,而索引中沒有這個列,所以要使用覆蓋索引的話,需要新建一個組合索引merge,索引列組合爲account和name,有倆個索引,一個是account單列索引,另一個是account和name的組合索引。

      mysql> explain select name from users where account >5;
      +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
      | id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
      +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
      | 1  | SIMPLE      | users | NULL       | index | NULL          | merge | 1027    | NULL | 10   | 33.33    | Using where; Using index |
      +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
      

      可以看出Extra值變爲了Using index了

    • 排序優化
      前提是有一個account索引,索引列爲account爲int類型。

      mysql> explain select id,name from users order by account desc;
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      | 1  | SIMPLE      | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10   | 100.00   | Using filesort |
      +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
      

      可以看出上述sql未使用索引,新建了account索引爲什麼沒使用呢?上面已經介紹過了,因爲select的字段中沒有account。接下來我們使用覆蓋索引來優化,新建一個組合索引merge,索引列爲account和name。

      mysql> explain select id,name from users order by account desc;
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      | 1  | SIMPLE      | users | NULL       | index | NULL          | merge   | 1027    | NULL | 10   | 100.00   | Using index |
      +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
      

      可以看出上述使用了merge索引,且Extra爲Using index,使用了覆蓋索引。上面不是說覆蓋索引是查詢列包含在所使用的索引中嗎?那麼id也不包含在merge索引中啊,id爲主鍵,這就是primary索引的不同。

    總結:覆蓋索引適用於select查詢列比較少的情況下,這樣不需要回表,查詢更加優化

  • 參考:傳送門

 

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