如果还未安装Elasticsearch,推荐参考这篇博客通过docker安装测试环境:Elasticsearch环境与搭建#Docker-Compose 单机部署(个人测试推荐使用)

RESTful & JSON

Elasticsearch使用RESTful风格的http请求进行通信(操作索引、添加数据、修改数据、删除数据等),通过JSON数据格式传递数据。

所谓的RESTful,就是通过一个URI(Universal Resource Identifier)标识网络上的一个资源,再通过HTTP Method标识操作类型

比如,https://localhost:9200/employee/123 标识了id为123的某个员工,则:

  • Post请求表示添加id为123的员工信息
POST https://localhost:9200/employee/123
{
    "first_name": "John",
    "last_name": "Smith",
    "age": 25,
    "about": "I love to go rock climbing",
    "interests": [
        "sports",
        "music"
    ]
}
  • Get请求表示获取id为123的员工信息
GET https://localhost:9200/employee/123
  • Put请求表示修改id为123的员工信息
PUT https://localhost:9200/employee/123
{
    "age": 27
}
  • Delete请求表示删除id为123的员工信息
DELETE https://localhost:9200/employee/123

Elasticsearch概念介绍

Elasticsearch是面向文档型的数据库,一条数据就是一个文档。为了方便理解,这里将Elasticsearch的结构和MySQL的结构做一个类比。

img

ES中的Index可以看做一个库,而Typpes相当于表,Documents则相当于表的一行数据。

这里Types的概念已经被逐渐弱化,在Elasticsearch 6.X中,一个 index 下已经只能包含一个type, Elasticsearch 7.X 中, Type 的概念已经被删除了。

倒排索引

要理解什么事倒排索引,首先要了解什么是正排索引。

正排索引

假如在MySQL的某个表中有以下数据:

id content
1001 My name is zhang san.
1002 My name is li si.

所谓的正排索引就是我们一直以来习惯的通过id找到文章内容,比如通过id=1001查询到content内容。

在正排索引中,如果我们要查询某个列包含了某个关键字的数据,则需要使用MySQL的like关键字进行模糊查询,比如select * from table where content like '%zhang%';

但我们都知道,MySQL前后模糊查询的效率是非常慢的。

倒排索引

而倒排索引则不一样,在保存数据时,ES先将需要索引的内容进行分词,然后记录每个单词对应的id。

比如My name is zhang san可以按照规则被分词为:namezhangsan三个词,而这三个词都关联了id=1001;My name is li si按照规则被分词为:namelisi,这三个词都关联了id=1002。

那么在ES中将会以下面的形式进行存储:

keyword id
name 1001,1002
zhang 1001
san 1001
li 1002
si 1002

此时通过zhang关键字在keyword中很快就能查出关联了1001一个id。

HTTP操作数据

这里使用的kibana的开发工具,所以只展示了http method和url path部分,如果想通过postman或apifox等工具发送请求,则填写完整的url并选择对应的http method即可。

索引操作

1)创建索引

PUT /shopping

2)查询索引信息

GET /shopping

4)删除索引

DELETE /shopping

文档操作

1)添加文档

不指定id则会给这条数据生成一个随机id

POST /shopping/_doc
{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 3999.00
}

或者

PUT /shopping/_doc
{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 3999.00
}

指定id则使用指定的id

POST /shopping/_doc/1001
{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 3999.00
}

或者

PUT /shopping/_doc/1001
{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 3999.00
}

2)查询文档

根据id查询
GET /shopping/_doc/1001
查询全部
GET /shopping/_search

3)修改文档

覆盖更新
POST或PUT /shopping/_doc/1001
{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 4999.00
}
局部更新
POST /shopping/_update/1001
{
  "doc": {
    "price": 2999.00
  }
}

覆盖更新和局部更新的区别是什么?

当使用覆盖更新时,则需要携带所有字段的数据,否则没有携带数据的字段的数据就会丢失。

比如id为1001在数据库中的数据为

{
  "title": "小米手机",
  "category": "手机",
  "images": "https://img.flycat.tech/common/avatar.jpg",
  "price": 4999.00
}

此时使用覆盖更新,但是只携带了price字段,更新语句如下:

POST /shopping/_doc/1001
{
  "price": 2999.00
}

那么此时id为1001在数据库中的数据就变成了只有一个price字段的文档:

{
  "price": 2999.00
}

4)删除文档

DELETE /shopping/_doc/1001

5)条件查询

