-
索引是什么?有什么利弊?一旦在面試中被問道,對於新入門的小白可能是個棘手的問題。
-
本篇文章將會詳細講述什么是索引、索引的優缺點、數據結構等等常見的知識。
什么是索引
-
索引就是一種的數據結構,存儲表中特定列的值並對值進行排序,所以是在表的列上面創建的。索引將通過縮小一張表中需要查詢的記錄的數目來加快搜索的速度。如果沒有索引,數據庫不得不進行全表掃描。索引就好比一本書的目錄,它會讓你更快的找到內容。
索引的優點
-
通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
-
可以大大加快數據的檢索速度,避免進行全表的數據掃描,大大減少遍歷匹配的行數,這也是創建索引的最主要的原因。
-
可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。
-
在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間。
-
通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。
索引的缺點
-
創建索引和維護索引要耗費時間,這種時間隨着數據量的增加而增加。
-
索引需要占物理空間,除了數據表占數據空間之外,每一個索引還要占一定的物理空間,如果要建立聚簇索引,那么需要的空間就會更大。
-
當對表中的數據進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了數據的維護速度。
在哪些列建立索引
-
在經常需要搜索的列上,可以加快搜索的速度;
-
在作為主鍵的列上,強制該列的唯一性和組織表中數據的排列結構;
-
在經常用在連接的列上,這些列主要是一些外鍵,可以加快連接的速度;
-
在經常需要根據范圍進行搜索的列上創建索引,因為索引已經排序,其指定的范圍是連續的;
-
在經常需要排序的列上創建索引,因為索引已經排序,這樣查詢可以利用索引的排序,加快排序查詢時間;
-
在經常使用在
WHERE
子句中的列上面創建索引,加快條件的判斷速度。
不在哪些列建索引?
-
對於那些在查詢中很少使用或者參考的列不應該創建索引。這是因為,既然這些列很少使用到,因此有索引或者無索引,並不能提高查詢速度。相反,由於增加了索引,反而降低了系統的維護速度和增大了空間需求。
-
對於那些只有很少數據值的列也不應該增加索引。這是因為,由於這些列的取值很少,例如人事表的性別列,在查詢的結果中,結果集的數據行占了表中數據行的很大比例,即需要在表中搜索的數據行的比例很大。增加索引,並不能明顯加快檢索速度。
-
對於那些定義為
text
,image
和bit
數據類型的列不應該增加索引。這是因為,這些列的數據量要么相當大,要么取值很少。 -
當修改性能遠遠大於檢索性能時,不應該創建索引。這是因為,修改性能和檢索性能是互相矛盾的。當增加索引時,會提高檢索性能,但是會降低修改性能。當減少索引時,會提高修改性能,降低檢索性能。因此,當修改性能遠遠大於檢索性能時,不應該創建索引。
索引的數據結構
常見的索引的數據結構有:B+Tree
、Hash索引
、FullText索引
、R-Tree索引
。
Hash 索引
1. 概述:
MySQL 中,只有
Memory
存儲引擎支持Hash
索引,是Memory
表的默認索引類型。hash 索引把數據的索引以 hash 值形式組織起來,因此檢索效率非常高,可以一次定位,不像B-/+Tree
索引需要進行從根節點到葉節點的多次 IO 操作。
2. Hash 索引的缺點:
① Hash 索引僅僅能滿足等值的查詢,不能滿足范圍查詢。因為數據在經過 Hash 算法后,其大小關系就可能發生變化。② Hash 索引不能被排序。同樣是因為數據經過 Hash 算法后,大小關系就可能發生變化,排序是沒有意義的。
③ Hash 索引不能避免表數據的掃描。因為發生 Hash 碰撞時,僅僅比較 Hash 值是不夠的,需要比較實際的值以判定是否符合要求。
④ Hash 索引在發生大量 Hash 值相同的情況時性能不一定比 B-Tree 索引高。因為碰撞情況會導致多次的表數據的掃描,造成整體性能的低下,可以通過采用合適的 Hash 算法一定程度解決這個問題。
⑤ Hash 索引不能使用部分索引鍵查詢。因為當使用組合索引情況時,是把多個數據庫列數據合並后再計算 Hash 值,所以對單獨列數據計算 Hash 值是沒有意義的。
FullText 索引
1. 概述:
全文索引,目前 MySQL 中只有
MyISAM
存儲引擎支持,並且只有char
、varchar
、text
類型支持。它用於替代效率較低的like
模糊匹配操作,而且可以通過多字段組合的全文索引一次性全模糊匹配多個字段。
2. 存儲結構:
同樣使用
B-Tree
存放索引數據,但使用的是特定的算法,將字段數據分割后再進行索引(一般每 4 個字節一次分割),索引文件存儲的是分割前的索引字符串集合,與分割后的索引信息,對應 Btree 結構的節點存儲的是分割后的詞信息以及它在分割前的索引字符串集合中的位置。
B-/+Tree 索引
-
B+Tree 是 mysql 使用最頻繁的一個索引數據結構,是 Innodb 和 Myisam 存儲引擎模式的索引類型。相對 Hash 索引,B+樹在查找單條記錄的速度比不上 Hash 索引,但是更適合排序等操作。
1. B+Tree 索引的優點:
-
帶順序訪問指針的 B+Tree:B+Tree 所有索引數據都在葉子結點上,並且增加了順序訪問指針,每個葉子節點都有指向相鄰葉子節點的指針。這樣做是為了提高區間查詢效率,例如查詢 key 為從 18 到 49 的所有數據記錄,當找到 18 后,只需順着節點和指針順序遍歷就可以一次性訪問到所有數據節點,極大提到了區間查詢效率。
-
大大減少磁盤 I/O 讀取次數。
B-/+Tree 索引:
-
文件系統及數據庫系統普遍采用 B-/+Tree 作為索引結構:一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲在磁盤上。這樣的話,索引查找過程中就要產生磁盤 I/O 消耗,相對於內存存取,I/O 存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤 I/O 操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤 I/O 的存取次數。
局部性處理與磁盤預讀
-
由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁盤 I/O。為了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存。
-
由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高 I/O 效率。預讀的長度一般為頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為 4k),主存和磁盤以頁為單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向后連續讀取一頁或幾頁載入內存中,然后異常返回,程序繼續運行。
B-/+Tree 索引的性能分析
-
上文說過一般使用磁盤 I/O 次數評價索引結構的優劣。先從 B-Tree 分析,根據 B-Tree 的定義,可知檢索一次最多需要訪問 h 個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次 I/O 就可以完全載入。為了達到這個目的,在實際實現 B-Tree 還需要使用如下技巧:
-
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現了一個節點只需一次 I/O。
-
B-Tree 中一次檢索最多需要
h-1
次 I/O(根節點常駐內存),漸進復雜度為O(h)=O(logdN)
。一般實際應用中,出度 d 是非常大的數字,通常超過 100,因此 h 非常小(通常不超過 3)。 -
綜上所述,用 B-Tree 作為索引結構效率是非常高的。
-
而紅黑樹這種結構,h 明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的 I/O 漸進復雜度也為 O(h),效率明顯比 B-Tree 差很多。
-
另外,B+Tree 更適合外存索引,原因和內節點出度 d 有關。從上面分析可以看到,d 越大索引的性能越好,而出度的上限取決於節點內 key 和 data 的大小,由於 B+Tree 內節點去掉了 data 域,因此可以擁有更大的出度,擁有更好的性能。(詳細見本部分第 3 點)
B-Tree 與 B+Tree 的對比
-
根據 B-Tree 和 B+Tree 的結構,我們可以發現 B+樹相比於 B 樹,在文件系統或者數據庫系統當中,更有優勢,原因如下:
1. B+樹的磁盤讀寫代價更低
B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對 B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說 I/O 讀寫次數也就降低了。
2. B+樹的查詢效率更加穩定
由於內部結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
3. B+樹更有利於對數據庫的掃描
B 樹在提高了磁盤 IO 性能的同時並沒有解決元素遍歷的效率低下的問題,而 B+樹只需要遍歷葉子節點就可以解決對全部關鍵字信息的掃描,所以對於數據庫中頻繁使用的 range query,B+樹有着更高的性能。
MySQL 索引的實現
-
在 MySQL 中,索引屬於存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,本部分主要討論 MyISAM 和 InnoDB 兩個存儲引擎的索引實現方式。
MyISAM 索引的實現
1. 主鍵索引
MyISAM 引擎使用 B+Tree 作為索引結構,葉節點的 data 域存放的是數據記錄的地址。下圖是 MyISAM 索引的原理圖:
-
這里設表一共有三列,假設我們以 Col1 為主鍵,則上圖是一個 MyISAM 表的主索引(Primary key)示意。可以看出 MyISAM 的索引文件僅僅保存數據記錄的地址。
2. 輔助索引
在
MyISAM
中,主索引和輔助索引(Secondary key
)在結構上沒有任何區別,只是主索引要求 key 是唯一的,而輔助索引的 key 可以重復。如果我們在 Col2 上建立一個輔助索引,則此索引的結構如下圖所示:
-
同樣也是一顆 B+Tree,data 域保存數據記錄的地址。因此,MyISAM 中索引檢索的算法為首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,則取出其 data 域的值,然后以 data 域的值為地址,讀取相應數據記錄。
-
MyISAM 的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與 InnoDB 的聚集索引區分。
InnoDB 索引的實現
-
雖然 InnoDB 也使用 B+Tree 作為索引結構,但具體實現方式卻不相同。
1. 主鍵索引
與 MyISAM 第一個重大區別是 InnoDB 的數據文件本身就是索引文件。從上文知道,MyISAM 索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在 InnoDB 中,表數據文件本身就是按 B+Tree 組織的一個索引結構,這棵樹的葉節點 data 域保存了完整的數據記錄。這個索引的 key 是數據表的主鍵,因此 InnoDB 表數據文件本身就是主索引。
上圖是 InnoDB 主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因為 InnoDB 的數據文件本身要按主鍵聚集,所以 InnoDB 要求表必須有主鍵(MyISAM 可以沒有),如果沒有顯式指定,則 MySQL 系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則 MySQL 自動為 InnoDB 表生成一個隱含字段作為主鍵,這個字段長度為 6 個字節,類型為長整形。
2. 輔助索引
第二個與 MyISAM 索引的不同是 InnoDB 的輔助索引 data 域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB 的所有輔助索引都引用主鍵作為 data 域。例如,下圖為定義在 Col3 上的一個輔助索引:
-
這里以英文字符的 ASCII 碼作為比較准則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
-
InnoDB 表是基於聚簇索引建立的。因此 InnoDB 的索引能提供一種非常快速的主鍵查找性能。不過,它的輔助索引也會包含主鍵列,所以,如果主鍵使用過長的字段,將會導致其他輔助索變得更大。如果想在表上定義 、很多索引,則爭取盡量把主鍵定義得小一些。InnoDB 不會壓縮索引。
-
更多內容請關注微信公眾號【碼猿技術專欄】
-