1.倒排索引
1》mysql等数据库使用正向索引
如果根据id(索引列查询),速度会非常快,但是如果根据非索引列,并且模糊查询时,速度会非常慢,流程如下(比如id是索引列,title是要模糊查询的非索引列)
1)用户搜索数据,条件是title符合"%手机%"
2)逐行获取数据,比如id为1的数据
3)判断数据中的title是否符合用户搜索条件
4)如果符合则放入结果集,不符合则丢弃。回到步骤1
2》所以要用到倒排索引,倒排索引的搜索流程如下:
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以发现id为1 2 3的记录,或者有手机,或者有华为,或者两个都有,
4)拿着文档id 1 2 3到正向索引中查找具体文档。
3》那么为什么一个叫做正向索引,一个叫做倒排索引呢?
-
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
-
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
-
倒排索引虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
2. ElasticSearch 和mysql 的对应关系
3.索引库dsl操作
3.1mapping映射属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
-
数值:long、integer、short、byte、double、float、
-
布尔:boolean
-
日期:date
-
对象:object
-
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
例如下面的json文档:
{ "age": 21, "weight": 52.1, "isMarried": false, "info": "黑马程序员Java讲师", "email": "zy@itcast.cn", "score": [99.1, 99.5, 98.9], "name": { "firstName": "云", "lastName": "赵" } }
对应的每个字段映射(mapping):
-
age:类型为 integer;参与搜索,因此需要index为true;无需分词器
-
weight:类型为float;参与搜索,因此需要index为true;无需分词器
-
isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
-
info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
-
email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
-
score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
-
name:类型为object,需要定义多个子属性
-
name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
-
name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
-
3.2dsl操作索引库
json格式,用于ElasticSearch的语句,进行增删改查操作
1》创建索引库(索引库相当于mysql中的表)
PUT /heima 索引库名
{
"mappings": {
"properties": {
"info":{ 文档名
"type": "text", 类型
"analyzer": "ik_smart" 使用的分词器
},
"email":{
"type": "keyword",
"index": "false" 是否使用倒排索引排序 ,电子邮箱,不会根据电子邮箱查询,所以是false,其他属性默认是true
},
"name":{
"properties": { 因为name中有姓和名两个属性,所以要写个properties
"firstName": {
"type": "keyword"
}
}
},
}
}
}
2》查询索引库结构
GET /heima
3》修改索引库结构
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
只能增加新的属性
PUT /heima/_mapping
{
"properties":{
"age":{
"type":"integer"
}
}
}
4》删除索引库
DELETE /索引库名
4.文档dsl操作
1.添加一条文档(记录) 如果该文档已存在,则更新该文档
POST /heima/_doc/1
{
"info":"黑马程序员java讲师",
"email":"1960703672@qq.com",
"name":{
"firstname":"云",
"lastname":"ZHAO"
}
}
2. 查询一条文档(记录)
GET /heima/_doc/1
3.删除一条文档(记录)
DELETE /heima/_doc/1
4.修改一条文档,如果不存在,就创建新的该条文档
POST /heima/_doc/1
{
"info":"黑马程序员java讲师",
"email":"1960703672@qq.com",
"name":{
"firstname":"云",
"lastname":"ZHAO"
}
}
5.局部修改文档字段
POST /heima/_update/1
{
"doc":{
"email":"1195"
}
}
5.dsl创建索引练习及文档查询操作
-
location:地理坐标,里面包含精度、纬度
-
all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索,
会创建一个新的all字段,包含使用了all的其他字段(下面代码中的name,brand,city),并创建反向索引
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
文档查询操作
1.查询所有文档
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
2. 全文检索查询
模糊查询info中有java工程师的,模糊查询不太准确,因为是根据分词来倒排索引查询的,比如分成了java 和工程师,会去找eleaticsearch分词后的字典中工程师和java对应的id,所以比如查询参数的info是 java 哈哈哈哈 工程师,是可以查询出来的,但如果是java 工程大师,工程大师在eleaticsearch分词后的字典中没有(就是eleaticsearch的info中没有工程大师info的记录),就查询不出来。
GET /heima2/_search
{
"query": {
"match": {"info":"java工程师"}
}
}
在定义索引(表结构)时,定义了all字段,关联了 酒店名称 品牌 城市 三个字段
所以可以直接根据all字段查询,会查询酒店名称 或者品牌 或者城市中有上海的
GET /hotel/_search
{
"query": {
"match": {
"all": "上海"
}
}
}
3.全文检索查询2
如果没有定义all字段,想查询酒店名称 或者品牌 或者城市中有上海的 ,就必须使用如下的查询方式,速度很慢,推荐在定义表时使用all字段,查询all字段。
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "杭州",
"fields": ["brand","name","bussiness"]
}
}
}
4.term精确查询
查询城市是上海的 (比如有个上海市,则不能查询到)
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
5.范围查询 要查询的字段必须是数字类型,可以比较的。
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 1000,
"lte": 2000
}
}
}
}
6.地理位置查询1(不常用)画一个矩形,查询在该矩形范围内的所有地址
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 31.1,
"lon": 121.5
},
"bottom_right": {
"lat": 30.9,
"lon": 0.0
}
}
}
}
}
7.地理位置查询2 (很常用) 附近的人,查询距离某个点什么范围的记录
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "15km",
"location": "31.21,121.5"
}
}
}
8.设置查询的优先级
在使用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。比如在搜索虹桥如家时:
[
{
"_score" : 17.850193,
"_source" : {
"name" : "虹桥如家酒店真不错",
}
},
{
"_score" : 12.259849,
"_source" : {
"name" : "外滩如家酒店真不错",
}
},
{
"_score" : 11.91091,
"_source" : {
"name" : "迪士尼如家酒店真不错",
}
}
]
算法主要有两种:
而这个算分结果,是可以被干预的,比如,收了如家的广告费,想要如家的搜索结果提前
实现:
原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
-
过滤条件:filter部分,符合该条件的文档才会重新算分
-
算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
-
weight:函数结果是常量
-
field_value_factor:以文档中的某个字段值作为函数结果
-
random_score:以随机数作为函数结果
-
script_score:自定义算分函数算法
-
-
运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
-
multiply:相乘
-
replace:用function score替换query score
-
其它,例如:sum、avg、max、min
-
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "外滩"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
]
}
}
}
查询结果:
9. 布尔查询
一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:
-
must:必须匹配每个子查询,类似“与”
-
should:选择性匹配子查询,类似“或”
-
must_not:必须不匹配,不参与算分,类似“非”
-
filter:必须匹配,不参与算分
-
比如在搜索酒店时,除了关键字搜索外,我们还可能根据品牌、价格、城市等字段做过滤:
每一个不同的字段,其查询的条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就必须用bool查询了。
-
查询城市是上海的,品牌是皇冠假日或者华美达的,价格大于500的,评分大于45的酒店
-
#布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询 GET /hotel/_search { "query": { "bool": { "must": [ { "term": { "city": "上海" } } ], "should": [ {"term": {"brand": "皇冠假日"}}, {"term":{"brand":"华美达"}} ], "must_not": [ {"range": {"price": { "lte": 500 }}} ], "filter": [ { "range": { "score": { "gte": 45 } } } ] } } }
-
10.根据地理位置排序(查询距离某个点最近的)
-
GET /hotel/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance": { "location": { "lat": 40.476483, "lon": 115.97481 }, "order": "asc" } } ] }
-
6.搜索结果处理
-
6.1手动排序
-
elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
手动排序后,不会再打分,也就没有_score属性了。
-
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "FIELD": "desc" // 排序字段、排序方式ASC、DESC } ] }
6.2分页查询
-
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
-
from:从第几个文档开始
-
size:总共查询几个文档
-
GET /hotel/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 }
6.3高亮
-
高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
-
默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
-
如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
#fields指定高亮的标签,如果搜索字段和高亮字段(即city和name)不是一个,需要require_field_match设为false GET /hotel/_search { "query": { "match": { "city": "上海" } }, "highlight": { "fields": { "name": { "require_field_match": "false" } } } }