完全匹配查询
# 查询category为“手机”的所有数据
GET /shopping/_search
{
  "query": {
    "match_phrase": {
      "category": "手机"
    }
  }
}
全文检索查询
# 搜索title中含有“手机”关键词的数据
GET /shopping/_search
{
  "query": {
    "match": {
      "title": "手机"
    }
  }
}

完全匹配和全文检索使用的关键字分别是match_phrase和match,这两者不同之处在于,match_phrase是精确匹配某个关键字,在上一个例子中就是查询category="手机"的数据,多一个字少一个字都不行。

而全文检索在查询时,会先根据分词规则将待查询数据进行分词,分词效果取决于分词器。

如果使用了ES默认的分词器,由于则其可能会把“手机”分成“手”和“机”两个词,那么查询出来的结果就是包含“手”或者包含“机”的所有数据。

分页操作
# 从索引0开始,查询10条数据
GET /shopping/_search
{
  "from": 0,
  "size": 10
}

分页计算通用公式:from = (页码 - 1) * 每页大小

指定查询字段
# 查询所有数据,并只返回title字段
GET /shopping/_search
{
  "_source": ["title"]
}
结果排序
# 查询所有数据,并指定price字段降序排序
GET /shopping/_search
{
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
多条件查询
# 多条件查询公式
GET /shopping/_search
{
    "query": {
        "bool": {
            "must|must_not|should": [
                {
                    "match|match_all|match_phrase": {
                        "FIELD": "TEXT"
                    }
                }
            ],
            "filter": [
                {
                    "range": {
                        "FIELD": {
                            "gt|gte|lt|lte": "VALUE"
                        }
                    }
                }
            ]
        }
    }
}

关键字解释

  • "FIELD":公式中出现的"FIELD"为文档字段
  • "TEXT":公式中出现的"TEXT"为条件输入的文本
  • "VALUE":公式中出现的"VALUE"为条件输入的数值类型的数据
  • must:must关键字下面的多个查询条件为或关系
  • should:should关键字下面的多个查询条件为或关系
  • filter:filter关键字用于过滤数据
  • range:range用来范围查询,其中gt=大于,gte=大于等于,lt=小于,lte=小于等于

查询示例

# 查询价格必须为2999的数据
GET /shopping/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "price": "2999"
          }
        }
      ]
    }
  }
}

# 查询价格必须为2999且title必须为“小米手机”的数据
GET /shopping/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "price": "2999"
          }
        },
        {
          "match": {
            "title": "小米手机"
          }
        }
      ]
    }
  }
}

# 查询价格必须为2999或title必须为“小米手机”的数据
GET /shopping/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "price": "2999"
          }
        },
        {
          "match": {
            "title": "小米手机"
          }
        }
      ]
    }
  }
}

# 查询价格大于等于3000小于等于4000的数据
GET /shopping/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "price": {
              "gte": 3000,
              "lte": 4000
            }
          }
        }
      ]
    }
  }
}
高亮显示
# 对结果中title字段高亮显示查询的关键字
GET /shopping/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}

6)聚合查询

# 聚合查询公式
GET /shopping/_search
{
  "aggs": {
    "NAME": {
      "terms|max|min|sum|avg": {
        "field": "FIELD",
        "size": 10
      }
    }
  },
  "size": 0
}

关键字解释

  • "NAME":名称,会在查询结果中展示改名称对应的聚合结果
  • terms:根据指定的字段进行分组
  • max|min|sum|avg:最大值或最小值或求和或求平均值
  • 外层的size:如果只需要聚合查询结果而不需要数据,则可以设为0

查询示例

# 根据price字段进行分组
GET /shopping/_search
{
  "aggs": {
    "price_group": {
      "terms": {
        "field": "price",
        "size": 10
      }
    }
  },
  "size": 0
}

映射操作

映射类似于MySQL的scheme(表结构),其定义了有哪些字段以及每个字段的类型等

与MySQL必须先定义scheme不同,在ES中如果没有预先定义映射,在插入数据时,ES会判断当前映射有没有对应字段信息,如果没有则会根据字段的值推断。

当然更推荐的是预先定义好映射信息。

定义映射

PUT /shopping/_mapping
{
    "properties": {
        "category": {
            "type": "keyword",
            "index": true,
            "fields": {
                "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                }
            }
        },
        "price": {
            "type": "float",
            "index": true
        },
        "title": {
            "type": "text",
            "index": true,
            "fields": {
                "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                }
            }
        }
    }
}

关键字解释

  • type:字段类型,keyword表示关键字,不进行分词;text表示该字段可被分词
  • index:为true则表示该字段可被索引,为false则不可出现在查询条件中

查询映射

跟查询索引相同,结果中包含了映射信息

GET /shopping