索引的類型
- 索引優化應該是對查詢性能優化最有效的手段了。
- 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萬行:
- 第一個數據集,Sean Carrey 出演了30000部作品,其中有20000部標題包含了Apollo
- 第一個數據集,Sean Carrey 出演了30000部作品,其中有40部標題包含了Apollo
- 第一個數據集,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 <> !=
- 字段類型不匹配;