MySQL和Lucene均可以對數據構建索引並經過索引查詢數據,一個是關係型數據庫,一個是構建搜索引擎(Solr、ElasticSearch)的核心類庫。二者的索引(index)有什麼區別呢?之前寫過一篇《Solr與MySQL查詢性能對比》,只是簡單的對比了下查詢性能,對於內部原理卻沒有解釋,本文簡單分析下二者的索引區別。html
MySQL索引實現mysql
在MySQL中,索引屬於存儲引擎級別的概念,不一樣存儲引擎對索引的實現方式是不一樣的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。算法
MyISAM索引實現sql
MyISAM引擎使用B+Tree做爲索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:數據庫
圖1是一個MyISAM表的主索引(Primary key)示意。能夠看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是惟一的,而輔助索引的key能夠重複。B+Tree的全部葉子節點包含全部關鍵字且是按照升序排列的。apache
MyISAM表的索引和數據是分離的,索引保存在」表名.MYI」文件內,而數據保存在「表名.MYD」文件內。數組
MyISAM的索引方式也叫作「非彙集」的,之因此這麼稱呼是爲了與InnoDB的彙集索引區分。緩存
InnoDB索引實現數據結構
雖然InnoDB也使用B+Tree做爲索引結構,但具體實現方式卻與MyISAM大相徑庭。post
第一個重大區別是InnoDB的數據文件自己就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件自己就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以InnoDB表數據文件自己就是主索引。
圖2是InnoDB主索引(同時也是數據文件)的示意圖,能夠看到葉節點包含了完整的數據記錄。這種索引叫作彙集索引。由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。
第二個與MyISAM索引的不一樣是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的全部輔助索引都引用主鍵做爲data域。例如,圖3爲定義在Col3上的一個輔助索引:
這裏以英文字符的ASCII碼做爲比較準則。彙集索引這種實現方式使得按主鍵的搜索十分高效,可是輔助索引搜索須要檢索兩遍索引:首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄。
瞭解不一樣存儲引擎的索引實現方式對於正確使用和優化索引都很是有幫助,例如知道了InnoDB的索引實現後,就很容易明白爲何不建議使用過長的字段做爲主鍵,由於全部輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段做爲主鍵在InnoDB中不是個好主意,由於InnoDB數據文件自己是一顆B+Tree,非單調的主鍵會形成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段做爲主鍵則是一個很好的選擇。
講MySQL索引的實現的文章不少,以上也是參考了《MySQL索引背後的數據結構及算法原理》,如今來看看Lucene的索引原理。
Lucene索引實現
Lucene的索引不是B+Tree組織的,而是倒排索引,Lucene的倒排索引由Term index,Team Dictionary和Posting List組成。
有倒排索引(invertedindex)就有正排索引(forwardindex),正排索引就是文檔(Document)和它的字段Fields正向對應的關係:
DocID |
name |
sex |
age |
1 |
jack |
男 |
18 |
2 |
lucy |
女 |
17 |
3 |
peter |
男 |
17 |
倒排索引是字段Field和擁有這個Field的文檔對應的關係:
Sex字段:
男 |
[1,3] |
女 |
[2] |
Age字段:
18 |
[1] |
17 |
[2,3] |
Jack,lucy或者17,18這些叫作term,而[1,3]就是posting list。Posting list就是一個int型的數組,存儲了全部符合某個term的文檔id。那麼什麼是Term index和Term dictionary?
如上,假設name字段有不少個term,好比:Carla,Sara,Elin,Ada,Patty,Kate,Selena
若是按照這樣的順序排列,找出某個特定的term必定很慢,由於term沒有排序,須要所有過濾一遍才能找出特定的term。排序以後就變成了:Ada,Carla,Elin,Kate,Patty,Sara,Selena
這樣就能夠用二分查找的方式,比全遍歷更快地找出目標的term。如何組織這些term的方式就是 Term dictionary,意思就是term的字典。有了Term dictionary以後,就能夠用比較少的比較次數和磁盤讀次數查找目標。可是磁盤的隨機讀操做仍然是很是昂貴的,因此儘可能少的讀磁盤,有必要把一些數據緩存到內存裏。可是整個Term dictionary自己又太大了,沒法完整地放到內存裏。因而就有了Term index。Term index有點像一本字典的大的章節表。好比:
A開頭的term ……………. Xxx頁
C開頭的term ……………. Xxx頁
E開頭的term ……………. Xxx頁
若是全部的term都是英文字符的話,可能這個term index就真的是26個英文字符表構成的了。可是實際的狀況是,term未必都是英文字符,term能夠是任意的byte數組。並且26個英文字符也未必是每個字符都有均等的term,好比x字符開頭的term可能一個都沒有,而s開頭的term又特別多。實際的term index是一棵trie 樹:
上圖例子是一個包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的trie樹。這棵樹不會包含全部的term,它包含的是term的一些前綴。經過term index能夠快速地定位到term dictionary的某個offset,而後從這個位置再日後順序查找。再加上一些壓縮技術(想了解更多,搜索 Lucene Finite State Transducers),Term index的尺寸能夠只有全部term的尺寸的幾十分之一,使得用內存緩存整個term index變成可能。總體上來講就是這樣的效果:
由Term index到Term Dictionary,再到Posting List,經過某個字段的關鍵字去查詢結果的過程就比較清楚了,經過多個關鍵字的Posting List進行AND或者OR進行交集或者並集的查詢也簡單了。
對比MySQL的B+Tree索引原理,能夠發現:
1)Lucene的Term index和Term Dictionary其實對應的就是MySQL的B+Tree的功能,爲關鍵字key提供索引。Lucene的inverted index能夠比MySQL的b-tree檢索更快。
2)Term index在內存中是以FST(finite state transducers)的形式保存的,其特色是很是節省內存。因此Lucene搜索一個關鍵字key的速度是很是快的,而MySQL的B+Tree須要讀磁盤比較。
3)Term dictionary在磁盤上是以分block的方式保存的,一個block內部利用公共前綴壓縮,好比都是Ab開頭的單詞就能夠把Ab省去。這樣Term dictionary能夠比B-tree更節約磁盤空間。
4)Lucene對不一樣的數據類型採用了不一樣的索引方式,上面分析是針對field爲字符串的,好比針對int,有TrieIntField類型,針對經緯度,就能夠用GeoHash編碼。
5)在 Mysql中給兩個字段獨立創建的索引沒法聯合起來使用,必須對聯合查詢的場景創建複合索引,而Lucene能夠任何AND或者OR組合使用索引進行檢索。
參考:
《MySQL索引背後的數據結構及算法原理》: http://blog.codinglabs.org/articles/theory-of-mysql-index.html
http://stackoverflow.com/questions/4628571/solr-date-field-tdate-vs-date
http://lucene.apache.org/core/