Elasticsearch学习系列二(基础操作)
本文将分为3块讲解Es的基础操作。分别为:索引(index)、映射(mapping)、文档(document)。
索引操作
- 创建索引库
语法:
PUT /索引名称{
"settings":{
"属性名":"属性值"
}
}
settings:就是索引库的设置,可以定义如分片数、副本数等等。不设置的话就是都走默认值。
示例:
PUT /test-demo
- 判断索引是否存在
HEAD /索引名称
- 查看索引
- 查看单个索引
GET /索引名称
- 批量查看索引
GET /索引名称1,索引名称2
- 查看所有索引
GET _all
- 打开索引
POST /索引名称/_open
- 关闭索引
POST /索引名称/_close
- 删除索引
DELETE /索引名称
映射操作
索引创建之后,等于有了关系型数据库中的database。Es7.x取消了索引type类型的设置,不能指定类型,默认为_doc,但是字段仍然是有的,我们需要设置字段的约束信息,叫做字段映射(mapping)。
字段的约束包括:
- 字段的数据类型
- 是否要存储
- 是否要索引
- 分词器等
Es Mapping属性:
Mapping字段设置流程图:
示例:
PUT blog_index
{
"mappings": {
"doc": {
"_source": {
"enabled": false
},
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 100
}
},
"store": true
},
"publish_date": {
"type": "date",
"store": true
},
"author": {
"type": "keyword",
"ignore_above": 100,
"store": true
},
"abstract": {
"type": "text",
"store": true
},
"content": {
"type": "text",
"store": true
},
"url": {
"type": "keyword",
"doc_values":false,
"norms":false,
"ignore_above": 100,
"store": true
}
}
}
}
}
这个索引 Mapping中,_source设置为false,同时各个字段的store根据需求设置了true和false。url的doc_values设置为false,该字段url不用于聚合和排序操作。建 mapping时,可以为字符串(专指keyword)指定ignore_above ,用来限定字符长度。超过ignore_above的字符会被存储,但不会被索引。注意,是字符长度,一个英文字母是一个字符,一个汉字也是一个字符。
在动态生成的 mapping 中, keyword 类型会被设置 ignore_above: 256 。
ignore_above 可以在创建 mapping 时指定
- 创建映射字段
语法:
PUT /索引名/_mapping
{
"properties":{
"字段名":{
"type":"类型",
"index":true,
"store":true,
"analyzer":"分词器"
}
}
}
- 字段名:根据需要任意填写
- type:类型,可以是text(可分词)、keyword(不可分词)、long、short、date、integer、object
- index:是否索引,默认为true
- store:是否独立存储,默认为false。原始的文本会存储在 _source 里面,如果设置为true,则是独立的存储某个字段,获取独立存储字段比从_source里解析快,但是更占空间。
- analyzer:指定分词器,一般中文可以选择ik_max_word、ik_smart
示例:
PUT /test-demo1/_mapping
{
"properties":{
"name":{
"type":"text",
"index":true,
"store":true,
"analyzer":"ik_max_word"
},
"job":{
"type":"text",
"analyzer":"ik_max_word"
},
"logo":{
"type":"keyword",
"index":false
}
,
"amt":{
"type":"double"
}
}
}
- 查看映射关系
- 查看某个索引
GET /索引名称/_mapping
- 查看所有索引
GET _mapping
#或者
GET _all/_mapping
- 修改映射关系
这里的修改指的是新增字段,其他更改不支持。只能删除索引,重建映射
PUT /索引库名/_mapping
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
- 一次性创建索引和映射
语法:
put /索引库名称
{
"settings":{
"索引库属性名":"索引库属性值"
},
"mappings":{
"properties":{
"字段名":{
"映射属性名":"映射属性值"
}
}
}
}
示例:
PUT /test-demo2
{
"settings":{},
"mappings": {
"properties": {
"name":{
"type":"text",
"analyzer": "ik_max_word"
}
}
}
}
文档操作
文档,即索引库中的数据,会根据规则创建索引,将来用于搜索。可以类比做数据库中的一行数据。
- 新增文档
语法:
#自动生成id
POST /索引名称/_doc
{
"field":"value"
}
#手动指定id
POST /索引名称/_doc/1
{
"field":"value"
}
示例:
POST /test-demo1/_doc/1
{
"name":"百度",
"job":"运营",
"amt":"3000.34",
"logo":"http://www.lgstatic.com/ttasdf2",
"createTime":"20220303230000"
}
- 查看单个文档
GET /索引名称/_doc/{id}
结果如下:
{
"_index" : "test-demo1",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "百度",
"job" : "运营",
"amt" : "3000.34",
"logo" : "http://www.lgstatic.com/ttasdf2",
"createTime" : "20220303230000"
}
}
元数据项 | 含义 |
---|---|
_index | document所属index |
_type | document所属type,Elasticsearch7.x默认type为_doc |
_id | 代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document |
_version | document的版本号,Elasticsearch利用_version(版本号)的方式来确保应用中相互冲突的变更不会导致数据丢失。需要修改数据时,需要指定想要修改文档的version号,如果该版本不是当前版本号,请求将会失败 |
_seq_no | 严格递增的顺序号,每个文档一个,Shard级别严格递增,保证后写入的Doc seq_no大于先写入的Doc的seq_no。任何类型的写操作,包括index、create、update和Delete,都会生成一个_seq_no。 |
_primary_term | 当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。_primary_term主要是用来恢复数据时处理当多个文档的_seq_no一样时的冲突,避免Primary Shard上的写入被覆盖 |
found | true/false,是否查找到文档 |
_source | 存储原始文档 |
- 查看所有文档
POST /test-demo1/_search
{
"query":{
"match_all": {}
}
}
- 仅查询部分字段
GET /test-demo1/_doc/1?_source=name,job
- 更新文档(全部更新)
PUT /test-demo1/_doc/1
{
"name":"百度3",
"job":"运营",
"amt":"3000.34",
"logo":"http://www.lgstatic.com/ttasdf2",
"createTime":"20220303230000"
}
为什么说是全部更新呢?如果你只传了name,其他filed不传。那么文档里就只剩name了。
注意:Elasticsearch执行更新操作的时候,Elasticsearch首先将旧的文档标记为删除状态,然后添加新的文档,旧的文档不会立即消失,但是你也无法访问,Elasticsearch会在你继续添加更多数据的时候在后台清理已经标记为删除状态的文档。
全部更新,是直接把之前的老数据,标记为删除状态,然后,再添加一条更新的(使用PUT或者POST)
- 更新文档(部分更新)
POST /索引名称/_update/{id}
{
"doc":{
"field":"value"
}
}
- 删除文档
- 根据id删除
DELETE /索引名称/_doc/{id}
- 根据查询条件删除
POST /索引名称/_delete_by_query
{
"query":{
"match":{
"字段名":"搜索关键字"
}
}
}
- 删除所有文档
POST /索引名称/_delete_by_query
{
"query":{
"match_all":{}
}
}
- 文档强制创建
本来如果不存在会创建,存在会更新。强制创建就是仅创建,不更新。已存在就报错。
PUT /索引名称/_doc/{id}?op_type=create
{
"filed":"value"
}
番外:ES关联关系处理
目前ES主要有以下4种方法来处理数据实体间的关联关系:
- Application-side joins
这种方式,索引之间完全独立,由应用端的多次查询来实现近似关联关系查询。这种方法适用于关联的实体只有少量的文档记录的情况(使用ES的terms查询具有上限,默认1024,具体可在elasticsearch.yml中修改),并且最好它们很少改变,这将允许应用程序对结果进行缓存。
PUT /user/_doc/1
{
"name": "John Smith",
"email": "john@smith.com",
"dob": "1970/10/24"
}
PUT /blogpost/_doc/2
{
"title": "Relationships",
"body": "It's complicated...",
"user": 1
}
GET /user/_search
{
"query": {
"match": {
"name": "John"
}
}
}
GET /blogpost/_search
{
"query": {
"terms": { "user": [1] }
}
}
- Data denormalization(字段冗余)
通过字段冗余,以一张大表来实现粗粒度的index。使用的前提:冗余的字段应该很少改变,比较适合与一堆少量关系的处理。
PUT /user/_doc/1
{
"name": "John Smith",
"email": "john@smith.com",
"dob": "1970/10/24"
}
PUT /blogpost/_doc/2
{
"title": "Relationships",
"body": "It's complicated...",
"user": {
"id": 1,
"name": "John Smith"
}
}
GET /blogpost/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "relationships" }},
{ "match": { "user.name": "John" }}
]
}
}
}
- Nested objects(嵌套文档)
索引性能和查询性能二者不可兼得,必须进行取舍。嵌套文档将实体关系嵌套组合在单文档内部,这种方式牺牲建立索引性能。
当使用嵌套文档时,使用通用的查询方式是无法访问到的,必须使用合适的查询方式(nested query、nested filter、nested facet等),很多场景下,使用嵌套文档的复杂度在于索引阶段对关联关系的组织拼装。
PUT /drivers
{
"mappings":{
"properties":{
"driver":{
"type":"nested",
"properties":{
"last_name":{
"type":"text"
},
"vehicle":{
"type":"nested",
"properties":{
"make":{
"type":"text"
},
"model":{
"type":"text"
}
}
}
}
}
}
}
}
PUT /drivers/_doc/1
{
"driver" : {
"last_name" : "McQueen",
"vehicle" : [
{
"make" : "Powell Motors",
"model" : "Canyonero"
},
{
"make" : "Miller-Meteor",
"model" : "Ecto-1"
}
]
}
}
PUT /drivers/_doc/2
{
"driver" : {
"last_name" : "Hudson",
"vehicle" : [
{
"make" : "Mifune",
"model" : "Mach Five"
},
{
"make" : "Miller-Meteor",
"model" : "Ecto-1"
}
]
}
}
嵌套搜索:
GET /drivers/_search
{
"query" : {
"nested" : {
"path" : "driver",
"query" : {
"nested" : {
"path" : "driver.vehicle",
"query" : {
"bool" : {
"must" : [
{ "match" : { "driver.vehicle.make" : "Powell Motors" } },
{ "match" : { "driver.vehicle.model" :
"Canyonero" } }
]
}
}
}
}
}
}
}
- Parent/child relationships(父子文档)
父子文档牺牲了一定的查询性能来换取索引性能,适用于写多读少的场景。父子文档相比嵌套文档较灵活,适用于“一对大量”且这个“一”不是海量的应用场景,该方式比较耗内存和CPU,这种方式查询比嵌套方式慢5~10倍,且需要使用特定的has_parent和has_child过滤器查询语法,查询结果不能同时返回
父子文档(一次join查询只能返回一种类型的文档)。受限于父子文档必须在同一分片上(可以通过routing指定父文档id即可)操作子文档时需要指定routing
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
#插入父文档
PUT my_index/_doc/1?refresh
{
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}
#或者省略name
PUT /my_index/_doc/2?refresh
{
"text": "This is a question2",
"my_join_field": "question"
}
# 插入子文档
PUT /my_index/_doc/3?routing=1
{
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
#查询哪个文档有子文档
POST my_index/_search
{
"query": {
"has_child": {
"type": "answer",
"query" : {
"match": {
"text" : "this"
}
}
}
}
}
#根据父文档id查询子文档
GET my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
}
}