数据库差异

ElasticSearch是一个基于Lucene的搜索服务器,提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。它使用Java开发,并作为Apache许可条款下的开源发布,是当前流行的企业级搜索引擎。

—— 摘自百度百科

对于非关系型数据库不熟悉的,可以了解一下:
【知乎好文】MongoDB 等 NoSQL 与关系型数据库相比,有什么优缺点及适用场景?https://www.zhihu.com/question/20059632

与关系型数据库中表结构不同,文档中可以嵌入数组和子文档,就像程序中的数组和成员变量一样。这是关系型数据库三范式不允许的。但实际中我们经常看到一对多和一对一的数据。比如,一篇博客文章的 Tag 列表作为文章的一部分非常直观,而把 Tag 与文章的从属关系单独放一张表里就不那么自然。再比如,一个订单下面的收货地址,包括省、市、区、街道和门牌,作为一个子文档,与订单的信用卡地址很容易区分开。更方便的是,嵌入的数组和子文档之上可以直接建立索引,比如我可以很快找到所有 Tag 包含 MongoDB 的文章。

其实,我挺喜欢关系型数据库那些理论的,SQL 语言可以很精确地形式化,赏心悦目。然而,三范式强调的「数据没有任何冗余」并不是今天程序员们最关心的问题。他们用着方便不方便才是更重要的问题。

类比关系型数据库

手册上简单的对比图:
Relational DB > Database > Table > Row > Column
Elasticsearch > Index > Type > Document > Field

但其实ES与关系型数据库的设计理念不同,所以与关系型数据库不存在一一对应的关系,阮一峰老师的博客中对ES的基本概念解释的比较清晰:

Index(索引)里面的单条记录称为Document(文档),使用JSON格式表示。同一个Index里面的Document,不要求有相同的结构,但最好保持相同,有利于提高搜索效率。
Document可以分组(Type),比如weather这个Index里面,可以按照城市分组(背景和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做Type,它是虚拟的逻辑分组,用来过滤Document。
⚠️不同的Type应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如products和logs)应该存成两个Index,而不是一个Index里面的两个Type(虽然可以做到)。
– 根据规划,Elastic 6.x版只允许每个Index包含一个Type,7.x版将会彻底移除Type。
https://www.elastic.co/cn/blog/index-type-parent-child-join-now-future-in-elasticsearch

查询DSL

Elasticsearch提供基于JSON的完整查询DSL(域特定语言)来定义查询。将Query DSL视为查询的AST(抽象语法树),由两种类型的子句组成:
叶子查询从句:match、term、range
复合查询从句:bool、dis_max

match
GET /_search
{
	"query":{
		"match": {
			"message": "this that"
		}
	}
}

⚠️这种写法会查询所有message包含thisthat的文档,查询辅助还可以添加operator(or/and)、analyzer、lenient(是否忽略错误data-type)
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html 当message的查询词是QUICK!时:
将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。
用 term 查询计算每个文档相关度评分 _score ,这是种将 词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。参见 相关性的介绍 。
https://www.elastic.co/guide/cn/elasticsearch/guide/current/match-query.html

term
GET /_search
{
	"query":{
		"term":{
			"user":{
				"value": "Kimchy",
				"boost": 1.0
			}
		}
	}
}

用于查询一个特定的value,比如价格、ID、用户名

⚠️⚠️避免在text字段使用term,默认情况下text字段会被Elasticsearch预先处理,如去掉符号、分词成tokens、变为小写等
具体可以看一个Full text的查询例子:
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html

terms: 查询的字段为数组时使用。

对比match/term总结:前者用于text匹配,后者用于精确字段匹配。

range
GET _search
{
	"query":{
		"range":{
			"timestamp":{
				"time_zone": "+1:00",
				"gte": "2015-01-01 00:00:00",
				"lte": "now"	
			}
		}
	}
}

比大小:gt > ,gte >= ,lt > ,lte <=
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html

bool
POST _search
{
	"query":{
		"bool":{
			"must": {
				"term": {"user": "kimchy"}
			},
			"filter": {
				"term": {"tag":"tech"}
			},
			"must_not": {
				"range": {
					"age": {"gte": 10, "lte": 20}
				}
			},
			"should": [
			{"term": {"tag": "wow"}},
			{"term": {"tag": "elasticsearch"}}
			],
			"minim_should_match": 1,
			"boost": 1.0
		}
	}
}

