高性能MySQL - 創建高性能的索引(上)(B-Tree,Hash)

前言

索引在Mysql中也叫作‘鍵(key)’。
基本功能是用於存儲引擎快速找到記錄的一種數據結構。

Question:使用ORM,是否還需要關心索引
即使使用對象關係映射(ORM)工具,仍然要理解索引。除非只是生產非常基本的查詢(例如僅是根據主鍵查詢),否則它很難生成適合索引的查詢。

Mysql中,索引是在存儲引擎層而不是服務層實現的。所以沒有統一的索引標準。

Mysql支持的索引

1. B-Tree 索引

如果沒有特別指明類型,多半是說B-Tree索引。
存儲引擎不再需要進行全表掃描來獲取需要的數據,取而代之的是從索引的根節點開始進行搜索。根節點的槽中存放了指向子節點的指針,存儲引擎根據這些指針向下層查找。通過比較節點頁的值和要查找的值可以找到合適的指針進入下層子節點,這些指針實際上定義了子節點頁中值上限和下限。

假設有如下數據表:

CREATE TABLE Poople(
    last_name  varchar(50)   not null,
    first_name varchar(50)   not null,
    dob        date          not null,
    gender     enum('m','f') not null,
    key(last_name,first_name,dob)
);

索引包括last_name,first_name,dob, 該索引存儲形式:

這裏寫圖片描述

上述索引對如下類型的查詢有效:
1. 全值匹配
全值匹配指的是和索引中的所有列進行匹配。即key的值一併給出,然後查找。
2. 匹配最左前綴
前面提到的索引可用於查找所有姓位Allen的人,即只使用索引的第一列。
3. 匹配列前綴
也可以只匹配某一列的值的開頭部分。如查找所有以J開頭的姓的人。
4.匹配範圍值
可用於查找姓在Allen和Barrymore之間的人。
5.精確匹配某一列並範圍匹配另外一列
6.只訪問索引的查詢
索引樹的節點是有序的,所以除了按值可查找之外,索引還可以用於查詢中的ORDER BY。

(重點)B-Tree索引的限制:
· 如果不是按照索引的最左列開始查找,則無法使用索引。例如上面的例子,無法用於查找名字爲Bill的人,也無法查找某個特定的生日的人,因爲這兩列都不是最左數據列。
· 不能跳過索引中的列。
· 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查找。例如查找WHERE last_name='Smith' AND first_name LIKE 'J%' AND dob = '1976-12-23', 這個查詢只能使用索引的前兩列。

所以,重點在於索引列的順序!!在優化性能的時候,可能需要使用相同的列但順序不同的索引來滿足不同類型的查詢需求

2.哈希排序

哈希索引基於哈希表實現,只有精確匹配索引所有列的查詢纔有效。
在Mysql中,只有Memory引擎顯式支持哈希索引。
假設有如下表:

CREATE TABLE test hash (
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    KEY USING HASH(fname)
) ENGINE=MEMORY;

表中包含如下數據:
fname lname
Arijen Lentz
Baron Schwartz
Peter Zaitsev
Vadim Tkachenko

哈希函數f()
f(‘Arijen’) = 2323;
f(‘Baron’) = 7437;
f(‘Peter’) = 8784;
f(‘Vadim’) = 2458;

哈希索引的數據結構:
slot value
2323 指向第1行的指針
2458 指向第4行的指針
7437 指向第2行的指針
8784 指向第3行的指針

運行如下查詢時:

SELECT lname FROM test hash WHERE fname='Peter';

先計算’Peter’的哈希值,並使用該值尋找對應的記錄指針。因爲f(‘Peter’) = 8784,所以在索引中查找8784,可以找到指向第3行的指針,最後一步是比較第三行的值是否爲‘Peter’,以確保就是要查找的行。
因爲索引自身只需要存儲對應的哈希值,所以索引的結構十分緊湊,這也讓哈希索引查找的速度非常快。然而,哈希索引也有它的限制:
· 哈希索引只包含哈希值和航指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行。不過,訪問內存中的行的速度很快,所以大部分情況下這一點對性能影響並不影響。
· 哈希索引數據並不是按照索引值順序存儲的,所以也就無法用於排序。
· 哈希索引始終是使用索引列的全部內容來計算哈希值的。
· 不支持任何範圍查詢,只支持等值比較查詢
· 訪問哈希索引的數據非常快,除非有很多哈希衝突,當出現哈希衝突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進行比較,直到找到所有符合條件的行。
· 如果哈希衝突很多的話,一些索引維護操作的代價非常高。

所以,哈希索引只適用於某些特定的場所。而一旦適合哈希索引,它帶來的性能提升也是非常顯著的。

InnoDB引擎有一個特殊的功能叫做“自適應哈希索引”。當InnoDB注意到某些索引值被使用得非常頻繁時,它會在內存中基於B-Tree索引之上再創建一個哈希索引,這樣就讓B-Tree索引也具有哈希索引的一些優點,比如快速的哈希查找。這是一個完全自動的、內部的行爲,用戶無法控制或者配置,但可以選擇關閉

自定義哈希索引

SELECT id FROM url WHERE url="http://www.mysql.com" AND url_crc = CRC32("http://www.mysql.com");

