DSL 搜索

ES 使用 queryString 形式根据搜索词对索引表的文档进行检索:

GET /{indexName}/_doc/_search?q={fieldName1}:{searchTxt1}&q={fieldName2}:{searchTxt2}..

但这种 queryString 的形式查询数据只适合一些简单查询的场景,一旦参数多了就难以进行构建(需要添加分页、过滤等功能),所以多数情况下使用 DSL Domain Specific Language 进行查询更好,因为它基于 JSON 格式的数据查询,这样的可读性会更好,有利于复杂查询。

1 DSL 语法

1.1 基本搜索

  • match_all 关键字,表示 在索引中查询所有文档(上一篇文章有 queryString 查询的做法)
  • _source 关键字填充需要搜索的字段名
  • fromsize:默认的 ES 只显示10条数据,此时可以使用分页显示,from 是表示从起始索引,size 表示一页显示的个数
POST /{indexName}/_doc/_search
{
	"query": {
		"match_all": {}
	},
	"_source": [{fieldName1}, {fieldName2}...],
	"from": 1,
	"size": 5
}
  • match 关键字,将 搜索内容进行分词后搜索
  • match 关键字默认的 operator 属性是 or,即搜索内容分词后,只要存在一个词语匹配就展示结果,设置为 and 后,搜索内容分词后,都要满足词语匹配
  • minimum_should_match:最低匹配精度,比如说最低匹配精度为 70%,而搜索内容分词后有 10 个词语,则文档必须要有 10*0.7=7 个词语匹配才会符合显示需求。
POST /{indexName}/_doc/_search
// 默认 operator 为 or
{
	"query": {
		"match": {
			{fieldName}: {searchTxt}
		}
	}
}
// 设置 operator 为 true
{
    "query": {
		"match": {
			{fieldName}: {
				"query": {searchTxt},
                "operator": "and"
			}
		}
    }
}
// 最低匹配精度
{
    "query": {
		"match": {
			{fieldName}: {
				"query": {searchTxt},
				"minimum_should_match": {minimum_should_match}
			}
		}
    }
}
  • multi_match 关键字,满足使用 match 模式在多个字段属性中进行查询的需求
  • boost 权重,多字段属性查询时,可为某些字段属性设置权重,权重越高,文档相关性得分就越高,如下 {fieldName2}^{weight} 举例:"desc^10" 表示 desc 字段属性搜素提升 10 倍相关性,即如果其他字段没有设置权重,用户搜索时会以 desc 为主
POST /{indexName}/_doc/_search
{
	"query": {
		"multi_match": {
			"query": {searchTxt},
			"fields": [{fieldName1}, {fieldName2}...]
		}
	}
}
// 设置权重
{
	"query": {
		"multi_match": {
			"query": {searchTxt},
			"fields": [{fieldName1}, {fieldName2}^{weight}...]
		}
	}
}
  • match_phrase 关键字,搜索内容的分词结果必须在文档分词中都包含,且顺序必须相同,而且必须连续(词与词之间必须紧贴在一起),除非设置了 slop 属性,允许词语之间跳过的文字数量
POST /{indexName}/_doc/_search
{
	"query": {
		"match_phrase": {
			"desc": {
				"query": {searchTxt},
				"slop": {slopNum}
			}
		}
	}
}
  • term 关键字与 terms 关键字,将 搜索内容作为一整个关键词去搜索,而不是进行分词后搜索,同时 terms 支持多个词语匹配检索
POST /{indexName}/_doc/_search
// term
{
	"query": {
		"term": {
			{fieldName2}: {searchTxt2},
		}
	}
}
// terms
{
	"query": {
		"terms": {
			{fieldName1}: [{searchTxt1}, {searchTxt2}...]
		}
	}
}
  • prefix 关键字,根据前缀去查询
POST /{indexName}/_doc/_search
{
	"query": {
		"prefix": {
			// {prefixTxt} 作为前缀
			{fieldName}: {prefixTxt},
		}
	}
}
  • fuzzy 关键字,在用户进行搜索时可能出现打错字的现象,搜索引擎会自动纠正,然后尝试匹配数据
