使用 mysql
搜索时会面临的问题:
- 性能低下:当数据量比较小的时候,问题不大,当数据量比较大或者并发比较高时,用
mysql
的like
查询,性能是比较低的 - 没有相关性的排名:例如像搜索引擎它们,匹配度越高的,排名越靠前,
mysql
的like
没有这个功能 - 没有全文搜索
- 没有分词功能
什么是全文搜索
比如说在一件商品中,很多属性都有介绍的功能,比如标题、副标题、简介、详情等,数据库在存储时,需要为他们分别建立字段,但在搜索时,只需要输入一个关键词,就想搜索到所相关的商品,在数据库中是比较难做到的
在比如日志搜索,每一种不一样的日志,都要写 sql
查询,如果后面日志发生变化,还要修改 sql
对于这种不规则的数据,任何时候都可以搜索,这就是全文搜索
ElasticSearch
就是一个全文搜索引擎,它可以帮助我们解决上面的问题
它是一个分布式可扩展的实时搜索和分析引擎,是建立在 Lucene
基础之上的,提供基于 RESTful
的 web
接口
安装 ElasticSearch 和 Kibana
使用 docker
安装 ElasticSearch
和 Kibana
安装 ElasticSearch
bash
docker run --name go-elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms128m -Xmx256m" --network network1 --network-alias go-es -d elasticsearch:7.10.1
访问:localhost:9200
可以看到 ElasticSearch
的信息
json
{
"name": "d6c7d3b1c14b",
"cluster_name": "docker-cluster",
"cluster_uuid": "JqlWTdi3RaCxcJwDSnSoRg",
"version": {
"number": "7.10.1", // 版本号
"build_flavor": "default",
"build_type": "docker",
"build_hash": "1c34507e66d7db1211f66f3513706fdf548736aa",
"build_date": "2020-12-05T01:00:33.671820Z",
"build_snapshot": false,
"lucene_version": "8.7.0",
"minimum_wire_compatibility_version": "6.8.0",
"minimum_index_compatibility_version": "6.0.0-beta1"
},
"tagline": "You Know, for Search"
}
安装 kibana
kiabana
是 elasticsearch
的可视化工具
bash
docker run --name go-kibana -e ELASTICSEARCH_HOSTS="http://go-elasticsearch:9200" -p 5601:5601 --network network1 --network-alias go-kibana -d kibana:7.10.1
通过 http://localhost:5601/app/home#/
访问 kibana
- 在运行容器时
ELASTICSEARCH_HOSTS
需要配置elasticsearch
的地址,这里使用的是docker
容器名kibana
的版本需要和elasticsearch
的版本一致
ES 基本概念
- 从
ES7.x
开始,索引index
可以理解为数据库中的table
ES7.x
开始type
被废弃了(现在固定值为_doc
),但还保留着,8.x
正式废弃document
可以理解为数据库中的一条记录field
可以理解为数据库中的字段mapping
可以理解为数据库的schema
基本使用
查看当前节点所有的索引
bash
GET _cat/indices
green open .apm-custom-link 36Wb9HwmSOuZOedi_uLlLg 1 0 0 0 208b 208b
green open .kibana_task_manager_1 TmhGqbXASUOtf4UVvTmsDQ 1 0 5 35 172kb 172kb
green open .apm-agent-configuration R7E5SWxFTOSVDVsQAjVwnA 1 0 0 0 208b 208b
green open .kibana-event-log-7.10.1-000001 -YHTgGvvT9G6ov2SgTL1CA 1 0 3 0 16.4kb 16.4kb
green open .kibana_1 hW6aAfKcQySo6NTxSwo_2w 1 0 24 9 2.1mb 2.1mb
- 状态(
green
) - 索引的健康状态,green
表示可用,yellow
表示索引有问题,red
表示不可用 - 是否开启(
open
) - 索引是否被开启使用 - 索引名(
.apm-custom-link
) - 索引的名称 UUID
(36Wb9HwmSOuZOedi_uLlLg
) - 索引的唯一标识符- 分片数(
1
) - 索引的主分片数 - 副本数(
0
) - 索引设置的副本分片数 Docs count
(0
) - 索引中的文档数Deleted docs
(0
) - 索引中被标记删除的文档数Store size
(208b
) - 索引主分片所使用的存储空间大小Pri.Store size
(208b
) - 索引所有主分片总存储空间大小
获取某个索引
bash
GET account
{
"account": {
"aliases": {},
"mappings": { # 对应的是数据库的表结构
"properties": {
"age": {
"type": "long"
},
"company": {
"properties": {
"address": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",
"provided_name": "account",
"creation_date": "1693545227630",
"number_of_replicas": "1",
"uuid": "y9lUDYf9Q1umYjUrnZ-mKA",
"version": {
"created": "7100199"
}
}
}
}
}
新建数据
使用 PUT
操作数据说明:
account
:索引名_doc
固定值1
:文档的唯一标识符,用PUT
必须指定
如果 account
不存在,会创建 account
bash
PUT /account/_doc/1
# 请求体
{
"name": "uccs",
"age": 18,
"company": [
{
"name": "astak",
"address": "shanghai"
},
{
"name": "astak2",
"address": "beijing"
}
]
}
# 响应体
{
"_index": "account", # 索引名
"_type": "_doc", # 固定值 _doc
"_id": "1", # 文档的唯一标识符,用 PUT 必须指定,用 POST ES 会自己生成
"_version": 1, # 版本号,第几次操作
"result": "created", # 如果数据不存在,这个值是 create;如果数据存在,这个值是 updated
"_shards": { # 分片信息
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0, # 乐观锁
"_primary_term": 1
}
使用 POST
操作数据说明:
- 新建数据时无需指定
id
,ES
会自动生成,同时result
的值是created
- 更新数据时需要指定
id
,同时result
的值是updated
- 多次运行会生成多条数据,可通过响应体中
id
和result
可知
bash
POST user/_doc
# 请求体
{
"name": "uccs",
"company": "imooc"
}
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "WYUzT4oBZ0WpQB8-3pDP", # 自动生成,每次运行都不一样
"_version": 1,
"result": "created", # 每次运行都是 created
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
新建数据时,如果数据不存在,会创建数据;如果数据存在,会报错
bash
POST user/_create/1
# 请求体
{
"name": "uccs",
"company": "imooc"
}
# 响应体
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [1])", # 错误的理由
"index_uuid": "oAG9ecTqTB6djw11e4OJYA",
"shard": "0",
"index": "user"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, document already exists (current version [1])",
"index_uuid": "oAG9ecTqTB6djw11e4OJYA",
"shard": "0",
"index": "user"
},
"status": 409
}
获取数据
获取数据的方式有两种形式:
- 通过
id
获取数据(知道数据的 id) - 搜索数据(将所有符合条件的数据都找到)
通过 id 获取数据
通过 id
获取数据,需要加上 _doc
返回的数据中,_source
是真正的数据
bash
GET user/_doc/1
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 2,
"_primary_term": 1,
"found": true, # 是否找到数据
"_source": { # 真正的数据
"name": "uccs",
"company": "imooc"
}
}
如果我们只想获取真正的数据,不需要其他的信息,可以使用 _source
过滤
也就是说将 _doc
替换成 _source
即可
bash
GET user/_source/1
# 响应体
{
"name": "uccs",
"company": "imooc"
}
搜索数据
有两种方式可以查询:
url
的形式查询,类似query
的形式,不过查询能力有限- 通过请求体的形式查询,查询能力比较强
使用 url 查询
下面的例子是个全量查询,会查询所有的索引,从响应结果中可以看到,它还查询了一些系统索引(不过现在已经说明以后会被废弃)
bash
GET _search?q=shanghai
# 响应体
#! Deprecation: this request accesses system indices: [.apm-agent-configuration, .apm-custom-link, .kibana_1, .kibana_task_manager_1], but in a future major version, direct access to system indices will be prevented by default
{
"took": 13, # 查询耗时
"timed_out": false, # 是否超时
"_shards": { # 分片信息
"total": 7,
"successful": 7,
"skipped": 0,
"failed": 0
},
"hits": { # 查询到的数据
"total": {
"value": 1,
"relation": "eq" # 查询的条件,eq 表示 = 的意思
},
"max_score": 0.3616575, # 得分
"hits": [ # 搜索结果,匹配度最高的一项
{
"_index": "account", # 数据所在的索引名
"_type": "_doc",
"_id": "1",
"_score": 0.3616575,
"_source": { # 真正的数据
"name": "uccs",
"age": 18,
"company": [
{
"name": "astak",
"address": "shanghai"
},
{
"name": "astak2",
"address": "beijing"
}
]
}
}
]
}
}
如果只想查询某一个索引的数据,可以在 url
中指定索引名
bash
GET account/_search?q=shanghai
它还可以做的很复杂,不过这种方式不常用,了解即可
bash
GET /_search?pretty&q=title:azure&explain=true&from=1&size=10&sort=title:asc&fields:user,title,content
使用请求体查询
query
是查询条件,match_all
表示查询所有数据,默认返回 10
条数据
通过这种方式查询是没办法计算得分的,所有的数据都是 1.0
bash
GET user/_search
# 请求体
{
"query": { # 查询条件
"match_all": {} # 查询所有数据
}
}
# 响应体
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 1.0, # match_all 是没法计算得分的,所以都是 1.0
"hits": [
{
"_index": "user",
"_type": "_doc",
"_id": "WYUzT4oBZ0WpQB8-3pDP",
"_score": 1.0,
"_source": {
"name": "uccs",
"company": "imooc"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "WoU0T4oBZ0WpQB8-pZAj",
"_score": 1.0,
"_source": {
"name": "uccs",
"company": "imooc"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "1",
"_score": 1.0,
"_source": {
"name": "uccs",
"company": "imooc"
}
}
]
}
}
更新数据
使用这种方式更新数据,如果数据不存在,会创建数据,如果数据存在,会更新数据
bash
POST user/_doc/3
# 请求体
{
"name": "astak"
}
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 2,
"result": "updated", # 如果数据不存在,这个值是 create;如果数据存在,这个值是 updated
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}
这种问题会存在一个坑,当我向这个数据更新某个字段时,会覆盖掉其他的字段
比如 user/3
这条数据中已经存在了 name
的字段,当我在设置 age
时,会覆盖掉 name
的值
bash
POST user/_doc/3 # 使用 PUT 也是同样的问题
# 请求体
{
"age": 10
}
#########
GET user/_doc/3
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 5,
"_seq_no": 7,
"_primary_term": 1,
"found": true,
"_source": {
"age": 10
}
}
update
如何解决这个问题呢?
需要将 _doc
换成 _update
它的写法有不一样,需要将更新的数据放到 doc
中
bash
POST user/_update/3
# 请求体
{
"doc": {
"name": "astak"
}
}
#######
GET user/_doc/3
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 7,
"_seq_no": 9,
"_primary_term": 1,
"found": true,
"_source": {
"age": 10,
"name": "astak"
}
}
这里要注意细节 _doc
和 _update
的一个细节
- 使用
_update
更新数据时,如果每次更新的数据都一样,_version
和_seq_no
的值不会变化 - 使用
_doc
更新数据时,更新一次,_version
和_seq_no
的值就会加1
删除数据
删除某条数据
bash
DELETE user/_doc/3
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 8,
"result": "deleted", # 表示数据删除成功
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 10,
"_primary_term": 1
}
########
GET user/_doc/3
# 响应体
{
"_index": "user",
"_type": "_doc",
"_id": "3",
"found": false # 是否找到数据
}
删除整个索引
bash
DELETE user
# 响应体
{
"acknowledged": true # 表示删除成功
}
#########
GET user/_doc/3
# 响应体
{
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index [user]",
"resource.type": "index_expression",
"resource.id": "user",
"index_uuid": "_na_",
"index": "user"
}
],
"type": "index_not_found_exception",
"reason": "no such index [user]",
"resource.type": "index_expression",
"resource.id": "user",
"index_uuid": "_na_",
"index": "user"
},
"status": 404
}
批量操作
批量插入,使用 POST
,_bulk
是固定值
每条数据有两行:
- 第一行表示操作类型,
index
表示插入,delete
表示删除,create
表示创建,update
表示更新_index
:在哪个索引上操作_id
:数据的唯一标识符
- 第二行表示数据
- 这里要注意:
delete
操作没有第二行数据update
操作的第二行数据是doc
,表示更新的数据
如果某一条数据操作失败,不影响其他数据的操作
bash
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
bash
POST _bulk
# 请求体
{ "index": { "_index": "user", "_id": "1" } }
{ "age": 20 }
{ "delete": { "_index": "user", "_id": "3" } } # 删除不存在的数据,不影响其他数据
# 响应体
{
"took": 186,
"errors": false,
"items": [ # 操作成功后数据在这里显示
{
"index": {
"_index": "user",
"_type": "_doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1,
"status": 201
}
},
{
"delete": {
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 1,
"result": "not_found", # 操作失败
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1,
"status": 404
}
}
]
}
批量查询
bash
GET _mget
# 请求体
{
"docs": [
{ "_index": "user", "_id": "1" }, # user 索引, id 为 1 的数据
{ "_index": "account", "_id": "1" } # account 索引,id 为 1 的数据
]
}
# 响应体
{
"docs": [
{
"_index": "user",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
...
}
},
{
"_index": "account",
"_type": "_doc",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
....
}
}
]
}
分页查询
分页查询可以使用 from
和 size
:
from
:第几页,默认是0
size
:多少条,,默认是10
需要注意的是,from
和 size
适用于数据量比较小的情况,在数据量大的情况的下,优先使用 scroll
bash
GET user/_search
# 请求体
{
"query": {
"match_all": {}
},
"from": 10,
"size": 20
}
条件查询
match
按条件查询:
key: value
的形式,搜索user
索引address
字段中所有包含street
的数据value
大小写不敏感
bash
GET user/_search
# 请求体
{
"query": {
"match": {
"address": "Street" # 大小写不敏感
}
}
}
倒排索引(分词)
ElasticSearch
中的倒排索引是指:
- 在写入数据时,
ES
会对数据进行分词,然后将分词后的数据存储到倒排索引中 - 在搜索时,
ES
也会对搜索的数据进行分词,然后去倒排索引中查找,最后将结果返回
具体如下图所示:
用 671 Bristol Street
和 789 Madison Street
举例:
- 写入数据时,会进行分词,分词后的数据存储到倒排索引中
671 Bristol Street
分词后的数据是671
、bristol
、street
789 Madison Street
分词后的数据是789
、sadison
、street
- 分词后会在
doc
中保存相关信息,比如:id
,位置,出现次数等(图中只列举了id
)
- 查询数据时,也会进行分词,分词后去倒排索引中查找,最后将结果返回
- 比如搜索
Madison Street
,分词后的数据是madison
、street
- 然后去倒排索引中查找
madison
找的到,id
是13
street
找的到,id
是6
、13
- 最后将
id
为6
、13
的数据返回
- 比如搜索
例如下面的例子,我们可以看到:
- 搜索出来了
3
条结果,因为这三条结果满足了分词street
和madison
的条件 - 每条结果都有得分,匹配度越高的得分越高,排名越靠前
bash
GET user/_search
# 请求体
{
"query": {
"match": {
"address": "Madison Street"
}
}
}
# 响应体
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 386,
"relation": "eq"
},
"max_score": 6.9447823,
"hits": [
{
"_index": "user",
"_type": "_doc",
"_id": "13",
"_score": 6.9447823, # 匹配度最高
"_source": {
"address": "789 Madison Street"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "956",
"_score": 5.990829,
"_source": {
"address": "490 Madison Place"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "6",
"_score": 0.95395315,
"_source": {
"address": "671 Bristol Street"
}
}
]
}
}
match_phrase 查询
match
查询是模糊匹配,会对输入的数据进行分词,只要字段中包含分词,就都会被查询出来:
使用 Madison Street
去查询 Madison walk on the street
也会被匹配出来
但这并不是我们想要的结果
我们想要精确的匹配结果,就需要使用 match_phrase
match_phrase
也会进行分词,但结果需要包含所有的分词,才会被匹配出来:
比如 Madison Street
会用短语去匹配,所以不会匹配到 Madison walk on the street
multi_match 查询
match
只能进行单字段查询,无法进行多字段查询
es
提供了 multi_match
进行多字段查询:
fields
:查询的字段query
:需要查询的值
只要 address
或者 state
中包含 us
,就会被查询出来
bash
GET user/_search
# 请求体
{
"query": {
"multi_match": {
"query": "us",
"fields": ["address", "state"]
}
}
}
有时候,某个字段的权重比较高,我们可以通过 ^
来设置权重:
address
最后的得分会被乘以2
bash
GET user/_search
# 请求体
{
"query": {
"multi_match": {
"query": "us",
"fields": ["address^2", "state"]
}
}
}
query_string 查询
query_string
和 match
用法差不多,区别是:
match
需要指定字段名query_string
默认是查询所有字段,也可以指定字段查询
bash
GET user/_search
# 请求体
{
"query": {
"query_string": {
"default_field": "address", # 指定字段查询,如果不指定 default_field,就是查询所有字段
"query": "Madison Street",
}
}
}
可以在词语之间使用操作符 OR
和 AND
OR
和match
类似AND
和match_parse
类似
bash
GET user/_search
# 请求体
{
"query": {
"query_string": {
"query": "Madison OR Street",
}
}
}
term 查询
term
查询是精确查询,不会对输入的数据进行分词,只要字段中包含输入的数据,就会被查询出来
但是 term
查询是区分大小写的,比如 Street
和 street
是不一样的
我们在写入数据时,是先分词的,这一步会把数据中大小的字母都转成小写,所以当你在用大写字母去查询时,是查不到数据的
bash
GET user/_search
# 请求体
{
"query": {
"term": {
"address": "street"
}
}
}
# 响应体
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 385,
"relation": "eq"
},
"max_score": 0.95395315,
"hits": [
{
"_index": "user",
"_type": "_doc",
"_id": "6",
"_score": 0.95395315,
"_source": {
"address": "671 Bristol Street"
}
}
]
}
}
bash
GET user/_search
# 请求体
{
"query": {
"term": {
"address": "Street"
}
}
}
# 响应体
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
term 查询 ------ 范围查询
term
查询还可以进行范围查询,比如查询 age
在 10
到 20
之间的数据,可以使用 range
bash
GET user/_search
# 请求体
{
"query": {
"range": {
"age": {
"gte": 10,
"lte": 20
}
}
}
}
# 响应体
{
"took": 28,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 44,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "user",
"_type": "_doc",
"_id": "157",
"_score": 1.0,
"_source": {
"age": 10
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "215",
"_score": 1.0,
"_source": {
"age": 20
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "816",
"_score": 1.0,
"_source": {
"age": 18
}
}
]
}
}
exists
可以查询某个字段是否存在,比如查询 age
字段是否存在
bash
GET user/_search
# 请求体
{
"query": {
"exists": {
"field": "age"
}
}
}
模糊查询,这里的模糊查询是指,自动纠错,比如 street
和 stret
是一样的
bash
GET user/_search
# 请求体
{
"query": {
"fuzzy": {
"address": "stret"
}
}
}
match
中也可以使用 fuzzy
查询
bash
GET user/_search
# 请求体
{
"query": {
"match": {
"address": {
"query": "stret",
"fuzziness": 1
}
}
}
}
复合查询
bool
查询是复合查询的一种
语法是:
json
{
"query": {
"bool": {
"must": [], // 必须全匹配,用于加分
"should": [], // 匹配也可以,不匹配也可以;用于加分,匹配的话得分会高,不匹配得分会低
"must_not": [], // 必须不匹配,用于过滤,不影响得分
"filter": [] // 必须匹配,用于过滤,不影响得分
}
}
}
bool
查询,采用了一种匹配越多越好的方法,也就是说每个匹配的 must
和 should
子句的得分会加在一起,提供最终的得分
bash
GET user/_search
# 请求体
{
"query": {
"bool": {
"must": [
{ "term": { "state": "tn" } },
{ "range": { "age": { "gte": 20, "lte": 30 } } }
],
"must_not": [{ "term": { "gender": "m" } }],
"should": [{ "match": { "firstname": "Decker" } }],
"filter": [{ "range": { "age": { "gte": 25, "lte": 30 } } }]
}
}
}
# 响应体
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 6,
"relation": "eq"
},
"max_score": 11.173367,
"hits": [
{
"_index": "user",
"_type": "_doc",
"_id": "686",
"_score": 11.173367,
"_source": {
"firstname": "Decker",
"age": 30,
"gender": "F",
"state": "TN"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "869",
"_score": 4.6700764,
"_source": {
"firstname": "Corinne",
"age": 25,
"gender": "F",
"state": "TN"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "442",
"_score": 4.6700764,
"_source": {
"firstname": "Lawanda",
"age": 27,
"gender": "F",
"state": "TN"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "707",
"_score": 4.6700764,
"_source": {
"firstname": "Sonya",
"age": 30,
"gender": "F",
"state": "TN"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "72",
"_score": 4.6700764,
"_source": {
"firstname": "Barlow",
"age": 25,
"gender": "F",
"state": "TN"
}
},
{
"_index": "user",
"_type": "_doc",
"_id": "767",
"_score": 4.6700764,
"_source": {
"firstname": "Anthony",
"age": 27,
"gender": "F",
"state": "TN"
}
}
]
}
}
mapping
text
和 keyword
的区别:
text
会分词,然后存入倒排索引keyword
不会分词
查看索引的 mapping
,使用 GET + 索引名
bash
GET user
# 响应体
{
"user": {
"aliases": {},
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text", # 这个字段被分词了
"fields": {
"keyword": {
"type": "keyword", # 字段字段可以不分词
"ignore_above": 256
}
}
},
"age": {
"type": "long"
},
"balance": {
"type": "long"
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",
"provided_name": "user",
"creation_date": "1693653631917",
"number_of_replicas": "1",
"uuid": "gt2Hh0upSX6ERoWNDny0lQ",
"version": {
"created": "7100199"
}
}
}
}
}
不分词查询
bash
GET user/_search
# 请求体
{
"query": {
"match": {
"address.keyword": "789 Madison Street" # 不会被分词
}
}
}
定义索引的 mapping
,使用 PUT + 索引名
bash
PUT usertest
# 请求体
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"name": {
"type": "text" # 会分词
},
"desc": {
"type": "keyword" # 不会分词
}
}
}
}
analyzer
从最开始的一张图中,我们可以看出,不管在写入数据还是在查询数据时,有一个分析(analyzer
)的过程,它是由三部分组成:
Character Filters
:对原始文本增加、删除或者对字符做转换Tokenizer
:将字符流分解成单词、词语等,输出单词流。也负责记录每个单词的顺序和该单词在原始文本中的起始和结束偏移offsets
。Token Filters
:对分词后的单词进行处理,比如转换小写,去掉没有的符号(!
、。
等),去掉停用词(the
、and
等)
内置 analyer
Standard Analyzer
:默认分词器,按词切分,小写处理Simple Analyzer
:按照非字母切分(符号被过滤),小写处理Stop Analyzer
:小写处理,停用词过滤(the
,a
,is
)Whitespace Analyzer
:按照空格切分,不转小写Keyword Analyzer
:不分词,直接将输入当做输出Patter Analyzer
:正则表达式,默认\W+
Language
:提供了30
多种常见语言的分词器
analyer 测试
bash
GET _analyze
# 请求体
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
# 响应体
{
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "<NUM>",
"position": 1
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "<ALPHANUM>",
"position": 4
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "<ALPHANUM>",
"position": 5
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "<ALPHANUM>",
"position": 6
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "<ALPHANUM>",
"position": 7
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "<ALPHANUM>",
"position": 8
},
{
"token": "dog's",
"start_offset": 45,
"end_offset": 50,
"type": "<ALPHANUM>",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "<ALPHANUM>",
"position": 10
}
]
}
analyzer 使用策略
在搜索时,会采用以下策略:
-
搜索时,指定
analyzer
,采用指定的analyzer
(文档)bashGET usertest/_search { "query": { "match": { "address": { "query": "789 Madison Street", "analyzer": "simple" # 采用这个 analyzer } } } }
-
搜索时,如果不指定
analyzer
,会采用创建索引时指定的search_analyzer
(文档)bashPUT usertest { "mappings": { "properties": { "address": { "type": "text", "search_analyzer": "simple" # 采用这个 search_analyer } } } }
-
使用默认的
analyzer
,在setting
中:analysis.analyzer.default_search
(文档)bashGET usertest { "settings": { "analysis": { "analyzer": { "default_search": { "type": "whitespace" # 采用这个 default_analyzer } } } } }
-
使用创建索引时指定的的
analyzer
(文档)bashPUT usertest { "mappings": { "properties": { "address": { "type": "text", "analyzer": "simple" # 采用这个 analyzer } } } }
-
采用默认的
analyzer
,是standard
(文档)
IK 分词器
-
在 elasticsearch-analysis-ik 中找到
ES
对应的版本,下载ik
分词器 -
将下载的压缩包解压,然后重名为为
ik
-
将
ik
文件夹放到elasticsearch
的plugins
目录下docker
安装的ES
,plugins
目录在/usr/share/elasticsearch/plugins
- 如果没有权限,执行
chmod 777 -R ik
-
重启
ES
docker restart <ES_ContainerId>
-
查看插件
bash./bin/elasticsearch-plugin list
使用
bash
GET _analyze
# 请求体
{
"text": "中国科学技术大学",
"analyzer": "ik_max_word"
}
# 响应体
{
"tokens": [
{
"token": "中国科学技术大学",
"start_offset": 0,
"end_offset": 8,
"type": "CN_WORD",
"position": 0
},
{
"token": "中国",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "科学技术",
"start_offset": 2,
"end_offset": 6,
"type": "CN_WORD",
"position": 2
},
{
"token": "科学",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "技术",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 4
},
{
"token": "大学",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 5
}
]
}
bash
# 请求体
{
"text": "中国科学技术大学",
"analyzer": "ik_smart"
}
# 响应体
{
"tokens": [
{
"token": "中国科学技术大学",
"start_offset": 0,
"end_offset": 8,
"type": "CN_WORD",
"position": 0
}
]
}
扩展词库
- 在
ik/config
目录下新建custom
目录 - 新建文件,文件最好以
dic
结尾mydict.dic
,将扩展词写入文件中stopword.dic
,将停用词写入文件中
- 编辑
IKAnalyzer.cfn.xml
文件<entry key="ext_dict">mydict.dic</entry>
<entry key="ext_stopwords">stopword.dic</entry>
- 重启容器