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/