类似关系型数据库中每个字段都有对应的数据类型,例如nvarchar、int、date等等,elasticsearch也会将文档中的字段映射成对应的数据类型,这一映射可以使ES自动生成的,也是可以由我们自定义的。不同的映射关系是会影响到我们的搜索查询功能。
GET /_search?q= # results
GET /_search?q=-- # results !
GET /_search?q=date:-- # result
GET /_search?q=date: # results !
分析上面的查询语句,我们可以发现一个很奇怪的现象。为什么通过q=2014可以查到12条结果,但通过date字段查询年份q=date:2014却没有结果呢?我们推测就是因为不同的字段映射的类型不同而导致的。
- 使用分析工具
通过_mapping api可以分析类型的映射
GET /gb/_mapping/tweet
{
"gb": {
"mappings": {
"tweet": {
"properties": {
"date": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"name": {
"type": "string"
},
"tweet": {
"type": "string"
},
"user_id": {
"type": "long"
}
}
}
}
}
}
我们可以看到date字段是date类型的,而q=2014查询的是_all字段,我们知道这个字段是String类型的。我们可能会猜测 date
字段和 string
字段 索引方式不同,所以搜索结果也不一样。
但实际上虽然不同的数据类型的索引方式会有所不同,但最大的区别是一个是代表精确值的字段(包括String类型),另一个是代表全文的字段。这个区别非常重要,这正是ES和其他关系型数据库最大的不同。ES会将全文域的所有词条形成倒排索引,这也是ES搜索的基础。
- 倒排索引
elsaticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
例如,假设我们有两个文档,每个文档的 content
域包含如下内容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
为了创建倒排索引,我们首先将每个文档的 content
域拆分成单独的 词(我们称它为 词条
或 tokens
),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
如果我们想要搜索 quick brown
,我们只需要查找包含每个词条的文档:
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | |
由上图可得知,匹配的文档有两个,但是第一个文档要比第二个匹配度更好。但这也引发出了一些问题。
对于一个搜索操作来说:
1、用户可能不关心词条的大小写,Quick和quick可能是一样的
2、由于词法的变化,例如单复数的变化,对于用户来说可能也是不关心的,例如dog和dogs
3、同义词,一些同义词尽管不同,但实际上表达的含义是相同的,我们希望能够匹配出来。
解决方案:
对于输入和输出事先做预处理,也就是在倒排索引前对文档统一处理,例如把Quick统一成quick,把dogs统一成dog。搜索时,对搜索的内容也做统一处理,例如Quick dogs转化成quick dog。这样通过倒排索引就可以解决上面几个问题,优化搜索。这个过程就是分析。
- 分析和分析器
所谓分析器实际上是把三个功能封装在了一个包里
1、字符过滤器
首先,字符串按顺序通过每个字符过滤器。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 &
转化成 `and`。
2、分词器
字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
3、Token过滤器
词条按顺序通过每个Token过滤器。这个过程可能会改变词条(例如,小写化 Quick
),删除词条(例如, 像 a,and,the
等无用词),或者增加词条(例如,像 jump
和 leap
这种同义词)
- 内置分析器
ES附带了可以直接使用的预包装的分析器
示例文本
"Set the shape to semi-transparent by calling set_trans(5)"
1、标准分析器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据Unicode联盟定义的单词边界划分文本。删除绝大部分标点。最后,将词条小写。
set, the, shape, to, semi, transparent, by, calling, set_trans,
2、简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。
set, the, shape, to, semi, transparent, by, calling, set, trans
3、空格分析器
空格分析器在任何不适字母的地方分割文本,将词条小写。
Set, the, shape, to, semi-transparent, by, calling, set_trans()
4、语言分析器
特定语言分析器可用于很多语言。它们可以考虑指定语言的特点。例如, 英语
分析器附带了一组英语无用词(常用单词,例如 and
或者the
,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的词干。
set, shape, semi, transpar, call, set_tran,
这里set_trans变为了set_tran,calling变为了call
- 测试分析器
有些时候很难理解分词的过程和实际被存储到索引中的词条,你可以使用 analyze
API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:
GET 127.0.0.1:9200/_analyze
{
"analyzer": "standard",
"text": "our Fingertips owns the power of changing the world"
}
响应
{
"tokens": [
{
"token": "our",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "fingertips",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "owns",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "the",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "power",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "of_changing",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "the",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "world",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
}
]
}
从中我们可以看到分析器是怎么帮我们分词的。token
是实际存储到索引中的词条。 position
指明词条在原始文本中出现的位置。 start_offset
和end_offset
指明字符在原始字符串中的位置。
- 核心简单域类型
elasticsearch支持一下简单域类型
1、字符串:string
2、整数:byte,short,integer,long
3、浮点型:float,double
4、布尔型:boolean
5、日期:date
当你索引一个包含新域的文档(之前未曾出现), Elasticsearch 会使用动态映射,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:
JSON type |
域 type |
布尔型: |
|
整数: |
|
浮点数: |
|
字符串,有效日期: |
|
字符串: |
|
这意味着如果通过“123”索引一个数字,那么它会被映射成为string,但如果这个域已经是long,那么ES会尝试将这个字符串转化为long,如果无法转化,则抛出一个异常。
- 自定义域映射
自定义映射可以做到
1、全文字符串域和精确值字符串域的区别
2、使用特定的语言分析器
3、优化域以适应部分匹配
4、指定自定义数据格式
域属性:
- type
域最重要的属性是type.对于不是string的域,一般只需要设置type
{
"number_of_clicks": {
"type": "integer"
}
}
默认, string
类型域会被认为包含全文。就是说,它们的值在索引前,会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。
- index
index属性控制了怎样所以字符串,值可以为:
1、analyzed
首先分析字符串,然后索引它。换句话说,以全文索引这个域。
2、not_analyzed
索引这个域,所以可以搜索到它,但索引指定的精确值。不对它进行分析。
3、no
这个域不会被搜索到。
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
- analyzer
对于 analyzed
字符串域,用 analyzer
属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard
分析器, 但你可以指定一个内置的分析器替代它,例如 whitespace
、 simple
和 english:
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
更新映射
首次 创建一个索引的时候,可以指定类型的映射。你也可以使用 /_mapping
为新类型(或者为存在的类型更新映射)增加映射。
注意:尽管你可以增加一个已经存在的映射,但无法修改存在的域映射。如果一个域的映射已经存在,那么该域的数据可能已经被索引。如果你意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索。
- 创建索引时指定映射
PUT /gb
{
"mappings": {
"tweet" : {
"properties" : {
"tweet" : {
"type" : "string",
"analyzer": "english"
},
"date" : {
"type" : "date"
},
"name" : {
"type" : "string"
},
"user_id" : {
"type" : "long"
}
}
}
}
}
这段代码指定了tweet类型的映射,把tweet属性使用english分析器,是一个全文string类型。date为日期类型,name为全文string类型使用standard分析器,user_id是长整型。
- 新增一个属性,并指定映射
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
新增了一个tag属性,为一个精确值string类型,即不对这个字段作分析
我们来测试一下
GET /gb/_analyze?field=tweet&text=Black-cats GET /gb/_analyze?field=tag&text=Black-cats
可以看到对于tweet属性来说,索引为black和cat,而对于Black-cat 来说索引为Black-cat