# Elastic Search 进阶
# 1. 操作索引
ES 集群可以包含:
多个索引(indices)。类比 SQL 领域中的数据库(database);
每一个索引(index)中可以包含多个类型(types)。类比 SQL 领域中的表(table);不过 type 的概念在弱化,可使用
_doc
关键字代替。每一个类型包含多个文档(documents),类比于 SQL 领域中的行(row);
然后每个文档(document)包含多个字段(Fields),类比于 SQL 领域中的列(Column)。
# 创建索引
PUT 索引名
返回结果显示 acknowledged
的值为 true
,说明新建索引成功。
索引名中不能含有大写字母。
Elasticsearch 默认给一个索引设置 5 个分片 1 个副本,一个索引的分片数一经指定后就不能再修改,副本数可以通过命令随时修改。
如果想创建自定义分片数和副本数的索引,可以通过 setting 参数在创建索引时设置初始化信息。
例如,指定创建 3 个分片 0 个副本:
PUT 索引名 { "settings" : { "number_of_shards" : 3, "number_of_replicas" : 0 } }
在创建索引之后,也可修改其索引的副本数。
例如,将名为
xxx
的索引的副本数修改为 3 使用如下命令:PUT xxx/_settings { "number_of_replicas" : 3 }
# 删除索引
索引的删除只需要使用 DELETE 方法,传入要删除的索引名即可。注意,一旦执行删除操作索引中的文档就不服存在。
删除名为
xxx
的索引,命令如下:DELETE xxx
如果删除成功,会有以下响应:
{ "acknowledged" : true }
尝试删除一个不存在的索引,会报 索引未找到
异常。
# 查看索引(了解、自学)
使用 GET 方法加上 _setting 参数可以查看一个索引的所有配置信息。
GET xxx/_settings
GET xxx,yyy,zzz/_settings
GET _all/_settings
# 2. 增删改文档
Elasticsearch 中文档的增删改查和关系型数据库操作非常相似。
# 新建文档(插入新的文档)
PUT 索引名/_doc/手动指定ID值
{
"field1" : "value1",
"field2" : "value2",
"field3" : "value3",
...
}
例如:
PUT books/_doc/1
{
"id" : "1",
"title" : "Java 编程思想",
"language" : "java",
"author" : "Bruce Eckel",
"price": 70.20,
"publish_date" : "2007-10-01",
"description" : "Java 学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉。"
}
如果没有出现错误,Elasticsearch 服务器会返回一个 JSON 格式的响应信息中会有 "created": true
信息。
当然,你也可以直接通过查询命令以验证你是否插入成功:
GET /索引名/_doc/ID值/_source
GET /索引名/_doc/_search
向索引中新加文档时,如果你没有手动指定文档的 ID,那么 Elasticsearch 会自动生成它的 ID 。不过,此时需要使用 POST 命令,而非 PUT 命令。
POST books/_doc
{
"id" : "1",
"title" : "Java 编程思想",
"language" : "java",
"author" : "Bruce Eckel",
"price": 70.20,
"publish_date" : "2007-10-01",
"description" : "Java 学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉。"
}
# 删除文档
Delete API 允许基于指定的 ID 从索引库中删除一个文档。
DELETE /索引名/_doc/ID值
例如:
DELETE /books/_doc/1
删除成功后,Elasticsearch 返回的信息中会给出类似 "found" : true, "result" : "deleted"
信息。
# 查询删除
POST /索引名/_doc/_delete_by_query
{
"query" : {
"term": {
"FIELD1": "VALUE1",
...
}
}
}
如果是无条件删除所有文档,那么语法是:
POST 索引名/_doc/_delete_by_query
{
"query" : {
"match_all" : {}
}
}
# 更新文档
文档被索引之后,如果要更新,那么 Elasticsearch 内部首先要找到这个文档,删除旧的文档内容执行更新,更新完之后再索引最新的文档。
语法:
POST 索引名/_doc/id值 { "field1" : "value1", "field2" : "value2", "field3" : "value3", ... }
该命令与新增文档命令「看起来很像」,唯一不同的就是 PUT 请求变为 POST 请求。
执行成功时,返回的信息中我们可以看到
已更新
,以及增加后的版本号。{ ... "_version" : xxx, "result" : "updated", }
再次强调,更新的底层原理是『先删除后增加』。
POST 请求总结:
不带 ID 时,是新增操作,并且是要求 ElasticSearch 为新增数据的 _id 赋值。
带 ID 时,是修改操作,通过携带的 ID 来指定要修改那条数据。
# 局部更新文档
接受一个局部文档参数 doc
,它会合并到现有文档中,对象合并在一起,存在的标量字段被覆盖,新字段被添加。
语法:
POST 索引值/_doc/id值/_update { "doc" : { "field1" : "value1", "field2" : "value2", "field3" : "value3", ... } }
局部更新仍旧也是先删除旧文档,再重新添加新文档。
# 映射
映射也就是 Mapping ,用来定义一个文档(Document)以及其包含的字段如何被存储和索引,可以再映射中事先定义字段的数据类型、分词器等属性。
简单来说,Elasticsearch 中的映射就如同数据库领域中的表定义。
映射可分为『动态映射』和『静态映射』。
动态映射就是文档在写入 Elasticsearch 时,由 Elasticsearch 根据字段的类型自动识别。而静态映射就如同表定义,在写入数据之前对字段的属性进行手工设置。
动态映射是一种偷懒的方式,在创建索引(index)后,直接去插入文档,跳过定义的环节,而由 Elasticsearch 根据插入的文档的数据来自己识别。
Elasticsearch 自动推测字段类型的规则
JSON 格式的数据 | 自动推测的字段类型 |
---|---|
null | 没有字段被添加 |
true or false | boolean 类型 |
浮点类型数字 | float 类型 |
数字 | long 类型 |
JSON 对象 | object 类型 |
数组 | 由数组中第一个非空值决定 |
string | 有可能是 date 类型、double 类型或 long 类型、text 类型、keyword 类型 |
动态映射看似很方便,但是有一个缺点:它使用的是默认的分词器(standard),特别是对于我们中文环境而言,这简直就是致命缺点。
因此,通常我们在创建完索引之后,会手动创建映射:
PUT /索引名称/_mapping
{
"properties": {
"属性名" : { "type": "类型" },
"属性名" : { "type": "keyword" },
"属性名" : { "type": "text", "analyzer": "分词器分词方式" },
"属性名" : { "type": "date", "format": "格式1 || 格式2 || ..." },
...
}
}
例如,素材
中的 mapping 命令:
PUT /books/_mapping
{
"properties": {
"id" : { "type": "keyword" },
"title" : { "type": "text", "analyzer": "ik_smart" },
"language": { "type": "keyword" },
"author" : { "type": "keyword" },
"price" : { "type": "float" },
"publish_date" : { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" },
"description" : { "type": "text", "analyzer": "ik_max_word" }
}
}
查看索引的 mapping ,命令如下:
GET xxx/_mapping
如果你输入字符串看起来像日期(例如:2000-1-1),那么 Elasticsearch 会将它识别为一个 date 类型的字段。
另外,Elasticsearch 5.X 之后的字段类型不再支持 string ,而是由 text 和 keyword 取代。
text 类型的字段中的数据会被分词,以支持全文索引;
keyword 类型的字段只能作为一个整体被精确搜索。
# 3. 查询文档
Elasticsearch 提供了 GET API 查看在其中所存储的文档(Document),使用 GET 命令并指定文档所在的索引(index)、类型(type)和 id 即可返回一个 JSON 格式的文档(Document)。
如果所查看的文档存在,返回的信息中会含有 "found" : true
,反之,则会含有 "found" : false
。
# 简单查询
# 根据 ID 查询
GET /索引名/_doc/id值
# 空查询
空查询是指没有指定任何查询条件的查询。这种情况下,返回的是所有文档:
GET 索引名/_doc/_search
例如:
GET books/_doc/_search
你会看到,es 在建议你,不用写 _doc 关键字。即,写成:
GET 索引名/_search
# 分页查询
Elasticsearch 提供了两个结果分页的属性:
分页属性 | 说明 |
---|---|
from | 指定返回结果的开始位置。默认值为 0 。 |
size | 指定一次性返回结果包含的最大文档数量。 |
# GET /books/_doc/_search
GET /books/_search
{
"from" : 0,
"size" : 100
}
# 显示版本号
默认情况下返回结果中不包含文档的版本号,如果需要,可以在查询体中设置 version 属性为 true 。
GET books/_search
{
"version" : true
}
# 排序
当搜索的字段有多个时,可以指定字段进行排序。
GET books/_search
{
"sort" : [
{ "price": { "order" : "desc" } },
{ "publish_date": { "order": "asc" } }
]
}
# 数据列过滤
数据列过滤允许在查询的时候不显示原始数据,或者显示部分原始数据。
GET books/_search
{
"_source" : false
}
GET books/_search
{
"_source" : ["id", "price"]
}
# 词项(term)查询
词项查询是对倒排序索引中存储的词进行精确操作。词项级别的查询通常用于结构化数据,如数字,日期。
# term 查询
term 查询用来查找 所指定的字段中包含给定单词
的文档。term 查询『不会被解析』,只有查询词和文档中的词精确匹配才会被搜索到。应用场景为查询人名、地名等需要精确匹配的需求。
查询命令如下:
GET books/_search
{
"query" : {
"term" : { "title" : "思想" }
}
}
# 返回部分字段信息
默认情况下,返回的结果中包含了文档的所有字段信息,有时候为了简洁,只需要在查询结果中返回某些字段。
GET books/_search
{
"_source" : ["title", "author"],
"query" : {
"term" : { "title" : "思想" }
}
}
# 返回版本号
默认情况下,返回的结果中不包含文档的版本号,如果需要,可以在查询体中设置 version
属性为 true
。
GET books/_search
{
"version" : true,
"query" : {
"term" : { "title" : "思想" }
}
}
# 最小评分过滤
Elasticsearch 提供了基于最小评分的过滤机制,通过这种机制,可以过滤/排除掉查询结果中相关性较低的结果。
GET books/_search
{
"min_score" : 0.6,
"query" : {
"term" : { "title" : "思想" }
}
}
# terms 查询
terms
查询是 term
查询的升级版,可以用来查询文档中包含多个词的文档。
比如,想查询 title 字段中包含 java
或 python
的文档。
GET books/_search
{
"query" : {
"terms" : {
"title" : ["java","python"]
}
}
}
# 全文(text)查询
全文搜索通常用于在全文(text)字段上进行搜索。
Elasticsearch 取消了 string 类型,取而代之的是 text 类型和 keyword 类型。
两者的区别在于:text 类型的字段中存储的字符串会被分词器分词,而 keyword 类型字段中存储的字符串则被当作一个整体看待。
在执行全文搜索前,所要查询的字段的分词器会应用于查询字符串,对其进行分词。
# match 查询
如果你进行的是 match 查询,那么,你所提供的查询条件会被分词。即,
你以为你提供的只是一个查询条件,其实你提供了一堆查询条件,而这是一堆条件之间是『或』 的关系。
GET books/_search
{
"query" : {
"match" : {
"title" : "java编程"
}
}
}
match 查询会对查询语句进行分词,分词后查询语句中的任何一个词项被匹配,文档即被选中。
TIP
想知道中文字符串被分词器解析出几个词项,可以通过下面命令简介知道:
POST _analyze
{
"analyzer" : "ik_max_word 或 ik_smart",
"text" : "内容"
}
# 查看某个字段的分词结果
GET 索引名/_doc/指定ID/_termvectors?fields=字段名
如果想查询匹配所有关键词的文档,可以用 and
操作符连接。
GET books/_search
{
"query" : {
"match" : {
"title" : {
"query" : "java编程思想",
"operator" : "and"
}
}
}
}
# match_phrase 查询
match_phrase
查询首先会把 query 内容分词,分词器可以自定义,同时文档还要满足以下两个条件才会被搜索到:
- 分词后所有词都要出现在该字段中
- 字段中词项顺序要一直
GET test/_search
{
"query" : {
"match_phrase" : {
"foo" : "hello world"
}
}
}
{ "foo" : "I just said hello world" }
会被选中{ "foo" : "Hello world" }
会被选中{ "foo" : "World hello" }
则不会
# multi_match 查询
multi_match
查询是 match
查询的升级版,用于搜索多个字段。
查询条件为 java 编程
,查询域为 title
和 description
:
GET books/_search
{
"query" : {
"multi_match" : {
"query" : "java编程",
"fields" : ["title", "description"]
}
}
}
multi_match
支持对要搜索的字段的名称使用通配符:
"fields" : ["title", "*_name"]
# 其它查询
# range 查询
range
查询用于匹配在某一范围内的数值型、日期类型的文档。比如搜索哪些书籍的价格在 50 到 100 之间,哪些书籍的出版日期在 2014 年到 2016 年之间。
使用 range
查询只能查询一个字段,不能作用于多个字段上。
range
查询支持的参数有以下几种:
参数 | 说明 |
---|---|
gt | 大于。查询范围的最小值,也就是下限,但不包含临界值。 |
gte | 大于等于。和 gt 的区别在于包含临界值。 |
lt | 小于。查询范围的最大值,也就是上限,但不包含临界值。 |
lte | 大于等于。和 lt 的区别在于包含临界值。 |
GET books/_search
{
"query" : {
"range" : {
"price" : {
"gt" : 50,
"lte" : 70
}
}
}
}
GET books/_search
{
"query" : {
"range" : {
"publish_date" : {
"gte" : "2016-01-01",
"lte" : "2016-12-31",
"format" : "yyyy-MM-dd"
}
}
}
}
# exists 查询
exists
查询会返回字段中至少有一个非空值的文档。例如:
GET books/_search
{
"query" : {
"exists" : {
"field" : "user"
}
}
}
对于上述情况,被选中的文档会有:
· { "user" : "tom" }
· { "user" : "" }
· { "user" : "-" }
· { "user" : ["tom"] }
· { "user" : ["tom", null] }
不会被选中的文档有:
· { "user" : null }
· { "user" : null }
· { "user" : [] }
· { "user" : [null] }
· { "hello" : "world" }
# prefix 查询
prefix 查询用于查询某个字段中『以给定前缀开始』的文档。
比如,查询 title 中以 java
为前缀的文档,那么含有 java
、javascript
、javaee
等以 java 开头的关键词的文档都会被选中。
GET books/_search
{
"query" : {
"prefix" : {
"title" : "java"
}
}
}
注意
prefix 查询性能并不很高,需要消耗较多的 CPU 资源。
# wildcard 查询
wildcard 查询就是通配符查询,支持单字符和多字符通配。
?
用来匹配任意一个字符,*
用来匹配零个或多个字符。
注意
和 prefix 查询一样,wildcard 查询的查询性能也不是很高。
GET books/_search
{
"query" : {
"wildcard" : {
"author" : "张*"
}
}
}
# 复合查询
复合查询就是把一些简单查询组合在一起实现更复杂的查询需求。
# bool 查询
bool 查询可以把任意多个简单查询组合在一起,使用 must
、should
、must_not
、filter
选项来表示简单查询之间的逻辑。每个选项都可以出现 0 到多次,具体含义如下:
选项 | 说明 |
---|---|
must | 文档必须匹配 must 选项下的查询条件,相当于 AND 。 |
should | 文档可以匹配 should 选项下的条件,也可以不匹配。相当于 OR 。 |
must_not | 与 must 相反,匹配该选项下的查询条件的文档会被排除,不显示。 |
filter | 和 must 一样,匹配 filter 选项下的条件的文档才会被选中,显示。但 filter 不评分,只起到过滤功能。 |
{
"match": { "title": "brown fox"}
}
实际上相当于就是:
// 命中的数据的 tittle 可以有 brown(也可以没有),可以有 fox(也可以没有)。
// 两个条件都是可以有(也可以没有)。
{
"bool": {
"should": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
{
"match": {
"title": {
"query": "brown fox",
"operator": "and"
}
}
}
相当于就是:
// 命中的数据的 tittle 必须有 brown ,必须有 fox 。
// 两个条件都是必须有。
{
"bool": {
"must": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
例如:
// 命中的数据的 tittle 必须有 brown ,desciption 中可以有“虚拟机”(也可以没有),price 必须不能大于等于 70 。
GET books/_search
{
"query" : {
"bool" : {
"must" : [{
"match" : { "title" : "java" }
}],
"should" : [{
"match" : { "description" : "虚拟机"}
}],
"must_not" : [{
"range" : {"price" : { "gte": 70 }}
}]
}
}
}