个人专题目录](//www.greatytc.com/p/140e2a59db2c)
1. elasticsearch文档基本查询
1.1 ids查询
ids查询是一类简单的查询,它过滤返回的文档只包含其中指定标识符的文档,该查询默认指定作用在“_id”上面。
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"ids": {
"values": [
"1145177",
"1170772"
],
"boost": 1
}
}
}
@Override
public void idsQuery(String index, String... ids) throws Exception {
builder(index, QueryBuilders.idsQuery().addIds(ids));
}
@Test
public void testIdsQuery() throws Exception {
baseQuery.idsQuery(Constants.INDEX_NAME, "1145177", "1170772");
}
1.2 prefix查询
前缀查询,可以使我们找到某个字段以给定前缀开头的文档。
最典型的使用场景,一般是在文本框录入的时候的联想功能。
例如想查到公司名以“中国”开头的文档:
#prefix 查询
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"prefix": {
"title": {
"value": "三",
"boost": 1
}
}
}
}
/**
* 模糊查询:prefixQuery
*/
@Override
public void prefixQuery(String index, String field, String prefixValue) throws Exception {
builder(index, QueryBuilders.prefixQuery(field, prefixValue));
}
@Test
public void testPrefixQuery() throws Exception {
baseQuery.prefixQuery(Constants.INDEX_NAME, "title", "三");
}
1.3 fuzzy查询
fuzzy才是实现真正的模糊查询,我们输入的字符可以是个大概,他可以根据我们输入的文字大概进行匹配查询。返回包含与搜索词类似的词的文档,该词由Levenshtein编辑距离度量。
包括以下几种情况:
更改角色(box→fox)
删除字符(aple→apple)
插入字符(sick→sic)
调换两个相邻字符(ACT→CAT)
参数说明:
prefix_length
不能被 “模糊化” 的初始字符数。 大部分的拼写错误发生在词的结尾,而不是词的开始。 例如通过将
prefix_length
设置为3
,你可能够显著降低匹配的词项数量。
例如用户在查询过程中把“中国移动”输为了“中国联动”:
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"fuzzy": {
"categoryName": {
"value": "平电视",
"fuzziness": "AUTO",
"prefix_length": 0,
"max_expansions": 50,#控制查询的数量,注意不是返回结果的最大值,而是在查询过程中最多去匹配到多少条,比如此处 50 条,即便有 1 万条能匹配到的,当查询过程中匹配到 50 条的时候就不再继续查询直接返回结果
"transpositions": true,
"boost": 1
}
}
}
}
@Override
public void fuzzyQuery(String index, String field, String keyword) throws Exception {
builder(index, QueryBuilders.fuzzyQuery(field, keyword));
}
把prefixLength(2)参数改为prefixLength(3)就查不到结果了,不能被 “模糊化” 的初始字符变成了中国联
。
@Test
public void testFuzzyQuery() throws Exception {
baseQuery.fuzzyQuery(Constants.INDEX_NAME, "title", "平电视");
}
1.4 wildcard查询
wildcard查询允许我们在要查询的内容中使用通配符*和?,和SQL语句。
<font color=red>注意:wildcard查询不注意查询性能,应尽可能避免使用。</font>
例如用户在查询公司名以“中国”开头的文档:
#wildcard 查询。查询条件分词,模糊查询
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"wildcard": {
"title": {
"wildcard": "三*",
"boost": 1
}
}
}
}
/**
* 模糊查询:WildcardQuery
*/
@Override
public void wildCardQuery(String index, String field, String wildcardValue) throws Exception {
builder(index, QueryBuilders.wildcardQuery(field, wildcardValue));
}
@Test
public void testWildCardQuery() throws Exception {
baseQuery.wildCardQuery(Constants.INDEX_NAME, "title", "三*");
}
1.5 range查询
本章到目前为止,对于数字,只介绍如何处理精确值查询。 实际上,对数字范围进行过滤有时会更有用。例如,我们可能想要查找所有价格大于 40 美元的产品。
在 SQL 中,范围查询可以表示为:
SELECT document
FROM products
WHERE price BETWEEN 20 AND 40
Elasticsearch 有 range
查询, 不出所料地,可以用它来查找处于某个范围内的文档:
"range" : {
"price" : {
"gte" : 20,
"lte" : 40
}
}
range
查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:
gt
:>
大于(greater than)lt
:<
小于(less than)gte
:>=
大于或等于(greater than or equal to)lte
:<=
小于或等于(less than or equal to)
例如要查询出短信状态报告响应时间在1-10秒之内的文档:
#范围查询
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"range": {
"price": {
"from": 2000,
"to": 3000,
"include_lower": true,
"include_upper": true,
"boost": 1
}
}
}
}
/**
* 范围查询:rangeQuery
*/
@Override
public void rangeQuery(String index, String field, int from, int to) throws Exception {
//builder(index, QueryBuilders.rangeQuery(field).from(from).to(to));
builder(index, QueryBuilders.rangeQuery(field).gte(2000).lte(3000));
}
@Test
public void testRangeQuery() throws Exception {
baseQuery.rangeQuery(Constants.INDEX_NAME, "price", 2000, 3000);
}
1.6 regexp查询
正则表达式查询,wildcard和regexp查询的工作方式和prefix查询完全一样。它们也需要遍历倒排索引中的词条列表来找到所有的匹配词条,然后逐个词条地收集对应的文档ID。它们和prefix查询的唯一区别在于它们能够支持更加复杂的模式。
这也意味着使用它们存在相同的风险。对一个含有很多不同词条的字段运行这类查询是非常消耗资源的。避免使用一个以通配符开头的模式(比如,*foo)。
尽管对于前缀匹配,可以在索引期间准备你的数据让它更加高效,通配符和正则表达式匹配只能在查询期间被完成。虽然使用场景有限,但是这些查询也有它们的用武之地。
<font color=red>注意:prefix,wildcard以及regexp查询基于词条进行操作。如果你在一个analyzed字段上使用了它们,它们会检查字段中的每个词条,而不是整个字段。</font>
例如查找长号码(longCode)以1069开头后面是数字的文档:
#模糊查询:regexpQuery
POST /book-index/_search
{
"from": 0,
"size": 100,
"query": {
"regexp": {
"title": {
"value": """\\w+(.)*""",
"flags_value": 65535,
"max_determinized_states": 10000,
"boost": 1
}
}
}
}
/**
* 模糊查询:regexpQuery
*/
@Override
public void regexQuery(String index, String field, String regex) throws Exception {
QueryBuilders.regexpQuery("title", "\\w+(.)*");
builder(index, QueryBuilders.regexpQuery(field, regex));
}
@Test
public void testRegexQuery() throws Exception {
baseQuery.regexQuery(Constants.INDEX_NAME, "title", "\\\\w+(.)*");
}
1.7 scroll查询
ES对于from+size的个数是有限制的,二者之和不能超过1w。当所请求的数据总量大于1w时,可用scroll来代替from+size。
原理
ES的搜索是分2个阶段进行的,即Query阶段和Fetch阶段。 Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。 而Fetch阶段比较重,需要将每个shard的结果取回,在协调结点进行全局排序。 通过From+size这种方式分批获取数据的时候,随着from加大,需要全局排序并丢弃的结果数量随之上升,性能越来越差。
而Scroll查询,先做轻量级的Query阶段以后,免去了繁重的全局排序过程。 它只是将查询结果集,也就是doc id列表保留在一个上下文里, 之后每次分批取回的时候,只需根据设置的size,在每个shard内部按照一定顺序(默认doc_id续), 取回这个size数量的文档即可。
使用场景
由此也可以看出scroll不适合支持那种实时的和用户交互的前端分页工作,其主要用途用于从ES集群分批拉取大量结果集的情况,一般都是offline的应用场景。 比如需要将非常大的结果集拉取出来,存放到其他系统处理,或者需要做大索引的reindex等等。
不要把 scroll
用于实时请求,它主要用于大数据量的场景。例如:将一个索引的内容索引到另一个不同配置的新索引中。
代码示例
例如滚动查询所有文档:
POST /book-index/_search?scroll=1m
{
"query": {
"match_all" : {}
},
"sort": [
"_doc"
]
}
POST /_search/scroll #继续使用滚动查询
{
"scroll": "1m", #设置时间
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTlU3Z1FCT1lWVHdadlhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVlR3WnZYTmFXZw==" #指定在哪个scroll的基础上继续查询
}
清除scroll
虽然我们在设置开启scroll时,设置了一个scroll的存活时间,但是如果能够在使用完顺手关闭,可以提早释放资源,降低ES的负担.
DELETE /_search/scroll
{
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB"
}
public void scrollQuery(String index) throws Exception {
SearchRequest searchRequest = new SearchRequest(index);
//值不需要足够长来处理所有数据—它只需要足够长来处理前一批结果。每个滚动请求(带有滚动参数)设置一个新的过期时间。
//创建一个有效期为 1 分钟的滚动
Scroll scroll = new Scroll(TimeValue.timeValueMillis(1L));
//设置滚动查询
searchRequest.scroll(scroll);
//创建查询条件的封装对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//一般情况下我们的滚动查询需要查询所有数据
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//设定每次返回多少条数据
searchSourceBuilder.size(5);
//对我们的请求指定查询条件
searchRequest.source(searchSourceBuilder);
log.info("string:" + searchRequest.source());
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
String scrollId = searchResponse.getScrollId();
SearchHit[] searchHits = searchResponse.getHits().getHits();
log.info("-----首页-----");
for (SearchHit searchHit : searchHits) {
log.info(searchHit.getSourceAsString());
}
//遍历搜索命中的数据,直到没有数据
while (searchHits != null && searchHits.length > 0) {
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(scroll);
log.info("string:" + scrollRequest.toString());
try {
searchResponse = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
scrollId = searchResponse.getScrollId();
searchHits = searchResponse.getHits().getHits();
if (searchHits != null && searchHits.length > 0) {
log.info("-----下一页-----");
for (SearchHit searchHit : searchHits) {
log.info(searchHit.getSourceAsString());
}
}
}
//清除滚屏
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
//也可以选择setScrollIds()将多个scrollId一起使用
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = null;
try {
clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
boolean succeeded = clearScrollResponse != null && clearScrollResponse.isSucceeded();
log.info("succeeded:" + succeeded);
}
@Test
public void testScrollQuery() throws Exception {
baseQuery.scrollQuery(indexName,type);
}
2. delete-by-query
删除查询的文档,由于每个文档都需要单独被删除,查询大量文档可能需要很长的时间。
注意:
不要使用delete-by-query来删除一个索引下的全部或者大部分文档,确实需要的话,可以创建一个新的索引,然后将需要保留的文档重新索引到新的索引中去,这样你就可以直接删掉旧索引。
POST /book-index/_delete_by_query
{
"query": { #此处使用之前的查询条件
"match_all": {}
}
}
@Override
public void deleteByQuery(String index, QueryBuilder queryBuilder) throws Exception {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(index).setQuery(queryBuilder);
BulkByScrollResponse response = restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
long deleted = response.getDeleted();
log.info("删除的数量是{}", deleted);
log.info(response);
}
@Test
public void testDeleteByQuery() throws Exception {
docService.deleteByQuery(Constants.INDEX_NAME, QueryBuilders.termQuery("id", "0"));
}