mysql的B+樹索引 查找使用了二分查找,redis 跳錶也使用了二分查找法,kafka查詢消息日誌也使用了二分查找法,二分查找法時間複雜度O(logn);html
參考:redis的索引底層的 跳錶原理 實現 聊聊Mysql索引和redis跳錶 ---redis的跳錶原理 時間複雜度O(logn)(阿里)mysql
參考:kafka如何實現高併發存儲-如何找到一條須要消費的數據(阿里)redis
參考:二分查找法:各類排序算法的時間複雜度和空間複雜度(阿里)算法
在MySQL中,主要有四種類型的索引,分別爲:B-Tree索引,Hash索引,Fulltext索引(MyISAM 表)和R-Tree索引,本文講的是B-Tree索引。sql
後面的索引原理必定要看,過重要了,阿里兩我的都問這個mysql的索引原理數據庫
mysql使用了 B+索引:數組
B樹:有序數組+平衡多叉樹;
B+樹:有序數組鏈表+平衡多叉樹;緩存
(a) Inodb存儲引擎 默認是 B+Tree索引數據結構
(b) MyISAM 存儲引擎 默認是Fulltext索引;併發
(c)Memory 存儲引擎 默認 Hash索引;
Hash索引
mysql中,只有Memory(Memory表只存在內存中,斷電會消失,適用於臨時表)存儲引擎顯示支持Hash索引,是Memory表的默認索引類型,儘管Memory表也可使用B+Tree索引。Hash索引把數據以hash形式組織起來,所以當查找某一條記錄的時候,速度很是快。可是由於hash結構,每一個鍵只對應一個值,並且是散列的方式分佈。因此它並不支持範圍查找和排序等功能。
B+Tree索引
B+Tree是mysql使用最頻繁的一個索引數據結構,是Inodb和Myisam存儲引擎模式的索引類型。相對Hash索引,B+Tree在查找單條記錄的速度比不上Hash索引,可是由於更適合排序等操做,因此它更受歡迎。畢竟不可能只對數據庫進行單條記錄的操做。
帶順序訪問指針的B+Tree
B+Tree全部索引數據都在葉子節點上,而且增長了順序訪問指針,每一個葉子節點都有指向相鄰葉子節點的指針。
這樣作是爲了提升區間效率,例如查詢key爲從18到49的全部數據記錄,當找到18後,只要順着節點和指針順序遍歷就能夠以此向訪問到全部數據節點,極大提升了區間查詢效率。
大大減小磁盤I/O讀取
數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點須要一次I/O就能夠徹底載入。
索引(Index)是幫助數據庫高效獲取數據的數據結構。索引是在基於數據庫表建立的,它包含一個表中某些列的值以及記錄對應的地址,而且把這些值存儲在一個數據結構中。最多見的就是使用哈希表、B+樹做爲索引。
通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。提及加速查詢,就不得不提到索引了。
咱們知道,數據庫查詢是數據庫最主要的功能之一。而查詢速度固然是越快越好。而當數據量愈來愈大的時候,查詢花費的時間會隨之增加。而索引,能夠加速數據的查詢。由於索引是有序排列的。
舉個例子來講,假設咱們有一個數據庫表Employee,這個表分別有三個字段:name,age,address。假設表中有1000條記錄。
假如沒有使用索引,當咱們查詢名爲「Jesus」的僱員的時候,即調用:
select name,age,address from Employee where name = 'Jesus';
此時數據庫不得不在Employee表中對這1000條記錄一條一條的進行判斷name字段是否爲「Jesus」。這也就是所謂的全表掃描。
而當咱們在Employee表上的name字段上建立索引時,當咱們查詢名爲「Jesus」的僱員時,會經過索引查找去查詢名爲「Jesus」的僱員,由於該索引已經按照字母順序排列,所以要查找名爲「Jesus」的記錄時會快不少,由於名字首字母爲「J」的僱員都是排列在一塊兒的。經過該索引,能獲取到表中對應的記錄。
假設索引(索引是一種數據結構)是鏈表結構。每一個節點存儲的是關鍵字字段(這個例子中對應的是name屬性)以及該關鍵字字段在數據庫表的對應的記錄的地址。而這些節點是根據name屬性排序的(即根據字母順序排序)。所以,當咱們執行上面說的查找名爲「Jesus」的sql語句時,數據庫會經過該索引來查詢,由於該鏈表是有序排列的,在咱們找到第一個name屬性爲「Jesus」的節點後,繼續日後找,當遇到name屬性不爲「Jesus」的節點時,就無需再日後查找了,由於節點是根據name屬性有序排列的啊。假設第一個name=「Jesus」的節點是第499個節點,最後一個name=「Jesus」的節點是第500個節點,那麼只須要遍歷501個節點就能夠了。當發現第501個節點的name字段不爲「Jesus」,後面的499個節點也就無需遍歷了。經過索引,咱們就找到了name爲「Jesus」的節點,而經過該節點的另外一個屬性(關鍵字字段在數據庫表的對應的記錄的地址),咱們就能獲取到Employee表中知足條件name=「Jesus」的記錄了。
經過使用索引,查詢判斷的次數就從1000次縮小到了501次了。起到了加速了查詢效率。但實際上數據庫中索引的結構,並非鏈表結構。
數據庫中實際使用的索引並不會是鏈表結構,由於效率過低了。
咱們知道鏈表的查詢效率是O(n)。就像上面的例子,遍歷了501次才找到第一條符合條件的記錄,這是很低效的。而咱們知道,數組+二分查找的效率是O(lgn),可是數組的插入元素以及刪除元素的效率很低,所以使用數組作爲索引結構並不合適。
另外,在選擇數據庫索引的結構的時候,要考慮到另外一個問題。索引是存在於磁盤中,當索引很是大的時候,達到幾個G的時候,沒法一次加載到內存中。
考慮到上面兩個因素,數據庫中索引使用的是樹形結構。
有這麼幾種樹:
B-Tree B+-Tree B*-Tree
首先要明白三種樹名中的「-」起到的是分隔的做用,並非「減」的意思。
所以正確的翻譯應該是B樹,B+樹,B*樹
。而不是B-樹,B+樹,B*樹
。所以,當你聽到別人說「B減樹」的時候,要明白它指的是B-Tree。即B樹和B-樹是同一種樹。
爲何要強調上面這一點呢,由於有的博文中寫的是:B樹是二叉樹,B-樹是多路搜索樹。
然而B樹和B-樹都是指B-Tree。引用維基百科上的話:
B-tree
Not to be confused with Binary tree.
也就是說,B-Tree並非Binart tree。B-Tree的中文名是平衡多路搜索樹。
(B樹的相關介紹在下面)
樹形結構是計算機系統裏最重要的數據結構。
咱們知道,二叉樹的查找的時間複雜度是O(log2N),其查找效率與深度有關,而普通的二叉樹可能因爲內部節點排列問題退化成鏈表,這樣查找效率就會很低。所以平衡二叉樹是更好的選擇,由於它保持平衡,即經過旋轉調整結構保持最小的深度。其查找的時間複雜度也是O(log2N)。
但實際上,數據庫中索引的結構也並不是AVL樹或更優秀的紅黑樹,儘管它的查詢的時間複雜度很低。
以前說了平衡樹的查找時間複雜度是O(log2N),已經很不錯了,但仍是不適合做爲索引結構。那麼確定是有一種更適合做爲索引的數據結構。那麼這個更適合做爲索引的數據結構,難道是查找的時間複雜度更低嗎?並非。這種做爲索引的數據結構的查找的時間複雜度也近似O(log2N)。
那爲何平衡二叉樹不適合做爲索引呢?
索引是存在於索引文件中,是存在於磁盤中的。由於索引一般是很大的,所以沒法一次將所有索引加載到內存當中,所以每次只能從磁盤中讀取一個磁盤頁的數據到內存中。而這個磁盤的讀取的速度較內存中的讀取速度而言是差了好幾個級別。
注意,咱們說的平衡二叉樹結構,指的是邏輯結構上的平衡二叉樹,其物理實現是數組。而後因爲在邏輯結構上相近的節點在物理結構上可能會差很遠。所以,每次讀取的磁盤頁的數據中有許可能是用不上的。所以,查找過程當中要進行許屢次的磁盤讀取操做。
而適合做爲索引的結構應該是儘量少的執行磁盤IO操做,由於執行磁盤IO操做很是的耗時。所以,平衡二叉樹並不適合做爲索引結構。
平衡二叉樹不適合做爲索引。那麼什麼才適合做爲索引——B樹。
平衡二叉樹沒能充分利用磁盤預讀功能,而B樹是爲了充分利用磁盤預讀功能來而建立的一種數據結構,也就是說B樹就是爲了做爲索引才被髮明出來的的。
來看看關於「局部性原理與磁盤預讀」的知識:
局部性原理與磁盤預讀: 因爲存儲介質的特性,磁盤自己存取就比主存慢不少,再加上機械運動耗費,磁盤的存取速度每每是主存的幾百分分之一,所以爲了提升效率,要儘可能減小磁盤I/O。爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這樣作的理論依據是計算機科學中著名的局部性原理: 當一個數據被用到時,其附近的數據也一般會立刻被使用。 程序運行期間所須要的數據一般比較集中。 因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升I/O效率。
搞清楚上面的意思。磁盤預讀是具體實現,其理論依據是局部性原理。
爲何說紅黑樹沒能充分利用磁盤預讀功能,引用一篇博文的一段話:
紅黑樹這種結構,h明顯要深的多。因爲邏輯上很近的節點(父子)物理上可能很遠,沒法利用局部性,因此紅黑樹的I/O漸進複雜度也爲O(h),效率明顯比B-Tree差不少。
也就是說,使用紅黑樹(平衡二叉樹)結構的話,每次磁盤預讀中的不少數據是用不上的數據。所以,它沒能利用好磁盤預讀的提供的數據。而後又因爲深度大(較B樹而言),因此進行的磁盤IO操做更多。
B樹的每一個節點能夠存儲多個關鍵字,它將節點大小設置爲磁盤頁的大小,充分利用了磁盤預讀的功能。每次讀取磁盤頁時就會讀取一整個節點。也正因每一個節點存儲着很是多個關鍵字,樹的深度就會很是的小。進而要執行的磁盤讀取操做次數就會很是少,更多的是在內存中對讀取進來的數據進行查找。
B樹的查詢,主要發生在內存中,而平衡二叉樹的查詢,則是發生在磁盤讀取中。所以,雖然B樹查詢查詢的次數不比平衡二叉樹的次數少,可是相比起磁盤IO速度,內存中比較的耗時就能夠忽略不計了。所以,B樹更適合做爲索引。
比B樹更適合做爲索引的結構是B+樹。MySQL中也是使用B+樹做爲索引。它是B樹的變種,所以是基於B樹來改進的。爲何B+樹會比B樹更加優秀呢?
B樹:有序數組+平衡多叉樹;
B+樹:有序數組鏈表+平衡多叉樹;
B+樹的關鍵字所有存放在葉子節點中,非葉子節點用來作索引,而葉子節點中有一個指針指向一下個葉子節點。作這個優化的目的是爲了提升區間訪問的性能。而正是這個特性決定了B+樹更適合用來存儲外部數據。
引用一段話:
走進搜索引擎的做者梁斌老師針對B樹、B+樹給出了他的意見(爲了真實性,特引用其原話,未做任何改動): 「B+樹還有一個最大的好處,方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持range-query很是方便,而B樹不支持。這是數據庫選用B+樹的最主要緣由。 好比要查 5-10之間的,B+樹一把到5這個標記,再一把到10,而後串起來就好了,B樹就很是麻煩。B樹的好處,就是成功查詢特別有利,由於樹的高度整體要比B+樹矮。不成功的狀況下,B樹也比B+樹稍稍佔一點點便宜。 B樹好比你的例子中查,17的話,一把就獲得結果了, 有不少基於頻率的搜索是選用B樹,越頻繁query的結點越往根上走,前提是須要對query作統計,並且要對key作一些變化。 另外B樹也好B+樹也好,根或者上面幾層由於被反覆query,因此這幾塊基本都在內存中,不會出現讀磁盤IO,通常已啓動的時候,就會主動換入內存。」
舉個例子來對比。
B樹:
好比說,咱們要查找關鍵字範圍在3到7的關鍵字,在找到第一個符合條件的數字3後,訪問完第一個關鍵字所在的塊後,得遍歷這個B樹,獲取下一個塊,直到遇到一個不符合條件的關鍵字。遍歷的過程是比較複雜的。
B+樹(葉節點保存數據,其餘的節點 所有存放索引):
相比之下,B+樹的基於範圍的查詢簡潔不少。因爲葉子節點有指向下一個葉子節點的指針,所以從塊1到塊2的訪問,經過塊1指向塊2的指針便可。從塊2到塊3也是經過一個指針便可。
引用一篇博文中網友評論的一段話:
數據庫索引採用B+樹的主要緣由是B樹在提升了磁盤IO性能的同時並無解決元素遍歷的效率低下的問題。正是爲了解決這個問題,B+樹應運而生。
B+樹只要遍歷葉子節點就能夠實現整棵樹的遍歷。並且在數據庫中基於範圍的查詢是很是頻繁的,而B樹不支持這樣的操做(或者說效率過低)。
正如上面所說,在數據庫中基於範圍的查詢是很是頻繁的,所以MySQL最終選擇的索引結構是B+樹而不是B樹。
一 索引原理
索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查字典,查火車車次,飛機航班等
本質都是:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。
數據庫也是同樣,但顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的。而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。
二 磁盤IO與預讀
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。
3、索引的數據結構
任何一種數據結構都不是憑空產生的,必定會有它的背景和使用場景,咱們如今總結一下,咱們須要這種數據結構可以作些什麼,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那麼咱們就想到若是一個高度可控的多路搜索樹是否能知足需求呢?就這樣,b+樹應運而生。
如上圖,是一顆b+樹,關於b+樹的定義能夠參見B+樹,這裏只說一些重點,淺藍色的塊咱們稱之爲一個磁盤塊,能夠看到每一個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P一、P二、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如1七、35並不真實存在於數據表中。
###b+樹的查找過程
如圖所示,若是要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找肯定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,經過指針加載磁盤塊8到內存,發生第三次IO,同時內存中作二分查找找到29,結束查詢,總計三次IO。真實的狀況是,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。
###b+樹性質
1.索引字段要儘可能的小:經過上面的分析,咱們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N必定的狀況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,若是數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲何每一個數據項,即索引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。這也是爲何b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低,致使樹增高。當數據項等於1時將會退化成線性表。
2.索引的最左匹配特性(即從左往右匹配):當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候,b+數是按照從左到右的順序來創建搜索樹的,好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性。
這也是常常考察的,好比 我定義了 A,B,C的聯合索引,若是 我只傳遞了 A,B 能走索引嗎?答案是能,由於最左側原理(百度問過)
MySQL從3.23.23版開始支持全文索引和全文檢索,fulltext索引僅可用於 MyISAM 表;他們能夠從CHAR、VARCHAR或TEXT列中做爲CREATE TABLE語句的一部分被建立,或是隨後使用ALTER TABLE 或CREATE INDEX被添加。////對於較大的數據集,將你的資料輸入一個沒有FULLTEXT索引的表中,而後建立索引,其速度比把資料輸入現有FULLTEXT索引的速度更爲快。不過切記對於大容量的數據表,生成全文索引是一個很是消耗時間很是消耗硬盤空間的作法。
文本字段上的普通索引只能加快對出如今字段內容最前面的字符串(也就是字段內容開頭的字符)進行檢索操做。若是字段裏存放的是由幾個、甚至是多個單詞構成的較大段文字,普通索引就沒什麼做用了。這種檢索每每以LIKE %word%的形式出現,這對MySQL來講很複雜,若是須要處理的數據量很大,響應時間就會很長。
這類場合正是全文索引(full-text index)能夠大顯身手的地方。在生成這種類型的索引時,MySQL將把在文本中出現的全部單詞建立爲一份清單,查詢操做將根據這份清單去檢索有關的數據記錄。全文索引便可以隨數據表一同建立,也能夠等往後有必要時再使用下面這條命令添加:
ALTER TABLE table_name ADD FULLTEXT(column1, column2)
有了全文索引,就能夠用SELECT查詢命令去檢索那些包含着一個或多個給定單詞的數據記錄了。下面是這類查詢命令的基本語法:
SELECT * FROM table_name
WHERE MATCH(column1, column2) AGAINST('word1', 'word2', 'word3')
上面這條命令將把column1和column2字段裏有word一、word2和word3的數據記錄所有查詢出來。
1,不要濫用索引
①,索引提升查詢速度,卻會下降更新表的速度,由於更新表時,mysql不只要更新數據,保存數據,還要更新索引,保存索引
②,索引會佔用磁盤空間
2,索引不會包含含有NULL值的列
複合索引只要有一列含有NULL值,那麼這一列對於此符合索引就是無效的,所以咱們在設計數據庫設計時不要讓字段的默認值爲NULL。
3,MySQL查詢只是用一個索引
若是where字句中使用了索引的話,那麼order by中的列是不會使用索引的
4,like
like '%aaa%'不會使用索引而like "aaa%"可使用索引
Mysql支持不少數據類型,選擇合適的數據類型存儲數據對性能有很大的影響。
(1)越小的數據類型一般更好:越小的數據類型一般在磁盤、內存和cpu緩存中都須要更少的空間,處理起來更快。
(2)簡單的數據類型更好:整形數據比起字符,處理開銷更小,由於字符串的比較更復雜。在MySQL中,應用內置的日期和時間數據類型,而不是字符串來存儲時間;以及用整形數據存儲IP地址。
(3)儘可能避免NULL:應該制定列爲NOT NULL,除非你想存儲NULL。在MySQL中,含有空值的列很難進行查詢優化,由於他們使得索引、索引的統計信息以及比較運算更加複雜。
1,INDEX(普通索引):ALTER TABLE 'table_name' ADD INDEX index_name('col')
最基本的索引,沒有任何限制
2,UNIQUE(惟一索引):ALTER TABLE 'table_name' ADD UNIQUE('col')
與「普通索引」相似,不一樣的就是:索引列的值必須惟一,但容許有空值。
3,PRIMARY KEY(主鍵索引):ALTER TABLE 'table_name' ADD PRIMARY KEY('col')
是一種特殊的惟一索引,不容許有空值。
4,FULLTEXT(全文索引):ALTER TABLE 'table_name' ADD FULLTEXT('col')
僅可用於MyISAM和InoDB,針對較大的數據,生成全文索引很耗時耗空間
組合索引:ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3')
爲了更多的提升mysql效率可創建組合索引,遵循「最左前綴」原則。建立複合索引應該將最經常使用(頻率)作限制條件的列放在最左邊,一次遞減。組合索引最左字段用in是能夠用到索引的。至關於創建了col1,col1col2,col1col2col3三個索引
1.MySQL索引背後的數據結構及算法原理(超讚的文章,2011年寫的文章,厲害……)
2.從B樹、B+樹、B*樹談到R 樹(這篇文章做者也是好厲害,其博客訪問量達千萬)
3.淺談算法和數據結構: 十 平衡查找樹之B樹,而這篇博文裏有B樹和B+樹插入元素的過程GIF圖,超讚,有助於對B樹和B+樹的理解!