Java|正排索引与倒排索引的理解

前言 最近在学习调研ElasticSearch,ES是一款热度较高的开源搜索服务器,能够提供近实时的数据全文检索功能,而实现检索功能一个其中较为重要的思想就是使用倒排索引,之所以成为倒排,与我们关系型数据库如Mysql的正排索引的区别在哪?在这篇文章总结一下我对两种索引的理解。
正文 正排索引 拿Mysql Innodb的聚簇索引来说,如下图所示,一个极简版(无页属性)的B+树索引结构大概是这样,叶子节点存放完整数据,非叶子节点存放建立对应聚簇索引对应的字段(主键),一条可以使用到聚簇索引的Sql,会依次从上到下进行B+树的查找直到字段一致;

CREATE TABLE user_info ( id int, name varchar(16), hobby varchar(256) );

Java|正排索引与倒排索引的理解
文章图片

而对应非聚簇索引只是叶子节点的内容存放的是该表的主键信息,查询的顺序则是 先通过非聚簇索引的字段找到叶子节点中一致的 单个或者多个主键id,再使用这些主键id进行回表,最终获得对应的完整实体数据。
如果我们看上面在mysql中表的hobby爱好字段,如果我们有业务需求:根据用户爱好关键字如“篮球”去查询对应用户列表,我们怎么做,只能是写个字符串的like sql,全表扫描的逻辑。
SELECT * FROM user_info WHERE hobby LIKE '%篮球%';

即使我们对hobby字段创建了普通索引,在Innodb引擎下,在查询中想使用字符串类型的索引也只能走最左前缀索引的逻辑,即 LIKE ‘篮球%’。幸好Innodb在5.6版本后支持了全文索引full text,在创建完全文索引后,查询中使用MATCH、AGAINST就能够使用全文索引了,比全表扫B+树效率会高很多,但是对应全文索引会占据相当的磁盘空间。全文索引与我们要说的倒排索引就是一个意思了。
SELECT * FROM user_info WHERE MATCH (hobby) AGAINST ('篮球');

倒排索引 相比B+树的正排索引,如果我们对hobby字段建立了索引,他的倒排索引极简的数据格式如下。创建倒排索引的field,会通过分词器根据语义将字段中的field分成一个一个对应的词索引(term index),构成该类型数据的全部词索引集合(term dictionary),如“喜欢篮球、唱歌”会被分成 “篮球”和“唱歌”两个term index;第二列是含有这些term index对应的文档Id(documentId),这个数据可以帮助我们最终溯源到完整实体数据;第三列则是对应term index在该文档字段中的位置,0表示在开头的位置,这个可以帮助标注检索出来数据的高亮信息。
Java|正排索引与倒排索引的理解
文章图片

倒排索引与分词
如何输入一个文档数据后创建对应的倒排索引,如 {“id”:1,“name”:“张三”,“hobby”:“喜欢篮球、唱歌”}。拿ES来说,可以预先设置字段为string及对应的分词器,会针对hobby这个字段进行预处理,一整句话在经过下面的三个分词步骤,会被分成多个对应的词索引(term index),每个term index对应的位置、文档id也都会生成,添加到上述的数据结构中。
  1. Character Filters:针对原始文本进行处理,比如去除html标签
  2. Tokenizer:将原始文本按照一定规则切分为单词
  3. Token Filters:针对Tokenizer处理的单词进行再加工,比如转小写、删除或增新等处理
针对不同的文本内容,我们可以使用不同的分词器甚至是自定义分词器,如ES的分词器:Standard Analyzer(默认分词器,按词切分,处理标点)、Simple Analyzer(不是子母的字符会被忽略切为切分点)、Whitespace Analyzer(基于空格的分词器)、IKAnalyzer(比较流行的中文分词器);Mysql的全文索引也有对应的中文分词器ngram。
两个索引查询顺序 通过上面的描述,我们可以知道在使用正排索引、倒排索引时的一个大概的查询顺序
Java|正排索引与倒排索引的理解
文章图片

倒排索引思想的应用 之前有接过一个需求描述:我们要对不同城市、年级、学期、设备的用户设定不同的内容展现规则,这几个属性是可以设置为空的,最后如果一个用户的属性匹配到了多个规则,那么就需要根据这几个属性的权重来打分,取分最高的规则,如我们配置规则如下:那么一个上海小学一年级春季的用户过来就匹配到了两个规则,然后根据这两个规则的属性进行权重值打分计算即可,最后选择显示A或B。
city grade term device rule
上海 春季 显示A
上海 小学一年级 显示B
上面在进行数据查询时,sql如下,在这个or的过程的思想就类似于“倒排索引中分词+查找所有含有该词索引的文档数据”(只是这个词索引是早已确定好的),然后在搜索到多条记录后进行权重值计算(类似ES中的检索关联程度打分)。
SELECT * FROM tb_rules WHERE (city = '上海' OR grade = '小学一年级' OR term = '春季')

总结 【Java|正排索引与倒排索引的理解】通过这篇文章,我们了解了正排索引、倒排索引的思想及简要的实现方式,希望能通过这些不一样的原理能给工作中的问题带来不同解决的思路。

    推荐阅读