MYSQL索引

索引的類型

  • 索引優化應該是對查詢性能優化最有效的手段了。
  • mysql只能高效地使用索引的最左前綴列。
  • mysql中索引是在存儲引擎層而不是服務器層實現的
 
B-Tree索引
 
B-Tree一般意味着全部的值都是按順序存儲的,而且每個葉子頁到根的距離相同。

 

圖中根節點沒有畫出來。
 
B-Tree對索引列是順序組織存儲的,索引很適合查找範圍數據。
 
B-Tree索引的限制
  • 若是不是按照索引的最左列開始查找,則沒法使用索引。
  • 不能跳過索引中的列
  • 若是查詢中有某列的範圍查詢,則其右邊全部列都沒法使用索引優化查詢。
這些限制都和索引列的順序存儲有關係。或者說是索引順序存儲致使了這些限制。
 
 
哈希索引(hash index)
 
哈希索引基於哈希表實現的,只有精確匹配索引全部列的查詢纔有效。
 
對於每一行數據,存儲引擎都會對全部的索引列計算一個哈希值(hash code),哈希值是一個較小的值,而且不一樣鍵值的行計算出來的哈希值不同。哈希索引將全部的哈希值存儲在索引中,同時保存指向每一個數據行的指針,這樣就能夠根據,索引中尋找對於哈希值,而後在根據對應指針,返回到數據行。
 
mysql中只有memory引擎顯式支持哈希索引,innodb是隱式支持哈希索引的。
 
哈希索引限制:
  • 哈希索引只包含哈希值和行指針,不存儲字段值,因此不能使用"覆蓋索引"的優化方式,去避免讀取數據表。
  • 哈希索引數據並非按照索引值順序存儲的,索引也就沒法用於排序
  • 哈希索引頁不支持部分索引列匹配查找,由於哈希索引始終是使用索引列的所有內容計算哈希值的。
  • 哈希索引只支持等值比較查詢,包括=,in(),<=>,不支持任何範圍查詢。列入where price>100
  • 訪問哈希索引的數據很是快,除非有不少哈希衝突(不一樣的索引列值卻有相同的哈希值)
  • 若是哈希衝突不少的話,一些索引維護操做的代價也會很高。
 
由於這些限制,哈希索引只適用於某些特定的場合。而一旦適合哈希索引,則它帶來的性能提高將很是顯著。
 
innodb引擎有一個特殊的功能「自適應哈希索引」,當innodb注意到一些索引值被使用的很是頻繁時,且符合哈希特色(如每次查詢的列都同樣),它會在內存中基於B-Tree索引之上再建立一個哈希索引。這是一個徹底自動的,內部行爲。
 
建立自定義哈希索引,像模擬innodb同樣建立哈希索引。
例如只須要很小的索引就能夠爲超長的鍵建立索引。
 
思路:在B-Tree基礎上建立一個僞哈希索引。這和真正的哈希索引不是一回事,由於仍是使用B-Tree進行查找,可是它使用哈希值而不是鍵自己進行索引查找。須要作的就是在查詢的where 子句中手動指定使用哈希函數。
 
例子:
若是須要存儲大量的url,並須要根據url進行搜索查找。若是使用B-Tree來存儲URL,存儲的內容就會很大,由於URL自己都很長。正常狀況下會有以下查詢:
 
mysql> select id from url where url='http://www.mysql.com';

 

若刪除原來url列上的索引,而新增一個被索引的url_crc列,使用crc32作哈希。就能夠實現一個僞哈希索引;查詢就變成下面的方式:
 
mysql> select id from url where url='http://www.mysql.com'
        -> and url_crc=crc32("http://www.mysql.com");

 

這樣性能會提升不少。
固然這樣實現的缺陷是須要維護哈希值,就是url改變對應哈希值也應該改變。能夠手動維護,固然最好是使用觸發器實現。
 
建立URL表
 
create table URL (
     id  int unsigned NOT NULL auto_increment,
     url varchar(255) NOT NULL,
     url_crc  int unsigned NOT NULL DEFAULT 0,
     PRIMARY KEY (id),
     KEY (url_crc)
);
 
建立觸發器:
 
delimiter //
create trigger url_hash_crc_ins before insert on URL FOR EACH ROW BEGIN
SET NEW.url_crc=crc32(NEW.url);
END;
//
 
CREATE TRIGGER url_hash_crc_upd BEFORE UPDATE ON URL FOR EACH ROW BEGIN
SET NEW.url_crc=crc32(NEW.url);
END;
//
 