??? 计算score没看懂:
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

⚠️bool过滤和bool查询的区别: should的匹配规则
在过滤中,should相当于or,至少匹配一个;
在查询中,所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢? 默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。
https://www.elastic.co/guide/cn/elasticsearch/guide/current/bool-query.html


聚合

两个基本概念:桶、指标(这就是全部)
类比SQL语句:SELECT COUNT(color) FROM table GROUP BY color
指标:COUNT(color)
桶:GROUP BY color
(以上为文档例子,个人感觉牵强)

详细说明:
——
1、张三属于男性雇员桶、西二旗属于北京桶、10月5号属于10月桶……满足特定条件的文档的集合。
2、桶可以嵌套,西二旗属于北京桶,北京属于中国桶。
3、Elasticsearch有很多中类型的桶,能让你通过很多中方式来划分文档(时间、最受欢迎的词、年龄区间、地理位置等等)

指标 ——
1、最小值、平均值、最大值、汇总……桶让我们划分到有意义的集合,但是最终我们需要的是对这些桶内的文档进行一些指标的计算。
2、指标能让你计算像平均工资、最高售出价格、95%的查询延迟等。

组合:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_combining_the_two.html

哪个汽车的销量最好:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_aggregation_test_drive.html

GET /_search
{
	"aggs":
	{
		"popular_colors" : {
			"terms" : {
				"field": "color"
			}
		}
	}
}

在本例中,我们定义了一个单terms桶,这个terms桶会为每个碰到的唯一词项动态创建新的桶。因为我们告诉它使用color字段,所以terms桶会为每个颜色动态创建新桶。
结果读取为:res = aggs.popular_colors.buckets

嵌套对象

nested结构是NoSQL的一种高级特性,当我们搜索时,嵌套结构会扁平化处理,失去嵌套对象的对应关系,为此使用nested类型构建嵌套对象映射

{
	"query" : {
		"bool" : {
			"must" : {
				{ "match" : { "title" : "eggs" }},
				{
					"nested" : {
						"path" : "comments",
						"query" : {
							"bool" : {
								"must" : [
									{ "match" : { "comments.name" : "john"}},
									{ "match" : { "comments.age" : 28 }}
								]
							}
						}
					}
				}
			}
		}
	}
}



实际使用:

精确值查找:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_exact_values.html

组合过滤器:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/combining-filters.html

查找多个term(terms:[]) / 精准查找:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_multiple_exact_values.html

exist查询

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_dealing_with_null_values.html

{
	"query": {
		"constant_score": {
			"filter": {
				"exists" : {
					"field" : "poiGrade"
				}
			}
		}
	}
}
自动缓存行为编辑

在 Elasticsearch 的较早版本中,默认的行为是缓存一切可以缓存的对象。这也通常意味着系统缓存 bitsets 太富侵略性,从而因为清理缓存带来性能压力。不仅如此,尽管很多过滤器都很容易被评价,但本质上是慢于缓存的(以及从缓存中复用)。缓存这些过滤器的意义不大,因为可以简单地再次执行过滤器。

检查一个倒排是非常快的,然后绝大多数查询组件却很少使用它。例如 term 过滤字段 “user_id” :如果有上百万的用户,每个具体的用户 ID 出现的概率都很小。那么为这个过滤器缓存 bitsets 就不是很合算,因为缓存的结果很可能在重用之前就被剔除了。

这种缓存的扰动对性能有着严重的影响。更严重的是,它让开发者难以区分有良好表现的缓存以及无用缓存。

为了解决问题,Elasticsearch 会基于使用频次自动缓存查询。如果一个非评分查询在最近的 256 次查询中被使用过(次数取决于查询类型),那么这个查询就会作为缓存的候选。但是,并不是所有的片段都能保证缓存 bitset 。只有那些文档数量超过 10,000 (或超过总文档数量的 3% )才会缓存 bitset 。因为小的片段可以很快的进行搜索和合并,这里缓存的意义不大。

一旦缓存了,非评分计算的 bitset 会一直驻留在缓存中直到它被剔除。剔除规则是基于 LRU 的:一旦缓存满了,最近最少使用的过滤器会被剔除。