操作 - Restful
# 索引库
# 创建
ES 中通过 Restful 请求操作索引库、文档。请求内容用 DSL 语句来表示。创建索引库和 mapping 的 DSL 语法如下:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
示例:
点击查看
PUT /heima
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": "false"
},
"name":{
"properties": {
"firstName": {
"type": "keyword"
}
}
},
// ... 略
}
}
}
字段拷贝可以使用 copy_to 属性将当前字段拷贝到指定字段。示例:
"all": {
"type": "text",
"analyzer": "ik_max_word"
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
}
# 查询
GET /索引库名
# 删除
DELETE /索引库名
# 修改
索引库和 mapping 一旦创建无法修改,但是可以添加新的字段,语法如下:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
示例:
点击查看
PUT /heima/_mapping
{
"properties": {
"age":{
"type": "integer"
}
}
}
# 文档
# 新增
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
如果不传递文档 id,则随机 id
示例:
点击查看
POST /heima/_doc/1
{
"info": "黑马程序员Java讲师",
"email": "[email protected]",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
# 详情
GET /索引库名/_doc/文档id
参考响应
点击查看
{
"_index" : "heima",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"info" : "黑马程序员Java讲师",
"email" : "[email protected]",
"name" : {
"firstName" : "云",
"lastName" : "赵"
}
}
}
# 删除
DELETE /索引库名/_doc/文档id
# 删除全部
POST /索引库名/_delete_by_query
{
"query": {
"match_all": {
}
}
}
# 修改
方式一: 全量修改,会删除旧文档,添加新文档
PUT /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
方式二: 增量修改,修改指定字段值
POST /索引库名/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
点击查看
POST /heima/_update/1
{
"doc": {
"email": "[email protected]"
}
}
# CAS 修改
内置字段
参数在普通修改的基础上,添加 version
参数,ES 默认使用内置的 _version
字段做版本控制
GET /task_apply/_doc/1732243647854014464
{
"_index" : "task_apply",
"_type" : "_doc",
"_id" : "1732243647854014464",
"_version" : 1,
....
}
修改
PUT /索引库名/_doc/文档id?version=1
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
外置字段
我们更多时候会使用 updateTime
字段作为 CAS 条件
新增时指定 version
POST /索引库名/_doc/文档id?version=更新时间毫秒时间戳&version_type=external
{
"字段1": "值1",
"字段2": "值2",
...
}
修改时
PUT /索引库名/_doc/文档id?version=本次更新时间毫秒时间戳&version_type=external
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
笔记
外置字段在修改时,ES 比较的并不同于内置字段,内置字段比较的是 version 值是否等于跟数据库的值,外置字段时,比较的是 version 值是否大于数据库的值
注意
使用外置方式似乎不能满足 CAS,只是能保证数据不会被旧的覆盖新的。
# 文档 - 搜索(列表查询)
Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language (opens new window))来定义查询。常见的查询类型包括:
- 查询所有: 查询出所有数据,一般测试用。例如:
- match_all
- 全文检索(full text)查询: 利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- 精确查询: 根据精确词条值查找数据,一般是查找 keyword、数值、日期、boolean 等类型字段。例如:
- ids
- range
- term
- 地理(geo)查询: 根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- 复合(compound)查询: 复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
# 查询条件
# 查询所有
// 查询所有
GET /indexName/_search
{
"query": {
"match_all": {
}
}
}
# 全文检索查询
搜索的字段越多,搜索的效率月底,需要搜索多个字段,应使用
copy_to
# match
类似于 MySQL 的
where FIELD like ‘%TEXT%’
全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:
GET /indexName/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
}
}
Example:
点击查看
GET /hotel/_search
{
"query": {
"match": {
"all": "如家外滩"
}
}
}
# multi_match
类似于 MySQL 的
where FIELD1 like ‘%TEXT%’ or FIELD2 like ‘%TEXT%’
与 match 查询类似,只不过允许同时查询多个字段,语法:
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "TEXT",
"fields": ["FIELD1", " FIELD12"]
}
}
}
Example:
点击查看
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "如家外滩",
"fields": ["brand","name","business"]
}
}
}
# 精确查询
# term
类似于 MySQL 的
where FIELD1 = ‘TEXT’
根据词条精确值查询
GET /indexName/_search
{
"query": {
"term": {
"FIELD": {
"value": "VALUE"
}
}
}
}
Example:
点击查看
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
# range
类似于 MySQL 的
where FIELD >= 10 and FIELD <= 20
根据值的范围查询
GET /indexName/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10,
"lte": 20
}
}
}
}
- gte:大于等于
- gt:大于
- lte:大于等于
- lt:小于
Example:
点击查看
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gt": 1108,
"lte": 3000
}
}
}
}
# 地理查询
# geo_bounding_box
查询 geo_point 值落在某个矩形范围的所有文档
GET /indexName/_search
{
"query": {
"geo_bounding_box": {
"FIELD": {
"top_left": {
"lat": 31.1,
"lon": 121.5
},
"bottom_right": {
"lat": 30.9,
"lon": 121.7
}
}
}
}
}
# geo_distance
查询到指定中心点小于某个距离值的所有文档
GET /indexName/_search
{
"query": {
"geo_distance": {
"distance": "15km",
"FIELD": "31.21,121.5"
}
}
}
# 复合查询
# 相关性算分
当我们利用 match 查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。 例如,我们搜索 "虹桥如家",结果如下:
[
{
"_score" : 17.850193,
"_source" : {
"name" : "虹桥如家酒店真不错",
}
},
{
"_score" : 12.259849,
"_source" : {
"name" : "外滩如家酒店真不错",
}
},
{
"_score" : 11.91091,
"_source" : {
"name" : "迪士尼如家酒店真不错",
}
}
]
elasticsearch 中的相关性打分算法是什么?
- TF-IDF: 在 elasticsearch5.0 之前,会随着词频增加而越来越大
- BM25: 在 elasticsearch5.0 之后,会随着词频增加而增大,但增长曲线会趋于水平
# fuction score
算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价
GET /hotel/_search
{
"query": {
"function_score": {
"query": { "match": {"all": "外滩"} },
"functions": [
{
"filter": {"term": {"id": "1"}},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
Example:
点击查看
给 “如家” 这个品牌的酒店排名靠前一些
把这个问题翻译一下,function score 需要的三要素:
哪些文档需要算分加权? 品牌为如家的酒店
算分函数是什么? weight 就可以
加权模式是什么? 求和
GET /hotel/_search
{
"query": {
"function_score": {
"query": {// ... },
"functions": [ // 算分函数
{
"filter": { // 满足的条件,品牌必须是如家
"term": {
"brand": "如家"
}
},
"weight": 2 // 算分权重为2
}
],
"boost_mode": "sum"
}
}
}
# Boolean Query
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
must:必须匹配每个子查询,类似 “与”
should:选择性匹配子查询,类似 “或”
must_not:必须不匹配,不参与算分,类似 “非”
filter:必须匹配,不参与算分
Example:
点击查看
搜索名字包含 “如家”,价格不高于 400,在坐标 31.21,121.5 周围 10km 范围内的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "如家"
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
],
"filter": [
{
"geo_distance": {
"distance": "10km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
]
}
}
}
# 排序
elasticsearch 支持对搜索结果排序 (opens new window),默认是根据相关度算分(_score)来排序。可以排序字段类型有:
- keyword 类型
- 数值类型
- 地理坐标类型
- 日期类型等。
一旦使用排序,原本的打分(score)失效,可提高效率
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": "desc" // 排序字段和排序方式ASC、DESC
}
]
}
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance" : {
"FIELD" : "纬度,经度", // 以此经纬度为中心
"order" : "asc",
"unit" : "km"
}
}
]
}
Example:
点击查看
1、对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序
评价是 score 字段,价格是 price 字段,按照顺序添加两个排序规则即可。
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": "desc"
},
{
"price": "asc"
}
]
}
2、实现对酒店数据按照到你的位置坐标的距离升序排序
获取经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.034661,
"lon": 121.612282
},
"order": "asc",
"unit": "km"
}
}
]
}
# 分页
elasticsearch 默认情况下只返回 top10 的数据。而如果要查询更多数据就需要修改分页参数了。 elasticsearch 中通过修改 from、size 参数来控制要返回的分页结果:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 990, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
深度分页问题
ES 是分布式的,所以会面临深度分页问题。例如按 price 排序后,获取 from = 990,size =10 的数据:
- 首先在每个数据分片上都排序并查询前 1000 条文档。
- 然后将所有节点的结果聚合,在内存中重新排序选出前 1000 条文档
- 最后从这 1000 条中,选取从 990 开始的 10 条文档
如果搜索页数过深,或者结果集(from + size)越大,对内存和 CPU 的消耗也越高。因此 ES 设定结果集查询的上限是 10000
深度分页解决方案
针对深度分页,ES 提供了两种解决方案,官方文档 (opens new window):
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
总结
from + size:
- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是 10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
after search:
- 优点:没有查询上限(单次查询的 size 不超过 10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
scroll:
- 优点:没有查询上限(单次查询的 size 不超过 10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从 ES7.1 开始不推荐,建议用 after search 方案。
# 高亮
高亮:就是在搜索结果中把搜索关键字突出显示。
原理是这样的:
- 将搜索结果中的关键字用标签标记出来
- 在页面中给标签添加 css 样式
语法:
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
Example:
点击查看
# 高亮查询,默认情况下,ES搜索字段必须与高亮字段一致
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"require_field_match": "false"
}
}
}
}