delimiter ;
 
mysql> select * from URL;
+----+-----------------------+------------+
| id | url                   | url_crc    |
+----+-----------------------+------------+
|  1 | htttp://www.mysql.com | 1727608869 |
+----+-----------------------+------------+
1 row in set (0.00 sec)
 
mysql> insert into URL(url) values('htttp://www.');
Query OK, 1 row affected (0.00 sec)
 
mysql> select * from URL;
+----+-----------------------+------------+
| id | url                   | url_crc    |
+----+-----------------------+------------+
|  1 | htttp://www.mysql.com | 1727608869 |
|  2 | htttp://www.          | 1196108391 |
+----+-----------------------+------------+
2 rows in set (0.00 sec)
 
mysql> UPDATE URL SET url='http://www.baidu.com' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> select * from URL;
+----+-----------------------+------------+
| id | url                   | url_crc    |
+----+-----------------------+------------+
|  1 | htttp://www.mysql.com | 1727608869 |
|  2 | http://www.baidu.com  | 3500265894 |
+----+-----------------------+------------+
2 rows in set (0.00 sec)

 

若是採用這種方式,不要使用SHA1()和MD5()做爲哈希函數,應該這個函數計算出來的哈希值是很是長的字符串,會浪費大量空間,比較時頁回更慢。
 
而若是數據表很是大,crc32()會出現大量的哈希衝突,而解決哈希衝突,能夠在查詢中增長url自己,進行進一步排除;
 
以下面查詢就能夠解決哈希衝突的問題:
mysql> select id from url where url='http://www.mysql.com'
        -> and url_crc=crc32("http://www.mysql.com");

 

空間數據索引(R-Tree)
myisam 表支持空間索引,能夠用做地理數據存儲。
 
全文索引
全文索引是一種特殊類型的索引,它查找的是文本中的關鍵詞,而不是直接比較索引中的值。第7章中會詳細介紹
 
索引的優勢

 
  • 索引大大減小了服務器須要掃描的數據量
  • 索引能夠幫助服務器避免排序和臨時表
  • 索引能夠將隨機I/O變成順序I/O
 
索引只要幫助存儲引擎快速查找到記錄,帶來的好處大於其帶來的額外工做時,索引纔是有效的。對於很是小的表,就不適合索引。由於全表掃描來的更直接,索引還須要維護,開銷也不小。
而對於特大型的表,創建和使用索引的代價隨之增加。這種狀況下,則須要一種技術能夠直接區分出查詢須要的一組數據,而不是一條記錄。例如可使用分區,或者能夠創建元數據信息表等。對於TP級別的數據,定位單條記錄的意義不大,索引常常會使用塊級別元數據技術來替代索引。
 
高性能的索引策略

 
正確地建立和使用索引是實現高性能查詢的基礎。
 
 1 獨立的列
「獨立的列」是指索引列不能是表達式的一部分,也不能是函數的參數。
例如:下面則沒法使用actor_id列的索引:
mysql> select actor_id from sakila.actor where actor_id + 1 = 5
而下面的actor_id 列的索引則會被使用
mysql> select actor_id from sakila.actor where actor_id = 5 - 1

 

2 前綴索引和索引選擇性
 
前綴的選擇性計算:
 
mysql> select count(DISTINCT city)/count(*) from table_name
前綴去重數 除 總數。
 
 
mysql> select
count(DISTINCT LEFT(city,3)) / count(*) AS sel3,                
count(DISTINCT LEFT(city,4)) / count(*)  AS sel4,
count(DISTINCT LEFT(city,5)) / count(*) AS sel5,
count(DISTINCT LEFT(city,6)) / count(*) AS sel6,
count(DISTINCT LEFT(city,7)) / count(*)  AS sel7
from city;
+--------+--------+--------+--------+--------+
| sel3   | sel4   | sel5   | sel6   | sel7   |
+--------+--------+--------+--------+--------+
| 0.7633 | 0.9383 | 0.9750 | 0.9900 | 0.9933 |
+--------+--------+--------+--------+--------+

 

能夠看到當前綴長度達到6以後,選擇性提高的幅度已經很小了。
所以選擇前綴長度爲6;
 
前綴索引是一種能使索引更小,更快的有效辦法,但也是有缺點的:
mysql沒法使用前綴索引作order by 和group by,也沒法使用前綴索引作覆蓋掃描。
 
3 多列索引
在多個列上創建的單列索引大部分狀況下並不能提升mysql的查詢性能。mysql5.0之後引入了一種叫"索引合併(index merge)"的策略,必定程度上可使用表上的多個單列索引來定位指定的行。
 