MySQL 優化器會使用這個選擇性很高而體積很小的基於url_crc列來完成查找。即使有多個記錄有相同的索引值,查找仍然很快。只需要根據哈希值做快速的整數比較就能找到索引條目。
這樣實現的缺陷是需要維護哈希值,可以手動維護,也可以使用觸發器實現。

CREATE TABLE pseudohash(
    id int unsigned NOT NULL auto_increment,
    url varchar(255) NOT NULL,
    url_crc int unsigned NOT NULL DEFAULT 0,
    PRIMARY KEY(id)
);
DILIMITER//
CREATE TRIGGER pseudohash_crc_ins BEFORE INSERT ON pseudohash FOR EACH ROW BEGIN SET NEW.url_crc=crc32(NEW.url);
END;

CREATE TRIGGER pseudohash_crc_upd BEFORE UPDATE ON pseudo hash FOR EACH ROW BEGIN SET NEW.url_crc=crc32(NEW.url);
END;

DELIMITER;

記住不要使用SHA1()和MD5()作爲哈希函數。因爲這兩個函數計算出來的哈希值是非常長的字符串,會浪費大量空間。SHA1()和MD5()是強加密函數,設計目標是最大限度消除衝突,但這裏並不需要這樣高的要求。

全文索引

是一種特殊類型的索引,查找的是文本中的關鍵詞,而不是直接比較索引中的值。全文搜索和其他幾類索引匹配的方式完全不一樣。更類似於搜索引擎做的事情,而不是簡單的WHERE條件匹配。

其他索引

分形樹索引,新開發的數據結構
聚族索引
覆蓋索引

索引是最好的解決方案嗎

索引並不一定是最好的解決方案,在小型表中,全表掃描更加高效。對於中到大型表,索引就非常有效。但對於特大型的表,建立和使用索引就非常有效。但到特大型的表,建立和使用索引的代價將隨之增長。
如果表的數量特別多,可以建立一個元數據信息表,用來查詢需要用到的某些特性。例如執行那些需要聚合多個應用分佈在多個表的數據的查詢,則需要記錄“哪些用戶的信息存儲在哪個表中”的元數據,這樣在查詢時就可以直接忽略那些不含指定用戶信息的表,對於大型系統,這是常用的技巧。

高性能的索引策略

通常有一些查詢不當地使用索引,或者使得MySQL無法使用已有的索引。如果查詢中的列不是獨立的,則MySQL就不會使用索引。

“獨立的列”是指索引列不能是表達式的一部分,也不能是函數的參數。
例如:

SELECT actor_id FROM skill.actor WHERE actor_id + 1 = 5

MySQL無法自動解析這個方程式。這完全是用戶行爲。

前綴索引和索引選擇性
索引很長的字符串時,會讓索引變得大而慢。通常可以索引開始的部分字符,這樣可以大大節約索引的選擇性。

“索引的選擇性”:不重複的索引值和數據表的記錄總數的比值,範圍從1/記錄總數 到1 之間。索引的選擇型越高,查詢效率越高。

對於BLOB、TEXT或很長的VARCHAR類型的列,必須使用前綴索引,因爲MySQL不允許索引這些列的完整長度。

所以我們要做的就是選擇足夠長的前綴以保證較高的選擇性。

例子:

SELECT COUNT(*) AS intercity FROM skill.city_demo GROUP BY city ORDER BY int DESC LIMIT 10;

結果:
cnt city
65 London
49 Hiroshima
48 Teboksary
……

現在查找到最頻繁出現的城市前綴,從3個字母開始:

SELECT COUNT(*) As cnt,LEFT(city,3) AS pref FROM skill.city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 10;

得出來的指400多到100多不等,繼續增加前綴,實驗後發現前綴長度爲7的時候比較合適,得出來的數值跟原來的城市出現的次數差不多。
還有一個方法是直接計算完整列的選擇性

SELECT COUNT(DISTINCT city)/COUNT(*) FROM skilla.city_demo;

找到合適的前綴長度之後,開始創建前綴索引:

ALTER TABLES skill.city_demo ADD KEY(city(7));

前綴索引的好壞:
好處:使索引更小,更快的有效方法。
壞處:MySQL無法使用前綴索引做ORDER BY和GROUP BY,也無法使用前綴索引做覆蓋掃描。

如何選擇索引列順序?
當不需要考慮排序和分組時,將選擇性高的列放在索引最前列通常是好的。 可能需要根據那些運行頻率最高的查詢來調整索引列的順序,讓這種情況下索引的選擇性最高。

比如說

SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;  

運行一下

SELECT SUM(staff_id = 2),SUM(customer_id = 584) FROM payment 

得到:SUM(staff_id = 2): 7992 ; SUM(customer_id) = 30
對於這條語句來說,將customer_id放在前面,對應的staff_id列的選擇性會變低。

但是這個方法依賴於選定的具體值,可能對其他一些條件值的查詢不公平,導致服務器整體性能可能不是很好。除非是從諸如: pt_query_digest等工具提取的“最差”查詢,再按上述的方法進行改動可能纔有好的效果。

發佈了54 篇原創文章 · 獲贊 32 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章