elasticsearch 权威指南
3.数据
3.5更新
文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用《索引文档》章节提到的index
API 重建索引(reindex) 或者替换掉它。
|
|
在响应中,我们可以看到Elasticsearch把_version
增加了。
1
|
|
- <1>
created
标识为false
因为同索引、同类型下已经存在同ID的文档。1>
在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。
在本章的后面,我们将会在《局部更新》中探讨update
API。这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch遵循与之前所说完全相同的过程,这个过程如下:
- 从旧文档中检索JSON
- 修改它
- 删除旧文档
- 索引新文档
3.6创建
|
|
3.9局部更新
文档局部更新
在《更新文档》一章,我们说了一种通过检索,修改,然后重建整文档的索引方法来更新文档。这是对的。然而,使用update
API,我们可以使用一个请求来实现局部更新,例如增加数量的操作。
我们也说过文档是不可变的——它们不能被更改,只能被替换。update
API必须遵循相同的规则。表面看来,我们似乎是局部更新了文档的位置,内部却是像我们之前说的一样简单的使用update
API处理相同的检索-修改-重建索引流程,我们也减少了其他进程可能导致冲突的修改。
最简单的update
请求表单接受一个局部文档参数doc
,它会合并到现有文档中——对象合并在一起,存在的标量字段被覆盖,新字段被添加。举个例子,我们可以使用以下请求为博客添加一个tags
字段和一个views
字段:
|
|
使用脚本更新
脚本能够使用update
API改变_source
字段的内容,它在脚本内部以ctx._source
表示。例如,我们可以使用脚本增加博客的views
数量:
|
|
|
|
3.10 MGET
检索多个文档
mget
API参数是一个docs
数组,数组的每个节点定义一个文档的_index
、_type
、_id
元数据。如果你只想检索一个或几个确定的字段,也可以定义一个_source
参数。
|
|
响应体也包含一个docs
数组,每个文档还包含一个响应,它们按照请求定义的顺序排列。每个这样的响应与单独使用get request响应体相同:
|
|
3.11 批量
bulk
请求体如下,它有一点不同寻常:
|
|
这种格式类似于用"\n"
符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:
- 每行必须以
"\n"
符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。 - 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。
删除操作不需要请求体(request body)。
每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error
标记将被设置为true
,然后错误的细节将在相应的请求中被报告。
|
|
5.搜索
5.1空搜索
|
|
返回结果:
|
|
5.2 多搜索和多索引
|
|
5.3分页
Elasticsearch接受from
和size
参数:
size
: 结果数,默认10
from
: 跳过开始的结果数,默认0
5.4 查询字符串
search
API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL)
在tweet
字段中包含elasticsearch
字符。
|
|
6.映射和分析
6.4 分析
当我们索引(index)一个文档,全文字段会被分析为单独的词来创建倒排索引。不过,当我们在全文字段搜索(search)时,我们要让查询字符串经过同样的分析流程处理,以确保这些词在索引中存在。
- 当你查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。
- 当你查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。
6.5 映射
string
类型的字段,默认的,考虑到包含全文本,它们的值在索引前要经过分析器分析,并且在全文搜索此字段前要把查询语句做分析处理。
string类型默认进行analyze,即全文搜索。而其他字段则是精确匹配。
index
index参数控制字符串以何种方式被索引。
值 | 解释 |
---|---|
analyzed |
首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。 |
not_analyzed |
索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。 |
no |
不索引这个字段。这个字段不能为搜索到。 |
string
类型字段默认值是analyzed
。如果我们想映射字段为确切值(即不分词),我们需要设置它为not_analyzed
。
分析
对于analyzed
类型的字符串字段,使用analyzer
参数来指定哪一种分析器将在搜索和索引的时候使用。默认的,Elasticsearch使用standard
分析器。
6.6 复合类型
多层对象
内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象
|
|
内部对象的映射
|
|
<1> 根对象.1>
<2><3> 内部对象.3>2>
内部对象怎么被索引
Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。
|
|
7.结构化查询
7.2结构化查询
|
|
7.3查询与过滤
原则上来说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句。
一条过滤语句会询问每个文档的字段值是否包含着特定值:
created
的日期范围是否在2013
到2014
?status
字段中是否包含单词 “published” ?lat_lon
字段中的地理位置与目标点相距是否不超过10km ?
一条查询语句与过滤语句相似,但问法不同:
查询语句会询问每个文档的字段值与特定值的匹配程度如何?
查询语句的典型用法是为了找到文档:
- 查找与
full text search
这个词语最佳匹配的文档 - 查找包含单词
run
,但是也包含runs
,running
,jog
或sprint
的文档 - 同时包含着
quick
,brown
和fox
— 单词间离得越近,该文档的相关性越高 - 标识着
lucene
,search
或java
— 标识词越多,该文档的相关性越高
7.4重要的查询过滤子句
term过滤
term
主要用于精确匹配哪些值,比如数字,日期,布尔值或not_analyzed
的字符串(未经分析的文本数据类型)
terms过滤
terms
跟term
有点类似,但terms
允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配。
|
|
range过滤
|
|
exists和missing过滤
exists
和 missing
过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL
条件。
这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。
bool过滤
match_all查询
match查询
match
查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。如果你使用
match
查询一个全文本字段,它会在真正查询之前用分析器先分析match
一下查询字符。如果用
match
下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed
的字符串时,它将为你搜索你给定的值。做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。
multi_match 查询
multi_match
查询允许你做match
查询的基础上同时搜索多个字段:
|
|
bool查询
bool
查询与bool
过滤相似,用于合并多个查询子句。不同的是,bool
过滤可以直接给出是否匹配成功, 而bool
查询要计算每一个查询子句的_score
(相关性分值)。
7.5 过滤与查询
search
API中只能包含 query
语句,所以我们需要用 filtered
来同时包含 “query” 和 “filter” 子句。
|
|
我们在外层再加入 query
的上下文关系:
|
|
7.6验证查询
validate
API 可以验证一条查询语句是否合法。
|
|
想知道语句非法的具体错误信息,需要加上 explain
参数:
|
|
explain参数可以帮助我们理解语句是否正确。
8.排序
8.2.字符串排序
为了使一个string字段可以进行排序,它必须只包含一个词:即完整的not_analyzed
字符串(译者注:未经分析器分词并排序的原字符串)。 当然我们需要对字段进行全文本搜索的时候还必须使用被 analyzed
标记的字段。
在 _source
下相同的字符串上排序两次会造成不必要的资源浪费。 而我们想要的是同一个字段中同时包含这两种索引方式,我们只需要改变索引(index)的mapping即可。 方法是在所有核心字段类型上,使用通用参数 fields
对mapping进行修改。 比如,我们原有mapping如下:
|
|
改变后的多值字段mapping如下:
|
|
<1> tweet
字段用于全文本的 analyzed
索引方式不变。1>
<2> 新增的 tweet.raw
子字段索引方式是 not_analyzed
。2>
现在,在给数据重建索引后,我们既可以使用 tweet
字段进行全文本搜索,也可以用tweet.raw
字段进行排序:
|
|
警告: 对
analyzed
字段进行强制排序会消耗大量内存。
8.3相关性
理解评分
当调试一条复杂的查询语句时,想要理解相关性评分 _score
是比较困难的。ElasticSearch 在 每个查询语句中都有一个explain参数,将 explain
设为 true
就可以得到更详细的信息。
|
|
<1> explain
参数可以让返回结果添加一个 _score
评分的得来依据。1>
提示: JSON形式的explain描述是难以阅读的 但是转成 YAML 会好很多,只需要在参数中加上format=yaml
Explain api
当explain
选项加到某一文档上时,它会告诉你为何这个文档会被匹配,以及一个文档为何没有被匹配。如请求路径为 /index/type/id/_explain
。
10.索引管理
10.1 创建删除
|
|
10.3配置分析器
|
|
10.7 元数据中的source字段
禁用_source字段
|
|
限制返回的字段
|
|
10.8 元数据中的all字段
|
|
12.结构化搜索
通过结构化搜索,你的查询结果始终是 是或非;是否应该属于集合。结构化搜索不关心文档的相关性或分数,它只是简单的包含或排除文档。
过滤器filter有term(terms),bool,range等等。
12.1查询准确值
当term用于查询字符串的时候,记得把term里面field设为not analyzed.
|
|
term(terms)是包含操作,不是等于操作。
12.2 组合过滤
|
|
12.3查询多个准确值
|
|
12.4 包含而不是相等
假如你有一个 term 过滤器 { "term" : { "tags" : "search" } }
,它将匹配下面两个文档:
|
|
<1> 虽然这个文档除了 search
还有其他短语,它还是被返回了1>
term
和 terms
是 必须包含 操作,而不是 必须相等。
完全匹配
假如你真的需要完全匹配这种行为,最好是通过添加另一个字段来实现。在这个字段中,你索引原字段包含值的个数。引用上面的两个文档,我们现在包含一个字段来记录标签的个数:
|
|
一旦你索引了标签个数,你可以构造一个 bool
过滤器来限制短语个数:
|
|
12.5范围
Elasticsearch 有一个 range
过滤器,让你可以根据范围过滤:
|
|
range
过滤器既能包含也能排除范围,通过下面的选项:
gt
:>
大于lt
:<
小于gte
:>=
大于或等于lte
:<=
小于或等于
range
过滤器也可以用于日期字段:
|
|
当用于日期字段时,range
过滤器支持日期数学操作。例如,我们想找到所有最近一个小时的文档:
|
|
这个过滤器将始终能找出所有时间戳大于当前时间减 1 小时的文档,让这个过滤器像移窗一样通过你的文档。
日期计算也能用于实际的日期,而不是仅仅是一个像 now 一样的占位符。只要在日期后加上双竖线 ||
,就能使用日期数学表达式了。
|
|
<1> 早于 2014 年 1 月 1 号加一个月1>
12.8 过滤顺序
在 bool
条件中过滤器的顺序对性能有很大的影响。更详细的过滤条件应该被放置在其他过滤器之前,以便在更早的排除更多的文档。
假如条件 A 匹配 1000 万个文档,而 B 只匹配 100 个文档,那么需要将 B 放在 A 前面。
缓存的过滤器非常快,所以它们需要被放在不能缓存的过滤器之前。
13.全文检索
全文检索:怎样对全文字段(full-text fields)进行检索以找到相关度最高的文档。
全文检索最重要的两个方面是:
相关度(Relevance)
根据文档与查询的相关程度对结果集进行排序的能力。相关度可以使用TF/IDF、地理位置相近程度、模糊相似度或其他算法计算。
分析(Analysis)
将一段文本转换为一组唯一的、标准化了的标记(token),用以(a)创建倒排索引,(b)查询倒排索引。
注意,一旦提到相关度和分析,指的都是查询(queries)而非过滤器(filters)。
准确值('not_analyzed'
) 全文('analyzed'
)
1.基于短语的查询
不会有分析阶段。这些查询在单一的短语上执行,term
查询只在倒排查询里精确地查找特定短语(或者说精确包含),而不会匹配短语的其它变形。
2.全文检索
match
和query_string
这样的查询是高级查询,它们会对字段进行分析:
如果检索一个
'date'
或'integer'
字段,它们会把查询语句作为日期或者整数格式数据。如果检索一个准确值(
'not_analyzed'
)字符串字段,它们会把整个查询语句作为一个短语。如果检索一个全文(
'analyzed'
)字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。我们很少需要直接使用基于短语的查询。通常我们会想要检索全文,而不是单独的短语,使用高级的全文检索会更简单(全文检索内部最终还是使用基于短语的查询)。
如果确实要查询一个准确值字段(
'not_analyzed'
),需要考虑使用查询还是过滤器。[提示] 单一短语的查询通常相当于是/否问题,用过滤器可以更好的描述这类查询,并且过滤器缓存可以提升性能:
12345678910GET /_search{"query": {"filtered": {"filter": {"term": { "gender": "female" }}}}}
分析器
有时,在索引时和搜索时使用不同的分析器是合理的。比如,在索引时我们希望索引到同义词(在quick出现的地方,我们希望同时索引fast,rapid和speedy)。但在搜索时,我们不需要搜索所有的同义词。我们只需要关注用户输入的单词,无论是quick,fast,rapid,还是speedy,输入什么就是什么。
为了区分,ElasticSearch支持参数 index_analyzer 和 search_analyzer,以及 default_index 和 default_search的分析器。
被破坏的相关度(Relevance is Broken)
在我们讨论更复杂的多字段查询之前,这里先解释一下为什么我们将测试数据只创建在一个主分片上(primary shard)。
用户会时不时的抱怨遇到这样的问题:用户索引了一些文档,运行了一个简单的查询,然后明显发现一些低相关性的结果出现在高相关性结果之上。
为了理解为什么会这样,我们可以想象一下,我们在两个主分片上创建了索引,总共10个文档,其中6个文档有单词foo,可能是shard 1有其中3个文档,而shard 2有其中另外3个文档,换句话说,所有文档是均匀分布存储的。
在何为相关性中,我们描述了ElasticSearch默认的相似度算法,这个算法叫做 TF/IDF (term frequency/inverse document frequency)。详细参照之前提到的内容。
但是由于性能原因,ElasticSearch在计算IDF时,不会计算所有的文档。相反,每个分片会根据该分片的所有文档,计算一个本地的IDF。
因为我们的文档是均匀的分布存储的,每个shard的IDF是相同的。如果有5个文档存在于shard 1,而第6个文档存在于shard 2,在这种场景下,foo在一个shard里非常普通(所以不那么重要),但是在另个shard里非常少(所以会显得更重要)。这样局部IDF的差异会导致不正确的结果。
在实际中,这不是一个问题,本地和全局的IDF的差异随着索引里文档数的增多会渐渐消失,在真实世界的数据量下,局部的IDF会迅速被均化,所以这里的问题并不是相关性被破坏了,而是数据量太小了。
为了测试,我们可以通过两种方式解决这个问题。第一种是只在主分片上创建索引,就如我们例子里做的那样,如果只有一个shard,那么本地的IDF就是全局的IDF。
第二个方式就是在搜索请求后添加 ?search_type=dfs_query_then_fetch,dfs 是指 分布式频率搜索(Distributed Frequency Search),它告诉ElasticSearch,先分别获得每个shard本地的IDF,然后根据结果再计算全局的IDF。
注意:
不要在产品上使用第二种方式,我们完全无须如此。
14.多字段搜索
14.1多字符串查询
|
|
为什么将译者条件语句放入另一个独立的 bool 查询中呢?所有的4个 match 查询都是 should 语句,我们为什么不将translator语句与其他语句(如 title、author)放在同一层呢?
答案在于分数的计算方式。bool 查询执行每个 match 查询,然后把他们加在一起,然后将结果与所有匹配的语句数量相乘,再除以所有的语句数量。处于同一层的每条语句具有相同的权重。在上面这个例子中,包含translator语句的 bool 查询,只占总分数的三分之一,如果我们将translator语句与 title 和 author 两个语句放入同一层,那么 title 和 author 语句只贡献四分之一。
14.2单一查询字符串
近似度匹配
部分匹配
补充
elasticsearch 设置 mapping 时的 store 属性
elasticsearch的store属性跟_source字段
后续的翻译:
ElasticSearch 2 (17) - 深入搜索系列之部分匹配