传统的关系型数据库例如 MySQL 在使用 like 关键词进行模糊搜索时存在以下几个问题:
1、只能走全表扫描,性能较差;
2、只能按照首尾位进行匹配,无法实现复杂的匹配需求,例如按照 like "%大猫老师%"
是无法检索出只包含大猫
或者老师
的数据记录的;
3、无法针对搜索结果进行打分。
为了应对海量文档的全文检索,ElasticSearch 应运而生。在这篇文章中,我们将会简析 ElasticSearch 的使用方法,帮助大家迅速入门搜索引擎。
1. 基本概念
术语名称 | 解释 |
---|---|
倒排索引 | 将文档内容中的单词作为索引,将包含该词的文档 ID 作为记录的结构 |
Lucene | Apache 开源的全文检索引擎工具包,提供了完整的查询引擎和索引引擎,部分语种文本分析引擎 |
ElasticSearch | 开源的实时分布式搜索分析引擎,内部使用 Lucene 做索引与搜索。它提供"准实时搜索"能力,并且能动态集群规模,弹性扩容 |
是不是还是有点懵逼?没事,下面的文章里我们会更加细致地介绍各个概念。
2. 什么是倒排索引
假设我们向搜索引擎存入两个文档:
文档 ID | 文档内容 |
---|---|
1 | A quick brown fox jumps over the lazy dog. |
2 | A handsome big cat teaches ElasticSearch. |
首先,文本分析器将文本进行分词,拆解成多个不同的语素(tokens),大部分情况下一个语素就是一个单词;随后对这些分词进行归一化,提取出词根,例如 foxes 可能被提取为词根 fox,jumps 被提取出 jump 等,最后形成的倒排索引表结构类似于下面的这张表:
词根 term | 记录表 posting list |
---|---|
big | 2 |
brown | 1 |
cat | 2 |
elasticSearch | 2 |
fox | 1 |
jump | 1 |
… | … |
倒排索引中每个词根对应的记录表 posting list 除了记录包含了该词根的文档 ID 列表之外,也有可能会包含该词根的词频、在文档中的偏移量等信息。
另外,可以看到倒排索引表针对关键词进行了排序,这样搜索某个关键词时可以通过二分查找降低查询次数。
Lucene 和 ElasticSearch 对倒排索引的数据结构进行了相应的优化。其底层实现我们会放在后面的文章中深入解析。
用户利用 Quick fox
对文档进行检索时,文本分析器会对搜索使用的关键词进行分词和解析,形成 quick 和 fox 两个词根,并根据上面的倒排索引表迅速定位到编号为 1 的文档,即 A quick brown fox jumps over the lazy dog.
3. ElasticSearch 中的基本概念
术语名称 | MySQL 中的类似概念 | 解释 |
---|---|---|
索引 _index | 表 table | 一般情况下把具有相同业务含义和数据结构的文档放在同一个索引下 |
类型 _type | 无 | 在 7.0.x 版本中已经废弃掉的一个概念,可以不做深入了解 |
文档 document | 行 row | 包含键值对的 JSON 对象,每个文档由多个字段组成,并有唯一的 ID |
字段 field | 字段 column | 文档所包含的各个键值对中的键 |
映射 mapping | 索引 index | 映射决定了各个字段在搜索引擎中会以什么样的方式被存储和索引;不同于 MySQL 中只需要为部分字段建立索引, ElasticSearch 会为每个字段建立各自的映射关系 |
Query DSL | SQL | ElasticSearch 用于对文档进行 CRUD 的领域特定语言 |
相关性评分 _score | 无 | ElasticSearch 用于对文档的评分,相关性评分越高,说明该文档与关键词的相关性越高 |
注意:实际上,在 Elasticsearch 中,我们的数据是被存储和索引在 分片 中,而一个索引仅仅是逻辑上的命名空间,这个命名空间由一个或者多个分片组合在一起。 然而,这是一个内部细节,我们的应用程序根本不应该关心分片,对于应用程序而言,只需知道文档位于一个 索引 内。
4. 开始搜索吧!
查询语句包含两类不同的查询方式:
- Query Context:评估文档与关键词的相似程度,按照相似程度进行打分;
- Filter Context: 评估文档能否满足筛选条件的要求,只区分文档是否满足要求,不对文档相关性评分有影响。
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2015-01-01" }}}
],
"minimum_should_match" : 1
}
}
}
以上面的查询语句为例,最终筛选出的文档满足:
1、status 字段的值一定为 published;
2、publish_date 字段的值一定在2015年1月1日之后;
3、title 字段的值尽可能与 Search 关键词相匹配,content 字段的值尽可能与 Elasticsearch 相匹配,相关程度越高,文档评分越高;
4、should 关键词下的条件至少匹配 1 条。
4.1 全文查询
全文查询是搜索引擎中最广泛使用的功能之一,针对映射关系为 text 的字段可以通过全文查询进行模糊搜索。
查询类型 | 适用场景 |
---|---|
match_all | 直接返回当前索引下的所有文档 |
match | 返回对应字段下与关键词匹配的文档并按相关性打分,例如搜索医院名称字段下与浙大最匹配的所有文档 |
intervals | 按照指定的关键词顺序检索文档,例如搜索菜单字段下匹配“日本料理”“清酒”的文档,其中“日本料理”必须出现在“清酒”的前面 |
match_bool_prefix | 例如输入“quick brown f”,检索出严格包含“quick”“brown”、且某个词的前缀能够匹配“f”的文档。很多同学可能会问这个查询怎么这么奇怪,但是仔细想想这个其实是可以用于在搜索框进行搜索建议的 |
match_phrase | 将关键词作为完整的短语去和文档进行匹配,可以设置不同分词的最大距离,例如搜索最匹配“科技大学”的文档,这四个字必须一起出现而不是被拆成“科技”“大学”两个词 |
multi_match | 类似于match,但是可以指定多个被检索字段,例如论文检索时希望返回标题字段或者摘要字段中包含“地铁”的文档 |
4.2 term-level queries 精确匹配
不同于全文查询,term-level queries 不会对文本进行分析,而是只返回结构化数据中能够精确匹配关键词的文档。
查询类型 | 适用场景 |
---|---|
exists | 返回包含该字段的所有文档 |
term | 返回对应字段下精确匹配关键词的文档,例如搜索医院等级为三甲的文档 |
terms | 返回对应字段下精确匹配多个关键词的文档,例如搜索医院等级为三甲或者二甲的文档 |
fuzzy | 返回与关键词在一定编辑距离内的文档,例如搜索菜单字段下与“cake”关键词编辑距离不超过20的所有文档 |
range | 例如搜索发布时间在一段时间范围之内的文档 |