POST /{indexName}/_doc/_search
{
	"query": {
		"fuzzy": {
			{fieldName}: {errorWord} 
		}
	}
}
  • ids 关键字,根据主键 ID 搜索
POST /{indexName}/_doc/_search
{
	"query": {
		"ids": {
			"type": "_doc",
			"values": [{id1}, {id2}...]
		}
	}
}

1.2 布尔查询

  • 布尔查询(多重组合查询
  • must:查询必须匹配搜索条件,类似于 SQL 语句的 and
  • should:查询匹配满足一个以上条件,类似于 SQL 语句的 or
  • must_not:不匹配搜索条件,一个都不要满足
  • 有时候,搜索同一个文档字段属性,希望 搜索内容分词A搜索内容分词B 权重更高可以使用 should 模式并设置 boost 权重属性
POST /{indexName}/_doc/_search
// 使用 must 举例
{
	"query": {
		"bool": {
			"must": [
				{
					"multi_match": {
						"query": {searchTxt1},
						"fields": [{fieldName1}, {fieldName2}..]
					}
				},
				{
					"term": {
						{fieldName3}: {searchTxt2}
					}
				},
				{
					"term": {
						{fieldName4}: {searchTxt3}
					}
				},
				...
				}
			]
		}
	}
}
// 同时使用 must,should,must_not 也是可以的
{
	"query": {
		"bool": {
			"must": [
				...
			]
			"should": [
				...
			]
			"must_not": [
				...
			]
		}
	}
}
// 使用 should 时,可以添加权重,举例
{
	"query": {
		"bool": {
			"should": [
				{
					"match": {
						"desc": {
							"query": "律师",
							"boost": 18
						}
					}
				},
				{
					"match": {
						"desc": {
							"query": "进修",
							"boost": 2
						}
					}
				}
			]
		}
	}
}

1.3 过滤器

对搜索出来的结果进行数据过滤(即查询后,对结果数据的筛选),不会到 ES 库里去搜,不会计算文档的相关度分数,所以过滤的性能会比较高,过滤器可以和全文搜索结合在一起使用。
post_filter 元素和上述的 query 元素一样,都是顶层元素,但 post_filter 不会计算数据的匹配度相关性分数,不会据分数排序。
举例:

POST /{indexName}/_doc/_search
// term
{
	"query": {
		"term": {
			{fieldName2}: {searchTxt2},
		}
	},
	// money 字段属性需要大于 60,小于 100
	"post_filter": {
		"range": {
			"money": {
				"gt": 60,
				"lt": 100
			}
		}
	}
}

1.4 排序

sort 元素和上述的 query 元素一样,都是顶层元素。

POST /{indexName}/_doc/_search
// term
{
	"query": {
		"term": {
			{fieldName2}: {searchTxt2},
		}
	},
	"sort": [
		{
			{fieldName1}: "desc"
		},
		{
			{fieldName2}: "asc"
		}
		...
	]
}

对文本排序,由于文本会被分词,所以排序的时候就会不知道用哪个分词后的内容去进行排序,所以需要在一开始设置文档字段的时候额外添加一个附属属性 keyword,用于排序:

POST /{indexName}/_mapping
{
  "properties": {
    {fieldName}: {
      "type": {typeName},
      "analyzer": {analyzerName},
      // 设置 keyword
      "fields": {
			"keyword": {
				"type": "keyword"
			}
		}
    }
  }
}

1.5 关键字高亮显示

POST /{indexName}/_doc/_search
// term
{
	"query": {
		"term": {
			{fieldName}: {searchTxt},
		},
		"term": {
			{fieldName2}: {searchTxt2},
		}
	},
	// 搜索词前后添加 span 标签
	"highlight": {
		"pre_tags": ["<span class="highlight">"],
		"post_tags": ["</span>"],
		"fields": {
			{fieldName}:{},
			{fieldName2}:{}
		}
	}
}