例子:表film_actor在字段film_id 和 actor_id上各有一個單列索引。
 
mysql> show create table film_actor;
| film_actor | CREATE TABLE `film_actor` (
  `actor_id` smallint(5) unsigned NOT NULL,
  `film_id`  smallint(5) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`actor_id`),
  KEY `idx_fk_film_id` (`film_id`),
  CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES `actor` (`actor_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_film_actor_film`  FOREIGN KEY (`film_id`) REFERENCES `film` (`film_id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
 
mysql> explain select film_id,actor_id from film_actor where actor_id=1 or film_id =1\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
         type: index_merge
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY,idx_fk_film_id
      key_len: 2,2
          ref: NULL
         rows: 29
        Extra: Using union(PRIMARY,idx_fk_film_id); Using where

 

能夠看到使用合併索引( index_merge)技術,優化了這次查詢;
實際上也說明了表上的索引建得很糟糕,否則就不用系統優化了;
 
合併索引有三個變種:OR條件的聯合(union),and條件的相交(intersection),組合前兩種狀況的聯合以及相交。
 
  • 當出現服務器對多個索引作相交操做時(一般有多個AND條件),一般意味着須要一個包含全部相關列的多列索引,而不是多個獨立的單列索引。
  • 當服務器須要對多個索引作聯合操做時(一般有多個OR條件),一般須要耗費大量CPU和內存資源在算法的緩存,排序,和合並操做上。特別是當其中有些索引的選擇性不高,須要合併掃描返回大量數據的時候。
  • 更重要的是,優化器不會把這些計算到"查詢成本(cost)"中,優化器只關心隨機頁面讀取。
 
總之若是在explain中看到索引合併,應該好好檢查一下查詢和表的結構,看是否是已是最優的。也能夠經過optimizaer_switch來關閉索引合併功能。也可使用 INGORE INDEX提示 讓優化器忽略掉某些索引。
 
4 選擇合適的索引列順序
 
正確的順序依賴於使用該索引的查詢,而且同時須要考慮如何更好地知足排序和分組的須要。
 
在一個多列BTree索引中,索引列的順序意味着索引首先按照最左列進行排序,其次是第二列等待。因此,索引能夠按照升序或者降序進行掃描,以知足精確符合列順序的ORDER BY ,GROUP BY,DISTINCT等子句的查詢需求。
 
當不須要考慮排序和分組時,將選擇性最高的列放在前面一般是很好的。這時候索引的做用只是用於優化where條件的查詢。
 
如下面的查詢爲例:
 
mysql> select * from payment where staff_id =2 and customer_id=584;

 

是應該建立一個(staff_id,customer_id)索引仍是應該顛倒一下?能夠跑一些查詢來肯定在這個表中值的分佈狀況,並 肯定哪一個列的選擇性更高
 
mysql> select sum(staff_id=2),sum(customer_id=584) from payment \G;
*************************** 1. row ***************************
     sum(staff_id=2): 7992
sum(customer_id=584): 30
1 row in set (0.04 sec)

 

 
應該講customer_id放在前面,由於對於條件值的customer_id數量更小。
 
mysql> select sum(staff_id=2) from payment where customer_id=584 \G;
*************************** 1. row ***************************
sum(staff_id=2): 17
1 row in set (0.00 sec)

 

能夠看到custmoer_id=584時staff_id=2 只有17個;
須要注意, 查詢結果很是依賴於選定的具體指定值
 
固然還可使用 計算兩參數的選擇性,來肯定哪一個參數放在前面:
 
mysql> select count(DISTINCT staff_id) / count(*) AS staff_id_first, count(DISTINCT customer_id) / count(*) AS customer_id_first from payment\G
*************************** 1. row ***************************
   staff_id_first: 0.0001
customer_id_first: 0.0373

 

顯然customer_id的選擇性(列去重數  除  全部列總數) 更好,
 
索引列的基數(即特定條件下的數量),會影響索引性能;
 
儘管關於選擇性和基數的經驗法則值得去研究和分析,但必定要記住where 子句中的排序,分組和範圍條件等其餘因素,這些因素可能對查詢的性能形成很是大的影響。
 
5  聚簇索引
聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式。
innodb的聚簇索引實際上在同一結構中保存了BTree索引和數據行。(主鍵是BTree索引+記錄是數據行)
 
當表有聚簇索引時,它的數據行實際上存放在索引的葉子頁中。術語"聚簇"表示數據行和相鄰的鍵值緊湊地存儲在一塊兒。
 
下圖展現了聚簇索引中的記錄是如何存放的。注意到,葉子頁包含了行的所有數據,但節點頁只包含了索引列。在這個案例中,索引列包含的是整數值。
 

 

innodb經過主鍵彙集數據,上圖中的"被索引的列"就是主鍵列。
 
彙集的優勢:
  • 能夠把相關數據保存在一塊兒。減小磁盤I/O
  • 數據訪問更快
  • 使用覆蓋索引掃描的查詢能夠直接使用頁節點中的主鍵值
彙集的缺點:
  • 聚簇數據最大限度地提升了I/O密集型應用的性能,但若是數據所有都放在內存中,則訪問的順序就沒有那麼重要了,聚簇索引也就沒什麼優點了。
  • 插入速速嚴重依賴於插入順序。
  • 更新聚簇索引列的代價很高。
  • 出入新行或者主鍵更新須要移動時,可能面臨"頁分裂(page split)"問題。當行的主鍵值要求必須插入到某個已滿的頁中時,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次頁分裂操做。頁分裂會致使表佔用更多的磁盤空間。
  • 二級索引(非聚簇索引)即普通索引,在其葉子節點包含了引用行的主鍵列。
 
 
innodb和myisam的數據分佈對比:
 
crate table layout_test(
     col1 int NOT NULL,
     col2 int NOT NULL,
     PRIMARY KEY(col1),
     KEY(col2)
);

 

假設col1 取值1--10000,按照隨機順序插入。col2取值從1--100之間隨機賦值,因此有不少重複的值。
 
myisam的數據分佈很是簡單,按照數據插入的順序存儲在磁盤上。以下圖:
 
這種分佈方式很容易建立索引,下圖,隱藏了頁的物理細節,只顯示索引中的"節點"
索引中的每一個葉子節點包含"行號。表的主鍵和行號在葉子節點中,且葉子節點根據主鍵順序排列。

 

 
那col2列上的索引又會怎麼樣呢?有什麼特殊嗎?答案是否認的,他和其餘任何索引同樣。

 

 
事實上,myisam中主鍵索引和其餘索引在結構上沒有什麼不一樣。主鍵索引就是一個名爲PRIMARY的惟一非空索引。
 
innodb的數據分佈。由於innodb支持聚簇索引,索引使用很是不一樣的範式存儲一樣的數據。看下圖:
 

 

第一眼看上去,感受和前面的圖5-5沒有什麼不一樣,其實該圖,顯示了整個表,而不是隻有索引。由於在innodb中,聚簇索引"就是"表,因此不用想myisam那樣須要獨立的行存儲。
 
innodb二級索引的葉子節點中存儲的不是"行指針"(即不是那個行號),而是主鍵值,並以此做爲指向行的"指針"。這樣的策略減小了當出現行移動或者數據頁分裂時二級索引的維護工做。固然是用主鍵值當作 指針會讓二級索引佔用更多的空間,同時避免了行出現移動或者數據分頁時二級索引的維護。
 
聚簇和非聚簇表的對比圖

 

innodb 最好主鍵設置爲自增類型 整數;
 
向聚簇索引插入順序的索引值

 

向聚簇索引中插入無序的值:

 

這樣的缺點:
  • 寫入的目標頁可能已經刷新到磁盤上並從緩存中移除,或者尚未加載到緩存中,這樣innodb在插入前不得不先找到並從磁盤讀取目標頁到內存中。致使了大量的隨機I/O。
  • 由於寫入是亂序的,innodb不得不頻繁地作頁分裂操做,以便爲新的行分配空間。頁分裂會致使移動大量數據,一次插入最少須要修改三個頁而不是一個頁。
  • 因爲頻繁的頁分裂,頁會變得稀疏被不規則地填充,因此最終數據會有碎片。
 
6 覆蓋索引
 
覆蓋索引,一個索引包含全部須要查詢的字段的值。
 
優勢:
  • 索引條目一般遠小於數據行大小,因此若是隻須要讀取索引,那麼mysql就會極大地減小數據訪問量。
  • 由於索引是按照列值順序存儲的(至少在單個頁內是如此),因此對於I/O密集型的範圍查詢會比隨機從磁盤讀取每一行數據的I/O要少得多。
  • 一些存儲引擎如Myisam在內存中只緩存索引,數據則依賴於操做系統來緩存,所以要訪問數據須要一次系統調用。
  • 因爲innodb的聚簇索引,覆蓋索引對innodb表特別有用。
 
使用覆蓋索引的狀況:
mysql> explain select store_id,film_id from inventory \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: inventory
         type: index
possible_keys: NULL
          key: idx_store_id_film_id
      key_len: 3
          ref: NULL
         rows: 3496
        Extra: Using index
1 row in set (0.00 sec)

 

此時,有查的字段select store_id,film_id,有一個多列索引idx_store_id_film_id,此時便使用到了覆蓋索引,不會再返回數據表去找數據行,由於索引中已經包含了;
 
假設索引覆蓋了where條件中的字段,可是整個查詢涉及的字段,來看看會發什麼狀況,以及如何重寫查詢以解決該問題。
注意: extra列的"using index" 和type列的"index"不一樣,type列和覆蓋索引毫無關係,它只是表示這個查詢訪問數據的方式,或者說mysql查找行的方式。而extra列的"using index"則說明。數據使用了 覆蓋索引;
 
上面例子中,使用了ACTOR索引,可是沒有使用覆蓋索引直接獲得數據緣由:
  • 沒有任何索引可以覆蓋這個查詢。
  • mysql能在索引中最左前綴匹配的like比較如"Apoll%",而沒法作通配符開頭的like 如"%Apoll%"
 
也有辦法能夠解決上面說的問題,使其使用覆蓋索引。須要重寫查詢並巧妙地設計索引。先新建一個三個列索引(actor,title,prod_id);而後重寫查詢:

 

咱們把這種方式叫作延遲關聯(defferred join),由於延遲了對列的訪問。
查詢在子查詢中使用了覆蓋索引,並找到了prod_id,而後作了內鏈接,經過prod_id再去查其餘列 會快不少。
 
固然這一切都要基於 數據集,假設這個products表中有100萬行,咱們來看一下上面兩個查詢在三個不一樣的數據集上的表現,每一個數據集都包含100萬行:
  1. 第一個數據集,Sean Carrey 出演了30000部做品,其中有20000部標題包含了Apollo
  2. 第一個數據集,Sean Carrey 出演了30000部做品,其中有40部標題包含了Apollo
  3. 第一個數據集,Sean Carrey 出演了50部做品,其中有10部標題包含了Apollo
 
測試結果:

 

結果分析:
  • 在第一個數據集中:
    • 原查詢:從索引actor中讀到30000條數據,再根據獲得的主鍵ID回數據表中再讀30000條數據;總共讀取60000條;
    • 優化後的查詢:先從索引actor2中讀到30000條sena carrey,以後在全部Sean Carrey 中作like 比較 ,找到20000條prod_id;以後仍是要回到數據表中,根據prod_id再讀取20000條記錄;總共讀取50000條;
    • 分析:總數雖然少了17%,可是子查詢中的like比較開銷會比較大,相抵以後效率並無什麼提高。
  • 在第二個數據集中:
    • 原查詢:從索引actor中讀到30000條數據,再根據獲得的主鍵ID回數據表中再讀30000條數據;總共讀取60000條;
    • 優化後的查詢:先從索引actor2中讀到30000條sena carrey,以後在全部Sean Carrey 中作like 比較 ,找到40條prod_id;以後仍是要回到數據表中,根據prod_id再讀取40條記錄;總共讀取30040條;
    • 分析:讀取總數下降了50%, 相比子查詢中的開銷 仍是值得;
  • 第三個數據集:顯示了子查詢效率反而降低的狀況。由於索引過濾時符合第一個條件的結果集已經很小,索引子查詢帶來的成本反而比從表中直接提取完整行更高。
 
7 使用索引掃描來作排序
 
(即order by  ,group by 使用到了索引)
 
mysql設計索引時應該儘可能同時知足排序,有又與查找行。
 
只有當索引的列順序和order by子句的順序徹底一致,而且全部列的排序方向(倒序或正序)都是同樣時,mysql才能使用索引來對結果作排序。
 
若是查詢須要關聯多張表,則只有當order by 子句引用的字段所有爲一個表時,才能使用索引作排序。
 
order by 子句知足最左前綴的要求,或者最左前綴爲常數,排序方向也要一致;
     idx_a_b (a,b)
     可以使用索引幫助排序的查詢:
  •      order by a          
    • 知足最左前綴要求           
  •      a = 3 order by b
    • 知足最左前綴爲常數
  •      order by a,b
    • 知足最左前綴要求
  •      order by a desc,b desc
    • 知足最左前綴要求
  •      a>5 order by a,b
    • 知足最左前綴要求
 
     不能使用索引幫助排序的查詢
  •       order by b 
    • 不知足最左前綴要求
  •       a >5 order by b
    • 不知足最左前綴,且,最左前綴不是常數
  •       a in (1,3) order  by b
    • 不知足最左前綴,且,最左前綴不是常數
  •       oder by a asc ,b desc
    • 排序方向不一致
 
idx_a_b_c(a,b,c)
    where a = 5 order by c
不能使用索引進行排序,不能跨越索引項進行排序;也是一種不知足最左前綴的狀況;
 
8  壓縮(前綴壓縮)索引
myisam使用前綴壓縮來減小索引的大小,從而讓更多的索引能夠放入內存,這在某些狀況下能極大地提高性能。默認只壓縮字符串,但經過參數設置也能夠對整數壓縮。
 
9  冗餘和重複索引
mysql容許在相同列上建立多個索引,但須要單獨維護重複的索引,而且優化器在優化查詢的時候也須要逐個考慮,這會影響性能。
 
重複索引:
實際上在ID上建了三個索引,這就是重複索引。
 
冗餘索引:
已有索引(A,B),再重建索引(A)就是冗餘索引;
而此時(B,A),則不是冗餘索引。索引(B)也不是索引(A,B)的冗餘索引;
 
已有索引(A),再建索引(A,ID),其中ID是主鍵,對innodb來講主鍵列已經包含在二級索引中了,因此這也是冗餘索引;
 
大多數狀況都不需冗餘索引,應該儘可能擴展已有的索引而不是建立新索引。
固然有時候也是須要冗餘索引的,由於擴展已有的索引會致使其變得太大,從而影響其餘使用該索引的查詢的性能。
 
建立索引

 
單列索引
  create index idx_test1 on tb_student(name);
 
聯合索引
  create index idx_test2 on tb_student(name,age)
 
索引中先根據name排序,name相同的狀況下,根據age排序
 
設計索引原則:
  • 搜索的索引列。
    • 不必定是所要選擇的列;即where 後面的查詢條件加索引,而不是select 後面的選擇列
  • 使用惟一索引。
  • 使用短索引。
    • 若是對字符串列進行索引,應該指定一個前綴長度,只要有可能就應該這樣作。
  • 利用最左前綴。
  • 不要過分索引
  • innodb表,指定主鍵,而且是自增的最好;
 
BTREE索引和HASH索引:
  • 均可以用在,where col=1 or col in (15,18,20),這樣的定值查詢中;
  • 而在範圍查詢中,where col>1 and col<10 或者 col like 'ab%' or col between 'lisa' and 'simon';此時只有BTREE索引能使用;HASH索引在這種狀況中,不會被使用到,會對全表進行掃描;
 
維護索引與表

 
維護索引和表
維護表有三個主要目的:
  • 找到並修復損壞的表
  • 維護準確的索引統計信息
  • 減小碎片
 
找到並修復損壞的表
check table  tb_name:檢查是否發生了表損壞
repair table  tb_name:
 
更新索引統計信息
mysql優化器經過兩個API來了解存儲引擎的索引值的分佈信息,以決定如何使用索引。
records_in_range():經過向存儲引擎傳入兩個邊界值獲取在這個範圍大概有 多少條記錄
 
info():該接口返回各類類型的數據,包括索引的 基數(每一個鍵值有多少條記錄)
 
mysql優化器使用的是基於成本的模型,而衡量成本的主要指標就是一個查詢須要掃描多少行。若是表沒有統計信息,或者統計信息不許確,優化器就極可能作出錯誤的決定。
 
analyze  table :從新生成統計信息;
 
mysql> show index from actor\G;
*************************** 1. row ***************************
        Table: actor
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: actor_id
    Collation: A
  Cardinality: 200
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
*************************** 2. row ***************************
        Table: actor
   Non_unique: 1
     Key_name: idx_actor_last_name
 Seq_in_index: 1
  Column_name: last_name
    Collation: A
  Cardinality: 200
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
2 rows in set (0.00 sec)
 
Cardinality,顯示了存儲引擎估算索引列有多少個不一樣的取值。
 
 mysql5.6 之後能夠經過參數innodb_analyze_is_persistent,來控制analyze 是否啓動;
減小索引和數據的碎片
 
數據碎片三種類型:
行碎片(row fragmentation)
數據行被存儲爲多個地方的多個片斷中。
行間碎片(Intra-row fragmentation)
邏輯上順序的頁,在磁盤上不是順序存儲的。
剩餘空間碎片(Free space fragmentation)
數據頁中有大量的空餘空間。
 
使用命令:
optimize table tb_name,清理碎片。
 
mysql> OPTIMIZE TABLE actor;
+--------------+----------+----------+-------------------------------------------------------------------+
| Table        | Op       | Msg_type | Msg_text                                                          |
+--------------+----------+----------+-------------------------------------------------------------------+
| sakila.actor | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| sakila.actor | optimize | status   | OK                                                                |
+--------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.02 sec)

 

 
對於不支持該命令的引擎能夠經過一個不作任何操做(no-op)的alter table 操做來重建表。
 
mysql> alter table actor engine=innodb;
Query OK, 200 rows affected (0.02 sec)
Records: 200  Duplicates: 0  Warnings: 0

 

索引項的值發生改變,此時索引項在索引表中的位置,就須要發生改變,這樣一個行爲稱爲索引維護;
 
由於若是不進行索引維護的話,就是說索引項的值改變後,並無從新排序,這樣改變項多了以後,就不是一個順序排序了,就起不到索引的效果了;
 
  • 索引維護由數據庫自動完成
  • 插入/修改/刪除每個索引行都變成一個內部封裝的事務
  • 索引越多,事務越長,代價越高
  • 索引越多對錶的插入和索引字段修改就越慢
 
 
假設一個錶帶了兩個索引;
那麼系統會總共建立3張表,一個數據表,兩個索引表;
在修改一個索引項數據的時候,會內部封裝成一個事務,同時這三張表進行修改;
 
使用索引

 
    1.使用WHERE查詢條件創建索引
 
    select  a,b  from tab where c=?;
          idx_c (c)
 
     select a,b from tab where c=? and d=?
          idx_cd(c,d)
 
     2.排序ORDER BY,GROUP BY,DISTINCT 字段添加索引      

 

     
3.聯合索引與前綴查詢
 
  • 聯合索引能爲前綴單列,複列查詢提供幫助
       
       在mysql5.6前,where a? and c? 只能部分
 
  • 合理建立聯合索引,避免冗餘
    (a),(a,b),(a,b,c)
    其實只須要對(a,b,c)創建索引便可;
 
索引與字段選擇性
    某個字段其值的重複程度,稱爲該字段的選擇性;
 

 

選擇性不好的字段一般不適合建立單列索引
  • 男女比例相仿的表中性別不適合建立單列索引
  • 若是男女比例極不平衡,要查詢的又是少數方(理工院校查女生)能夠考慮使用索引
 
聯合索引中選擇性好的字段應該排在前面
 
長字段的索引

 
  • 在很是長的字段上創建索引影響性能
  • innodb索引單字段(utf8)只能取前767bytes
  • 對長字段處理的方法
         email 類,創建前綴索引
              Mail_addr  varchar(2048)
              idx_mailadd (Mail_addr(30))----只保存前30個字符爲索引
              mysql容許對字段進行前綴索引
              對長字段咱們也能夠主動只取字段的前半部分;   
 
          住址類,分拆字段
           Home_address  varchar(2048)
           idx_Homeadd (Home_addr(30)) ???? -作前綴索引極可能行不通的,由於極可能前半段都是相同的省市區街道名稱
 
           方法:分拆字段
           Province  varchar(1024), City varchar(1024),District varchar(1024),Local_address varchar(1024)
            而後創建聯合索引或單列索引;
 
          
          
索引覆蓋掃描(直接使用索引中的數據,不須要從數據表中返回數據)
  • 最核心SQL考慮索引覆蓋
          select name from tb_user where UserId=?
          Key idx_uid_name(userid,name)
 
      不須要回表獲取name字段,IO最少,效率最高;
 
 
沒法使用索引

 
  • 索引列進行數學運算或函數運算
          where   id+1 = 10   ×
          where  id = (10-1)  √
     
          year(col) < 2007  ×
          col < '2007-01-01'√
   
  •  未含複合索引的前綴字段
          idx_abc (a,b,c):
          where b=? and c=? ×
 
          idx_bc(b,c) √
 
         注意:idx_adb (a,b,c)包含 idx_a (a),包含idx_ab(a,b),在5.6以後還包含idx_ac(a,c)
 
  •  前綴通配‘_’ 和‘%’通配符   
         LIKE '%XXX%' ×
         LIKE 'XXX%'   √
 
當使用到 like'%xx%'時,沒法使用索引,解決辦法是,使用全文索引在5.6以後。或者,使用鏈接 內層掃描 全索引表,以後找到符合條件的,再回到表中 查找 記錄,這樣能夠下降IO消耗,由於 通常來說 索引表 比較小,全掃索引表的話相對開銷 比 全掃數據表,要小不少;
 
  •  用OR分割開的條件,若是or前的條件中的列有索引,然後面的列中沒有索引,那麼所涉及的索引都不會被用到。由於後面的查詢確定要走全表掃描,在存在全表掃描的狀況下,就沒有必要多一次索引掃描增長I/O訪問,一次全表掃描過濾條件就足夠了。
 
  •   where條件使用NOT,<>,!=
  •   字段類型匹配
           並不絕對,可是沒法預測地會形成問題,不要使用;
           例子:a int(11) , idx_a (a);
                     where a = '123' ×
                      where a = 123 √
          因爲類型不一樣,mysql須要作隱式類型轉換才能進行比較。
          注意字段的類型,尤爲是int型時若是使用字符型去匹配,能獲得正確結果,而不會使用索引;一樣若是字段是,varchar型,那麼where 後面若是是一個 INT,也是不能使用索引;
          
mysql比較轉換規則:
兩個參數至少一個是null是不須要轉換;
兩個參數類型同樣時不須要轉換;
 
TIMESTAMP/DATATIME 和 常量 比較-->常量轉換爲timestamp/datetime
decimal和整數比較---------------------->整數轉換爲decimal
decimal和浮點數------------------------->decimal轉換爲浮點數
 
兩個參數都會被轉換爲浮點數再進行比較:
若是字符串型,比較,=,+,-,等;
 
一個字符串和一個整形-------------------->均轉換成浮點型
mysql> select '18015376320243459'=18015376320243459;
+---------------------------------------+
| '18015376320243459'=18015376320243459 |
+---------------------------------------+
|                                     1 |
 
mysql> select '1801'+0;
+----------+
| '1801'+0 |
+----------+
|     1801 |
+----------+

 

若是 age int(10), index_age(age);
mysql> explain select name from indextest where age='30'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: indextest
         type: ref
possible_keys: idx_age
          key: idx_age
      key_len: 1
          ref: const
         rows: 1
        Extra:
1 row in set (0.00 sec)
索引是數值;查詢條件是字符串'30',轉換成肯定數值30,轉換使用索引;
索引時字符串,查詢是數值時,沒法使用索引;
 
使用cast函數對age作顯示的類型轉換,會使索引消失;
即對索引項作任何的函數改變,都會使索引失效;
 
 總結

 
  •  BTREE
    • 存儲索引項與主鍵
    • BTREE索引可用在定值查詢,範圍查詢,
  •  HASH
    • 存儲哈希值與行指針
    • 僅用於定值查詢,建立僞哈希索引;
  •  前綴的選擇性計算(去重前綴數除總數)
    • mysql> select count(DISTINCT city)/count(*) from table_name
  •  索引合併(index merge):說明此時表上索引,表結構等須要優化了;
  •  選擇合適的索引列順序:須要根據表中實際數據進行選擇,選擇性高的放在前;
  •  聚簇索引:innodb的聚簇索引實際上在同一結構中保存了BTree索引和數據行
  •  myisam的數據分佈
    •  myisam按照數據插入的順序存儲在磁盤上
    •  主鍵索引時,自動增長行號,表的主鍵和行號在葉子節點中,且葉子節點根據主鍵順序排列;
    •  其餘列索引和主鍵索引無區別;
  •  innodb數據分佈:
    • 使用聚簇索引;
    • 二級索引包含索引項和主鍵值
  •  覆蓋索引:
    • extra中using index;
    • 延遲關聯(defferred join);
    • 固然覆蓋索引並非都能提高性能,須要根據集體數據集;
  •  使用索引進行排序,不能跨越索引項進行排序;
  •  索引維護:由數據庫自動完成,將DML封裝成內部事務,索引越多代價越高,
  •  更新索引統計信息:
    • records_in_range()獲取範圍中有多少鍵值,
    • info()獲取索引基數
  •  清理碎片:
    • optimize table tbl,
    • alter table tbl engine=innodb;
  •  使用索引
    • where
    • order by 、group by、distinct,
    • 聯合索引:注意冗餘,選擇性好的放在聯合索引左側;
  •  長字段的索引:
    • 創建前綴索引
    • 分拆字段創建聯合索引,
  •  沒法使用索引:
    • 索引列進行數學運算或函數運算
    • 未遵照最左前綴原則
    • or條件後一列沒有索引
    • where條件使用not <> !=
    • 字段類型不匹配;
相關文章
相關標籤/搜索