结构化搜索是指针对具有内在结构的数据进行检索的过程。比如日期、时间和数字都是结构化的,它们有精确的格式。文本也是可以 格式化的,比如彩色笔的颜色可以有red、green、blue等,文章也可以有关键词,网站商品也都有id等唯一标识。 结构化查询的结果总是非是即否,要么存在结果集中,要么不在。不关心文件的相关度或评分,只有文档的包括或排除处理。
进行精确值查找时,使用filters会有比较快的执行速度,而且不会计算相关度,跳过了整个评分的阶段,而且容易被缓存。关于过滤器的缓存,参考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/filter-caching.html
elasticsearch有查询表达式---query DSL,在用于查找精确值时,使用term也能达到相同的效果,term可用于数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。
POST /my_index2/my_type/1
{"price":10}
GET /my_index2/my_type/_search
{
"query":{
"term": {
"price": {
"value": 10
}
}
}
}
结果为:
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "my_index2",
"_type": "my_type",
"_id": "1",
"_score": 1,
"_source": {
"price": 10
}
}
]
}
}
可以看到有评分产生。
若想要不进行评分计算,只希望对文档进行包括或排除的计算,所以我们会使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。 最终组合的结果是一个 constant_score 查询,它包含一个 term 查询:
GET /my_index2/my_type/_search
{
"query":{
"constant_score": {
"filter": {
"term": {
"price": {
"value": 10
}
}
}
}
}
}
返回的结果为:
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "my_index2",
"_type": "my_type",
"_id": "1",
"_score": 1,
"_source": {
"price": 10
}
}
]
}
}
我们用 constant_score 将 term 查询转化成为过滤器,查询置于 filter 语句内不进行评分或相关度的计算,所以所有的结果都会返回一个默认评分 1 。
文本没有被设置成not_analyzed时会被分词,如果要让字段具有精确值,需要设置成not_analyzed。在修改索引mapping时,要先删除旧索引再新建一个正确映射的新索引。 具体查询方式与上面相同:
GET /my_index3/my_type/_search
{
"query":{
"constant_score": {
"filter": {
"term": {
"title": {
"value": "比特币"
}
}
}
}
}
}
在内部,ES会进行非评分查询时执行多个操作:
从概念上记住非评分计算是首先执行的,这将有助于写出高效又快速的搜索请求。
一般需要使用bool (布尔)过滤器。 这是个 复合过滤器(compound filter) ,它可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
就这么简单! 当我们需要多个过滤器时,只须将它们置入 bool 过滤器的不同部分进行嵌套即可。 就相当于用很多个if/else进行组合,能组合出一个很复杂的过程。可以将term过滤器、range过滤器等通过bool过滤器进行组合处理。
term查询对单个值非常有用,如果要查找价格字段值为20或30的文档时,可以使用多个term查询,也可以使用terms查询。
GET /my_index2/my_type/_search
{
"query":{
"constant_score": {
"filter": {
"terms": {
"price": [20,30]
}
}
}
}
}
需要注意的是,term和terms是包含匹配,而不是等值判断,也就是说除了能匹配上的term,也允许其他term存在。
在索引数组数据时,如果需要根据数组数量匹配,可以多索引一个字段,用来保存数量。
{ "tags" : ["search", "open_source"], "tag_count" : 2 }
搜索时也要传入数量:
GET /my_index/my_type/_search
{
"query": {
"constant_score" : {
"filter" : {
"bool" : {
"must" : [
{ "term" : { "tags" : "search" } },
{ "term" : { "tag_count" : 1 } }
]
}
}
}
}
}
range 查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:
查询20到40之间价格的:
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lt" : 40
}
}
}
}
}
}
(1) 直接日期查询:
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}
(2)对日期计算(date math):
"range" : {
"timestamp" : {
"gt" : "now-1h"
}
}
这个过滤器会一直查找时间戳在过去一个小时内的所有文档,让过滤器作为一个时间 滑动窗口(sliding window) 来过滤文档。
(3)针对具体时间计算:
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-01 00:00:00||+1M"
}
}
只要在某个日期后加上一个双管符号 (||) 并紧跟一个日期数学表达式。
更多关于日期的可以参考:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping-date-format.html
字符串范围可采用 字典顺序(lexicographically) 或字母顺序(alphabetically)。在倒排索引中的词项就是采取字典顺序(lexicographically)排列的,这也是字符串范围可以使用这个顺序来确定的原因。
执行效率:
数字和日期字段的索引方式使高效地范围计算成为可能。 但字符串却并非如此,要想对其使用范围过滤,Elasticsearch 实际上是在为范围内的每个词项都执行 term 过滤器,这会比日期或数字的范围过滤慢许多。 字符串范围在过滤 低基数(low cardinality) 字段(即只有少量唯一词项)时可以正常工作,但是唯一词项越多,字符串范围的计算会越慢。
null, [] (空数组)和 [null] 所有这些都是无法存于倒排索引中。针对这些字段,在ES中是什么都不存的。 在查询时,需要进行处理。
对于空值,感觉需要在业务上进行处理,尽量避免添加空值null或字符串null的情况。
exists与missing可以处理普通字段和一个对象的内部字段。
{
"name" : {
"first" : "John",
"last" : "Smith"
}
}
可以对name.first 与 name.last进行非空校验
其核心实际是采用一个 bitset 记录与过滤器匹配的文档。Elasticsearch 积极地把这些 bitset 缓存起来以备随后使用。一旦缓存成功,bitset 可以复用任何已使用过的相同过滤器,而无需再次计算整个过滤器。 这些 bitsets 缓存是“智能”的:它们以增量方式更新。当我们索引新文档时,只需将那些新文档加入已有 bitset,而不是对整个缓存一遍又一遍的重复计算。和系统其他部分一样,过滤器是实时的,我们无需担心缓存过期问题。
Elasticsearch 会基于使用频次自动缓存查询。如果一个非评分查询在最近的 256 次查询中被使用过(次数取决于查询类型),那么这个查询就会作为缓存的候选。但是,并不是所有的片段都能保证缓存 bitset 。只有那些文档数量超过 10,000 (或超过总文档数量的 3% )才会缓存 bitset 。因为小的片段可以很快的进行搜索和合并,这里缓存的意义不大。
一旦缓存了,非评分计算的 bitset 会一直驻留在缓存中直到它被剔除。剔除规则是基于 LRU 的:一旦缓存满了,最近最少使用的过滤器会被剔除。
参考:https://www.elastic.co/cn/blog/frame-of-reference-and-roaring-bitmaps
详细代码如下,关于代码解析部分,请关注后面的文章:
/**
* This is a cache for {@link BitDocIdSet} based filters and is unbounded by size or time.
* <p>
* Use this cache with care, only components that require that a filter is to be materialized as a {@link BitDocIdSet}
* and require that it should always be around should use this cache, otherwise the
* {@link org.elasticsearch.index.cache.query.QueryCache} should be used instead.
*/
public final class BitsetFilterCache extends AbstractIndexComponent implements IndexReader.ClosedListener, RemovalListener<IndexReader.CacheKey, Cache<Query, BitsetFilterCache.Value>>, Closeable {
public static final Setting<Boolean> INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING =
Setting.boolSetting("index.load_fixed_bitset_filters_eagerly", true, Property.IndexScope);
private final boolean loadRandomAccessFiltersEagerly;
private final Cache<IndexReader.CacheKey, Cache<Query, Value>> loadedFilters;
private final Listener listener;
public BitsetFilterCache(IndexSettings indexSettings, Listener listener) {
super(indexSettings);
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
this.loadRandomAccessFiltersEagerly = this.indexSettings.getValue(INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING);
this.loadedFilters = CacheBuilder.<IndexReader.CacheKey, Cache<Query, Value>>builder().removalListener(this).build();
this.listener = listener;
}
在title属性上搜索"比特币"进行全文搜索:
GET /my_index3/my_type/_search
{
"query":{
"match":{
"title":"比特币"
}
}
}
结果默认按照相关性得分(也就是每个文档跟查询的匹配程度)排序。这点和传统关系型数据库完全不同,数据库中的记录要么匹配要么不匹配。
看如下查询:
GET /my_index3/my_type/_search
{
"query":{
"match":{
"title":"比特币 以太坊"
}
}
}
GET /my_index3/my_type/_search
{
"query":{
"match_phrase":{
"title":"比特币 以太坊"
}
}
}
上面必须完全匹配短语"比特币 以太坊","比特币"和"以太坊"必须同时匹配,而且二者以短语的形式紧挨着。
想要某些片段高亮显示时,在执行查询时需要增加一个新的highlight参数:
GET /my_index3/my_type/_search
{
"query":{
"match":{
"title":"比特币 以太坊"
}
},
"highlight":{
"fields":{
"title":{}
}
}
}
返回结果为:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"title": "比特币 以太坊"
},
"highlight": {
"title": [
"<em>比特币</em> <em>以太坊</em>"
]
}
}
]
}
}