2 深度分页

深度分页其实就是指搜索的深浅度,比如第 10000 页,ES 在获取第 9999 条到 10009 条数据的时候,假如设置了多个主分片,那么它会从每个分片中拿出 10009 条数据集合在一起,再对这些汇总数据进行排序处理,最后获取对应的 10 数据,这样一来就会有性能的问题,ES 默认是不支持操作一万条数据以上的分页查询,而我们也应该避免深度分页操作(限制分页页数)
通过设置 index_max_result_window 来突破限制

PUT /{indexName}/_settings
{
	"index_max_result_window": "20000"
}

2.1 scroll 滚动搜索

一次性查询一万加的数据,往往会造成性能影响,因为数据量太多,这时候就可以使用滚动搜索。
滚动搜索可以先查询出一些数据,然后再接着依次往下查询,查询的时候会返回一个滚动 ID,相当于一个 锚标记,接下来的每次查询都需要携带上一次返回的 锚标记,值得注意的是,如果有数据变更,搜索出来的数据也只是旧版本的数据。
第一次滚动查询:

// scroll=1m 相当于是一个session会话时间,搜索保持上下文1分钟
POST /{indexName}/_search?scroll=1m
{
	"query": {
		"match_all": {}
	},
	"sort": ["_doc"],
	"size": 5 // 设置单页查询数量
}

后续查询:

POST /_search/scroll
{
	"scroll": "1m", //相当于是一个session会话时间,搜索保持上下文1分钟
	"scroll_id": {scroll_id}
}

3 批量操作

bulk 操作和以往的普通的请求格式有区别,它不能支持格式化 json,它的发送数据格式是:

{action:{metadata}}\n
{request body}\n
{action:{metadata}}\n
{request body}\n
...
  • {action:{metadata}} 代表批量操作的类型,不同操作类型之间是可以混合在同一请求中使用的
  • create:新增,如果文档不存在,就创建他,存在则会报错,但不影响其它操作
  • index:创建或替换一个现有的文档
  • update:部分更新文档
  • delete:删除一个文档
  • \n 即是回车符(不是填写 \n 进去请求体中,而是直接按回车键即可),每行结尾必须填写的规范(包括最后一行
  • {request body}:增和改操作的内容,删除操作无需携带

metadata 中需要指定要操作的文档的 _index(索引表表名)、_type(固定值为 _doc)和 _id,其中 _index_type 可以直接在 url 中指定

  • create 操作
POST /_bulk 
// 或者 /{indexName}/_doc/_bulk 这样指定url的话,下方 metaData 就无需声明 _index 和 _type 
{"create":{"_index":{indexName1},"_type":"_doc","_id":{id1}}}
{{fieldName1}: {value1}, {fieldName2}: {value2}}
{"create":{"_index":{indexName1},"_type":"_doc","_id":{id2}}}
{{fieldName1}: {value3}, {fieldName2}: {value4}}
{"create":{"_index":{indexName2},"_type":"_doc","_id":{id3}}}
{{fieldName3}: {value5}, {fieldName4}: {value6}}
  • index 操作,创建或替换一个现有的文档
POST /{indexName}/_doc/_bulk
{"index":{"_id":{id1}}}
{{fieldName1}: {value1}, {fieldName2}: {value2}}
{"index":{"_id":{id2}}}
{{fieldName1}: {value3}, {fieldName2}: {value4}}
  • update 操作,创建或替换一个现有的文档
POST /{indexName}/_doc/_bulk
{"update":{"_id":{id1}}}
{"doc":{{fieldName1}: {value1}, {fieldName2}: {value2}}}
{"index":{"_id":{id2}}}
{"doc":{{fieldName1}: {value3}, {fieldName2}: {value4}}}
  • delete 操作,创建或替换一个现有的文档
POST /{indexName}/_doc/_bulk
{"delete":{"_id":{id1}}}
{"delete":{"_id":{id2}}}