Elasticsearch保姆级教程(2026新鲜出炉)
本文介绍了Elasticsearch的基础知识、发展历程和核心概念。首先讲解了Elasticsearch作为开源搜索引擎的特点及其在ELK技术栈中的核心地位。然后回顾了从Lucene到Elasticsearch的发展历程,突出了Elasticsearch分布式和RESTful接口的优势。重点阐述了倒排索引的原理,通过商品数据示例说明其工作方式。最后对比了Elasticsearch与MySQL的概念
1 初识 Elasticsearch
1.1 什么是 Elasticsearch
Elasticsearch 是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容
Elasticsearch 结合 kibana、Logstash、Beats,也就是 elastic stack(ELK),被广泛应用在日志数据分析、实时监控等领域
Elasticsearch 是 elastic stack 的核心,负责存储、搜索、分析数据

1.2 Elasticsearch 的发展
Lucene 是一个 Java 语言的搜索引擎类库,是 Apache 公司的定级项目,由 DougCutting 于 1999 年研发,
官网地址:https://lucene.apache.org
Lucene 的优势:
- 容扩展
- 高性能(基于倒排索引)
Lucene 的缺点:
- 只限于 Java 语言开发
- 学习曲线陡峭
- 不支持水平扩展
2024 年 Shay Banon 基于 Lucene 开发了 Compass
2010 年 Shay Banon 重写了 Compass,取名为 Elasticsearch
官网地址:https://www.elastic.co/cn
相比于 Lucene,Elasticsearch 具备以下优势:
- 支持分布式,可水平扩展
- 提供 Restful 接口,可被任何语言调用
访问https://www.elastic.co/cn/downloads/elasticsearch 可以看到,Elasticsearch 目前最新版本是 9.2.3

1.3 倒排索引
传统数据库(MySQL)采用正向索引,比如订单表给订单 ID 创建索引,根据订单 ID 找到具体的订单数据
Elasticsearch 采用倒排索引,索引存的是词条,而不是订单 ID,搜索的时候,根据词条找到订单 ID。所以,倒排索引,会把文档中的内容分成词条再去存
下面说下什么是文档和词条:
- 文档(document):每条数据就是一个文档。比如商品表,每个商品就是一个文档。订单表,每个订单就是一个文档
- 词条(term):文档按照语义分成的词语
下面举例讲一下倒排索引的原理:
比如商品有有 4 条数据,每条数据有 id、title、price 三个字段。
现在先存第 1 个文档,会把 小米手机 分成 小米 和 手机 两个词,前面说了,商品表中一条数据就是一个文档,所以文档的 id 就是 1。
所以 Elasticsearch 存储的词条就是 小米 和 手机,文档 id 是 1,如下图所示:

接下来存第 2 个文档,会把 华为手机 分成 华为 和 手机,华为 在词条中不存在,所以新增一个词条,文档 id 是 2,手机 在词条中已经存在了,不会重复存,而是把文档 id(也就是 2),加在 1 的后面,如下图所示:

第 3、第 4 个文档以此类推

那搜索的时候,是怎么搜索的呢?

总结:
正向索引:基于文档 id 创建索引,查询词条时必须先找到文档,而后判断是否包含词条
倒排索引:对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档 id,而后获取文档
1.4 es 和 MySQL 的概念对比
1.4.1 文档
文档:Elasticsearch 是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息
文档数据会被序列化为 json 格式后存储在 Elasticsearch 中
比如下图中的商品数据,存到 es 中就是 json 格式的

1.4.2 索引
索引(Index):相同类型的文档的集合。前面说到,一条商品数据,就是一个文档,那索引就是 MySQL 中的商品表的概念
映射(mapping):索引中文档的字段约束信息,类似表的结构约束
如下图所示:商品索引也就是商品表,用户索引也就是用户表,订单索引也就是订单表

1.4.3 对比
| MySQL | Elasticsearch | 说明 |
|---|---|---|
| Table(表) | Index(索引) | 索引(index),就是文档的集合,类似于数据库的表(Table) |
| Row(行) | Document(文档) | 文档(Document)就是一条条的数据,类似于数据库中的行(Row)。文本都是 JSON 格式 |
| Column(列) | Field(字段) | 字段(Field)就是 JSON 文档中的字段,类似于数据库中的列(Column) |
| Schema(约束) | Mapping(映射) | 映射(mapping)是索引中文档的约束,例如字段类型约束,类似数据库的表结构(Schema) |
| SQL | DSL | DSL 是 Elasticsearch 提供的 JSON 风格的请求语句,用来操作 Elasticsearch,实现 CRUD |
架构上:
MySQL:擅长事务类型操作,可以确保数据的安全和一致性
Elasticsearch:擅长海量数据的搜索、分析、计算
2 安装
2.1 Docker 安装 Elasticsearch
Docker 安装 Elasticsearch 的启动脚本为:
docker run -d --name es \
-p 9200:9200 -p 9300:9300 \
-v /data/zhuxs/databases/elasticsearch/es_data:/usr/share/elasticsearch/data \
-v /data/zhuxs/databases/elasticsearch/es_plugins:/usr/share/elasticsearch/plugins \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
docker.elastic.co/elasticsearch/elasticsearch:9.0.1
- ES_JAVA_OPTS:表示配置 es 的 Java 堆内存大小,毕竟 es 是根据 Java 实现的
- discovery.type=single-node:表示 es 是单点部署的,而不是集群部署
- 9200 是暴露的 http 协议端口,供用户访问的
- 9300 是 es 容器各个节点之间互联的端口
部署起来后,浏览器访问 ip+9200,如下图所示:

如果浏览器能访问到上图的页面,说明 es 部署成功了
其中 version 表示 es 的版本信息, build_date 表示启动时间
2.2 Docker 安装 Kibana
Kibana 可以给我们提供一个 Elasticsearch 的可视化界面,便于我们学习
kibana 是一个针对 Elasticsearch 的开源分析及可视化平台,好比 MySQL 对应的 Navicat 一样。使用 kibana 可以查询、查看并与存储在 ES 索引的数据进行交互操作,使用 kibana 可以执行高级的数据分析,并能以图表、表格和地图的形式查看数据
Docker 安装 Kibana 的脚本如下:
docker run -d --name kibana \
-e "ELASTICSEARCH_HOSTS=http://localhost:9200" \
-p 5601:5601 \
kibana:7.12.1
- ELASTICSEARCH_HOSTS:表示设置 es 的地址
启动后,浏览器访问 ip+ 5601,如下图所示:

页面在加载,需要等几秒。加载完之后,就会来到下图页面:

- Add data:表示添加数据、导入数据
- Explore on my own:独自探索,表示自己玩
我们选择 Explore on my own 自己玩,点击后进入下图页面:

- Manage:表示管理
- Dev tools:表示开发工具
点击菜单,往下滑,有一个 Dev Tools 的工具

点击 Dev Tools ,进入下图:

Dev tools 开发工具,可以非常方便地发送一个 DSL 请求
页面中会有一个默认请求:
- GET:表示是 GET 请求
- _search:表示要做一次搜索
- query:表示查询
- match_all:表示匹配所有数据
点击运行按钮,就会向 Elasticsearch 发送一个请求

3 分词器
es 在创建倒排索引时需要对文档分词。在搜索时,需要对用户内容进行分词。但默认的分词规则对中文处理并不友好。
下面举个例子:
POST /_analyze
{
"analyzer": "standard",
"text": "黑马程序员学习java太棒了"
}
- POST:请求方式
- _analyze:请求路径。这里省略了 http://192.168.150.101:9200,有 kibana 帮我们补充
- 请求参数,json 风格
- analyzer:分词器类型,这里使用默认的
standard分词器(标准分词器) - text:要分词的内容
- analyzer:分词器类型,这里使用默认的
在 kibana 中执行,analyzer 值为 english 表示使用英语分词,下图中可以看到,英文 java 被分到一个词,但是中文每个字都分成一个词

即使使用标准分词器,analyzer 的值为 standard,效果还是一样,还是一个字一个词,说明对中文理解的不够好

3.2 IK 分词器
处理中文分词,一般使用 IK 分词器。https://github.com/medcl/elasticsearch-analysis-ik

点进去可以看到,IK 分词器有 2 种模式:ik_smart、ik_max_word
3.3 安装
IK 分词器有在线安装和离线安装,这里推荐离线安装
先下载 IK 分词器插件,下载后是一个后缀为 .zip 的压缩包,解压缩后把文件夹重命名为 ik
安装插件需要知道 elsticsearch 的 plugins 的目录位置,在启动 es 时,我们用了数据卷挂载,映射到磁盘上的目录为 /data/zhuxs/databases/elasticsearch/es_plugins ,然后把 ik 这个文件夹放到 /data/zhuxs/databases/elasticsearch/es_plugins 目录下就行
最后执行命令:docker restart es 重启容器就行
注意:使用的 ik 分词器得和 es 版本一致
3.4 测试
IK 分词器包含两种模式:
- ik_smart:最少切分。从粗粒度切分,会从字符越来越多开始到字符越来越少去看,比如 5 个字是不是一个词,如果不是,看 4 个字是不是一个词,如果还不是,看 3 个字是不是一个词,以此类推。比如
程序员3 个字刚好是一个词,那就不再继续往下看程序是不是一个词。所以分词粒度比较粗,词比较少。优势是分词少,占用内存空间也少, - ik_max_word:最细切分。从细粒度切分,在分词时会分更多词,比如把
程序员分成程序员、程序、员3 个词,这样分词就会分的很细。优势是由于分词更细更多,被搜索到的概率就越大。缺点是:分的词更多,更占用内存空间
IK 分词器安装好后,测试一下效果
先测试 ik_smart,如下图所示,没有再把每个字都分成一个词了,说明效果还不错

再测试下 ik_max_word,可以看到:程序员,被分成了 程序员、 程序 和 员

3.5 扩展词库
ik 分词器无法识别近几年的热门网络用语。如下图所示,把 奥力给 分成了 3 个字,说明分词器识别不了这个网络词语,有办法让 ik 分词器识别吗?有

又比如,文本中出现了一些禁忌词,比如涉及一些不能播的文章,希望分词后能够禁止出现这些词,那分词器能实现这种个性化设置吗?可以
ik 分词器支持扩展词库,只需要修改 ik 分词器目录中的 config 目录中的 IKAnalyzer.cfg.xml 文件:

ext 是扩展的意思,dist 是字典的意思。在 ext_dic 文件中,写入 奥力给,ik 分词器就能识别这个网络词语了
把某些敏感词停掉,不让用,也就是不让 ik 分词器识别并分词,怎么办?还是修改 ik 分词器目录中的 config 目录中的 IKAnalyzer.cfg.xml 文件:

stopword 是停止词汇的意思,把敏感词写到 stopword.dic 文件中,ik 分词器就不会对它分词了
IKAnalyzer.cfg.xml 文件中默认是没配置扩展字典和停止词字典的,需要自己配置,如下图所示:

现在配置扩展字典和停止字典,如下图所示:

虽然我们配置了扩展字典和停止字典,但是 ext.dic 和 stopword.dic 是我们随手写的名字,这两个文件根本不存在,所以还需要新建两个文件。
在 IKAnalyzer.cfg.xml 同级的目录下新建 ext.dic 和 stopword.dic 两个文件就行

比如在 ext.dic 文件中添加词:

4 索引操作
4.1 mapping 属性
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/8.19/mapping-params.html
mapping 是对索引中文档的约束,mapping 属性有很多,如下图所示:

常见的 mapping 属性包括:
- type:字段的类型,常见的简单类型有:
- 字符串:字符串有 2 种,一是 text(可分词的文本),二是 keyword:精确值,例如国家、品牌、ip 地址,不能拆分
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object,表示字段是一个对象,对象中有其它属性。对象中的属性也是可以参与搜索的
- 注意:没有数组这个类型,比如某字段是:“score”:[80 ,90 ,100],数组中的元素是 integer 类型,则 score 字段的类型也是 integer 类型
- Index:是否创建索引,默认为 true,表示会创建倒排索引,可以参与搜索。如果不手动设置,则默认每个字段都会创建索引,每个字段都能参与搜索。但是,不是所有字段都需要参与搜索,比如图片地址字段,搜索有意义吗?因此,这类字段可以设置不创建索引
- analyzer:使用哪种分词器,只有 text 类型才需要分词
- properties:该字段的子字段
4.2 创建索引
ES 中通过 Restful 请求操作索引库、文档。请求内容用 DSL 语句来表示,创建索引库和 mapping 的 DSL 语法如下:

上图中的右边可以看到,索引库中有 3 个字段:
- info:字段类型为 text(字符串),使用
ik_smart分词器 - email:字段类型是 keyword(不需要分词),不创建索引
- name:字段类型是对象,properties 表示有子字段,子字段是 firstName,类型是 keyword。上图在 name 字段少了一个属性:“type”: “object”
下面举例,创建一个名叫"heima"的索引库

也可以直接创建索引,不带 mapping 映射
首先,执行下面的命令:
# 查看es中所有的索引
GET /_cat/indices
cat:表示查看
indices:表示索引
这个命令执行的结果如下:

第 1 行:只是一个安全警告
第 2 - 8 行:是 es 中现有的索引。其中第三列是索引名称。这些索引是 kibana 工具连接 es 时创建的临时索引,每次启动时这些索引名称可能都不太一样,
执行命令:
GET /_cat/indices?v
v:表示显示标题
执行效果如下:

health:健康状态。健康状态有 3 种,分别是红绿黄。green(绿)表示索引的健康状态是健康的,yellow(黄)表示索引可用,但是处于危险状态,red(红)表示索引不可用
status:索引的打开状态,默认是打开的
index:索引名称
uuid:表示索引的唯一标识
pri:索引主分片的个数
rep:索引的副本分片的个数
docs.count:每个索引下面的文档数
docs.deleted:文档删除数量
store.size:存储大小
pri.store.size:主分片的存储大小
下面直接创建一个索引:
# 创建一个商品索引
PUT /products

acknowledged:值为 true,表示索引创建成功
shards_acknowledged:值为 true,表示分片创建成功
index:值为 products,表示索引名称是 products
再执行 GET /_cat/indices?v 看下索引有没有创建好呢?

上图可以看到,products 索引已经创建好了,但是健康状态是 yellow,表示可用,但是危险。为什么刚创建的索引就处于危险状态呢?这是因为它的主分片和副本分片默认都是 1,而主分片和副本分片都在同一个服务器上,一旦服务器损坏,主分片和副本分片的数据都会丢失。这时候 es 就认为你做的副本(备份)是没有意义的,索引是不安全的
那有没有办法在创建索引的时候,就把它变成绿色呢?有!在创建索引的时候做一些配置,比如设置主分片数量为 1,副本数量为 0
# 创建一个索引
PUT /orders
{
"setting": {
"number_of_shards": 1, //主分片数量
"number_of_replicas": 0 //副本分片数量
}
}
创建后可以查看一下,健康状态变成绿色了,如下图所示:

4.3 查询索引
查看索引库语法:
GET /索引库
示例:
GET /heima
下面举例:

如何查看索引的映射信息呢?
# 查看某个索引的映射信息
GET /heima/_mapping
4.4 删除索引
删除索引语法:
DELETE /索引库
DELETE /heima
4.5 修改索引
在 es 中,索引是不允许修改的,因为索引创建好后,它的 mapping 映射都创建好了,它会基于这些 mapping 去创建倒排索引。如果你要修改字段,就会导致原有的倒排索引彻底失效。不像 MySQL,创建表之后还可以修改表字段
es 虽然禁止你在索引库中修改原有的字段,但是允许你添加新的字段,语法如下:
PUT /索引库名/_mapping
{
"properties": {
"新字段名": {
"type": "integer"
}
}
}
注意,新字段名必须要是一个全新的字段名,否则就会认为你在改字段,就会报错
下面举例:

如果把 age 字段改成 long 类型,再添加一次,就会报错,如下图所示

5 文档操作
5.1 添加文档
新增文档的 DSL 语法如下:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
}
}
比如下面想插入一个文档 id 为 1 的数据

实战演示:

上图可以看到,“result”: “created” 表示插入成功
5.2 查询文档
查询文档 DSL 语法如下:
GET /索引库名/_doc/文档id
示例:
GET /heima/_doc/1
上面已经插入文档成功了,现在想查询一下,如下图所示:

_index:表示索引库是 heima
_id:表示文档 id 为 1
version:表示版本控制,每做一次文档修改,版本号都会 +1
source:表示你插入的原始文档
5.3 删除文档
删除文档的 DSL 语法如下:
DELETE /索引库名/_doc/文档id
示例:
DELETE /heima/_doc/1

“result”: “deleted”,表示删除成功
删除之后再查一下,如下图所示:

"found"的值变成 false 了,而且出现了 404,说明删除成功了
现在再重新插入一次,并执行查询,如下图所示,发现 version 变成 3 了,因为第一次插入,版本号为 1,删除后,版本号变成 2,再插入,版本号变成 3 了,所以每次写操作都会导致版本号不断增加

5.4 修改文档
5.4.1 全量修改
全量修改,会删除旧文档,添加新文档,DSL 语法如下:
PUT /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2":"值2",
// ... 略
}
细心的兄弟们会发现,这跟添加文档的 DSL 语法几乎一样,唯一的区别就是:添加文档是 POST,修改文档是 PUT
那如何删除旧文档呢?会根据文档 ID,去索引库中找到旧的文档,然后删掉。如果传的文档 ID 在索引库中不存在,就会直接新增。所以,使用 PUT 请求,如果文档 ID 存在则修改,不存在则新增,相当于可以替代 POST 请求
下面实战演示:

上图中只修改了 email,结果显示: “result”: “updated”,表示更新成功
现在修改文档 ID 为 3 的文档,这个文档 ID 根本不存在,所以会新增,如下图所示

上图中"result": “created”,表示新增成功。
5.4.2 新增修改
增量修改,修改指定字段值。其 DSL 语法如下:
POST /索引库名/_update/文档id
{
"doc": {
"字段名": "新的值"
}
}
下面实战演示,只修改 email 字段。“result”: “updated”,表示修改成功

5.5 总结
- 创建文档:POST /索引库名/_doc/文档 id { json 文档 }
- 查询文档:GET /索引库名/_doc/文档 id
- 删除文档:DELETE /索引库名/_doc/文档 id
- 修改文档
- 全量修改:PUT /索引库名/_doc/文档 id { json 文档 }
- 增量修改:POST /索引库名/_update/文档 id { “doc”: {字段} }
6 DSL 查询文档
6.1 DSL 查询分类
Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
- 查询所有:查出所有数据,一般测试用。例如:match_all。但是不是真的查出所有,而是不加限制的意思,实际上会分页,每页查 20 条
- 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。有
match_query和multi_match_query - 精确查询:根据精确词条值查询数据,一般是查找 keyword、数值、日期、boolean 等类型字段。例如
- ids:根据 id 精确匹配
- range:根据数值范围查询
- term:按照数据的值查询
- 地理(geo)查询:根据经纬度查询,有
geo_distance和geo_bounding_box - 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool:利用布尔这种逻辑运算将其他条件组合起来
- function_socre:控制相关度算分
DSL 的基本语法如下:
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
indexName:索引库的名称
_search:固定写法,表示要搜索
比如查询所有的 DSL 语法就是这样的:
GET /indexName/_search
{
"query": {
"match_all": {
}
}
}
注意:查询文档的 DSL 是:GET /indexName/_search,这是一种简写,全写可以这样写:GET /indexName/_doc/_search
6.2 DSL 基本语法
6.2.1 查询所有
上一节已经讲了查询所有的 DSL 语法,下面实战演示:

took:花费多长时间,单位是毫秒
time_out:是否超时
shards:当前索引的分片信息
hits:命中的数据、查询的结果
hits -> total:搜索到的总条数
hits -> max_score:文档的相关性得分
hits -> hits:是一个数组,存的是一个个文档对象
hits -> hits -> _source:原始的文档信息
6.2.2 全文检索查询
全文检索查询,会对用户输入内容分词,常用于搜索框检索。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"match": {
"字段名": "字段值"
}
}
}

如上图所示,会对值"外滩如家"进行分词,然后对 all 字段进行检索。all 字段的值中,包含外滩或者如家,只要能匹配到一个,就能检索出来
全文检索还有一种检索方式:multi_match,与 match 查询类似,只不过允许同时查询多个字段,但是查询的字段越多,查询性能越差。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "字段值",
"fields": ["字段1", "字段2"]
}
}
}
查询的效果就是:比如查询 A、B、C 三个字段,这样有一个字段的值为"字段值"的分词,就能检索到。下面实战演示:

上图中,对 外滩如家 进行分词,比如分成 外滩 和 如家,然后搜索了 3 个字段,只要有一个字段的值中包含了 外滩 或者 如家,就能匹配到
DSL
6.2.3 精确查询
精确查询一般是查找 keyword、数值、日期、boolean 等类型字段,这些类型有个共同特点:她们的值是一个不可分割的整体。所以不会对搜索条件分词。常见的有:
- term:根据词条精确值查询
- range:根据值的范围查
term 查询的 DSL 语法如下:
GET /indexName/_search
{
"query": {
"term": {
"字段名": {
"value": "字段值"
}
}
}
}
比如精确搜索 city 为上海的文档,如下图所示:

注意:如果要搜索的字段是 keword 类型,则只能精确地按照全部内容搜索,比如 city 字段是 keword 类型,想搜上海,那只能搜到 city=上海 的记录,搜不到 city=上海浦东 的记录。如果要搜索的字段是 text 类型,则按照分词之后的词来搜,比如 city 的值为 上海浦东,被分成 2 个词 上海 和 浦东,则按照上图搜索,也能搜到这条数据
6.2.4 范围查询
range 查询的 DSL 语法如下:
GET /indexName/_search
{
"query": {
"range": {
"字段名": {
"gte": "起始值", //gte表示大于等于
"lte": "结束值", //lte表示小于等于
}
}
}
}
比如像查询价格大于 100,小于等于 300 的文档,如下图所示:

gte:大于等于,如果只要大于呢?那就是 gt
lte:小于等于,如果只要小于呢?那就是 lt
6.2.5 前缀查询
前缀查询:查询以某某某为开头的数据。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"prefix": {
"字段名": {
"value": "字段值"
}
}
}
}
下面查询 title 字段以 “小” 开头的数据

6.2.6 通配符查询
wildcard 关键字:通配符查询,?用来匹配一个任意字符,* 用来匹配多个任意字符。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"wildcard": {
"字段名": {
"value": "字段值*"
}
}
}
}
示例如下:

6.2.7 多 id 查询[ids]
ids 关键字:值为数组类型,用来根据一组 id 获取多个对应的文档。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"ids": {
"values": ["aaa", "bbb"]
}
}
}
6.2.8 模糊查询
fuzzy 关键字:用来模糊查询含有指定关键字的文档。其 DSL 语法如下:
GET /indexName/_search
{
"query": {
"fuzzy": {
"字段名": "要搜索的值"
}
}
}
注意:fuzzy 模糊查询,最大模糊错误必须在 0 - 2 之间
- 搜索关键词长度为 2,不允许存在模糊
- 搜索关键词长度为 3-5,允许一次模糊
- 搜索关键词长度大于 5,允许最大模糊
示例如下:

6.2.9 过滤查询
过滤查询,其实准确来说,ES 中的查询操作分为 2 种:查询(query)和过滤(filter)。查询即是之前提到的 query 查询,它默认会计算出每个返回文档的得分,然后根据得分排序。而**过滤(filter)**只会筛选出符合的文档,并不计算得分,而且它可以缓存文档。所以,单从性能考虑,过滤比查询更快。换句话说,过滤适合在大范围筛选数据,而查询则适合精确匹配数据。一般应用时,应先使用过滤操作过滤数据,然后使用查询匹配数据

为什么过滤性能更高?query 查询是精确查询,会计算每个文档的得分,然后根据得分排序。而计算得分是一个非常复杂的过程。如果在过滤查询的基础之上做查询,会大大减少 query 查询去 ES 中查询数据的数据量,而且在此基础上计算得分,会更快
使用 filter 的 DSL 语法如下:
GET /indexName/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}} //查询条件
],
"filter": {...} //过滤条件
}
}
}
注意:在执行 filter 和 query 时,先执行 filter,再执行 query。ES 会自动缓存经常使用的过滤器,以加快性能
常见的过滤类型有:term、terms、ranage、exists、ids 等
先用 term 做示例:
先用 term 查询 description 字段值为 好吃 的数据,可以搜到好几条数据,如下所示:

现在先过滤出 description 字段值为 浣熊 的数据,然后在此基础上查询 description 字段值为 好吃 的数据,只能搜索到一条数据,如下图所示:

再用 terms 做示例,terms 表示根据多个条件过滤,如下图所示

现在用 range 做示例,range 表示范围过滤,下图是先过滤出价格在 0-10 之间的数据,再在此基础上查询 description 字段值为 豆腐 的数据

用 exists 做示例,exists 表示存在,过滤存在指定字段,获取字段不为空的索引记录使用。下图就是先过滤出带 aaaa 这个字段的数据,注意是根据字段名来过滤,而不是字段值

最后用 ids 示例,ids 表示过滤出 id 字段值为指定值的字段,比如下图是先过滤出 id 字段值为 1、2、3

6.2.10 地理查询
根据经纬度查询,
- geo_bounding_box:查询 geo_point 值落在某个矩形范围的所有文档
- geo_distance:查询到指定中心点小于某个距离值的所有文档
geo_bounding_box 查询的 DSL 语法如下:
GET /indexName/_search
{
"query": {
"geo_bounding_box": {
"字段名": {
"top_left": {
"lat": "经度值"
"lon": "维度值"
},
"bottom_right": {
"lat": "经度值"
"lon": "维度值"
}
}
}
}
}
下面实战演示:

上图中有 2 个点,第一个点就是 top_left,第二个点是 bottom_right
那搜索的效果就是:在这 2 个点之间,画一条横线和竖线,形成一个矩阵。矩阵范围内的点就能被检索到

geo_distance 查询的 DSL 语法如下:
GET /indexName/_search
{
"query": {
"geo_distance": {
"distance": "15km", //半径
"字段名":"31.21, 121.5" //经纬度,以这经纬度为中心点
}
}
}
其检索效果是,搜索以指定的点为中心点,半径为 15 公里的圆的内部的所有点,如下图所示:

6.2.11 复合查询
6.2.5.1 算分函数查询
复合(compound)查询:将其它简单查询组合起来,实现更复杂的搜索逻辑
- fuction_score:算分函数查询,可以控制文档相关性算法,控制文档排名。例如百度竞价。使用 Function Score Query,可以修改文档的相关性算分(query score),根据新得到的算分排序。其 DSL 语法如下:

下面是一个案例

6.2.5.2 布尔查询
布尔查询(Boolean Query)是一个或多个查询子句的组合。子查询的组合方式有:
- must:必须匹配每个子查询,类似 “与”,表示必须满足
- should:选择性匹配子查询,类似 “或”
- must_not:必须不匹配,不参与算分,类似 “非”
- filter:必须匹配,不参与算分,看上去跟 must 一样,但是底层不一样
es 在搜索的时候,不仅要看文档是否匹配,还要看文档和关键字之间的相关度,也就是打分,分值越高越靠前。打分有复杂的算分函数,每做一次算分会消耗一些资源。如果子查询比较多,每一个都参与算分,就会影响性能。如果使用 must_not、filter,不参与算分,它们只会返回满足还是不满足、是或否,所以会提升性能
下面是一个案例:关键词搜索需要参与算分,所以关键词使用 must

再来一个案例:

示例 1:查询 id 必须为 1 的数据

示例 2:查询 id 为 1,且 title 为 “小浣熊” 的数据

示例 3:如果是 should,满足一个条件就行,比如查询 id 为 1,或者 title 为 “浣熊”

示例 4:must_not,表示都不能,不能满足任何一个,比如查询 id 不为 1,且 title 不为 “浣熊” 的数据

6.2.12 多字段查询
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "要搜索的字段值",
"fields": ["字段1", "字段2"]
}
}
}
注意:字段类型分词,将查询条件分词之后进行查询该字段,如果该字段不分词就会将查询条件作为整体进行查询
示例如下:

搜的是 “浣猫”,但是因为会分词,把 “浣猫” 分成 “浣” 和 “猫”,然后拿这 2 个字去搜索,所以 title 和 description 字段值中,带有 “浣” 字就能搜索到
6.2.13 默认字段分词查询
GET /indexName/_search
{
"query": {
"query_string": {
"default_field": "字段名",
"query": "字段值"
}
}
}
注意:查询字段分词,就将查询条件分词查询,查询字段不分词,则将查询条件不分词查询
示例如下:

6.3 搜索结果处理
6.3.1 排序
Elasticsearch 支持对搜索结果排序,默认是根据相关度算分(_score)来排序,可以排序字段类型有:keyword 类型、数值类型、地理坐标类型、日期类型等。其 DSL 语法如下:
GET /indexName/_search
{
"query":{
"match_all":{}
},
"sort": [
"字段名": "desc" //排序字段和排序方式ASC、DESC
]
}
sort 里面是一个数组,意味着可以根据多个字段排序。多字段排序时,先按照第一个字段排序,第一个字段相等,再按第二个字段排序。下面是按照地理位置排序:
GET /indexName/_search
{
"query":{
"match_all":{}
},
"sort": [
"_geo_distance": {
"字段名":"经度,维度",
"order":"asc",
"unit":"km" //单位是km
}
]
}
注意:一旦排序,文档的_score 字段(也就是得分)就会为空
6.3.2 分页
Elasticsearch 默认情况下只返回 top10 的数据,而如果要查询更多数据就需要修改分页参数了。通过修改 from、size 参数来控制要返回的分页结果,其 DSL 语法如下:
GET /indexName/_search
{
"query":{
"match_all":{}
},
"from": 990, //分页开始的位置,默认是0
"size":10, //期望获取的文档总数
"sort": [
{
"price": "asc"
}
]
}
但是分页跟 MySQL 一样,都是先获取前 1000 条数据,截取 990-1000 这 10 个文档。所以也有深分页的问题。ES 设定结果集查询的上限是 10000
针对深分页,es 提供了 2 种解决方案:
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据,也就是 MySQL 中的,比如上一页最大 id 是 100,那下一页就查询 where id > 100。缺点是只能一页页地查。官方推荐使用的方式
- scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用
6.3.3 高亮
高亮:就是在搜索结果中把搜索关键字突出显示
原理:将搜索结果中的关键字用标签标记出来,在页面中给标签添加 CSS 样式
其 DSL 语法如下:
GET /indexName/_search
{
"query":{
"match":{ //注意,这里一定不能使用match_all
"字段名": "关键字"
}
},
"highlight": {
"fields": { //指定要高亮的字段
"字段名":{ //高亮字段和搜索字段必须一致
"pre_tags": "<em>", //用来标记高亮字段的前置标签
"post_tags": "</em>", //用例标记高亮字段的后置标签
}
}
}
}
示例如下,查询所有字段

也可以自定义标签

注意:高亮并没有修改原文档,而是把高亮结果放在 highlight 字段中

6.3.4 返回指定字段[_source]
_source 关键字:是一个数组,在数组中用来指定展示那些字段
GET /indexName/_search
{
"query":{
"match_all":{}
},
"_source": ["字段名1", "字段名2"]
}
7 数据聚合
7.1 聚合种类
聚合(aggregations):可以实现对文档数据的统计、分析、运算。聚合常见的有 3 类:
- 桶(Budget)聚合:用来对文档做分组。类似于 MySQL 的 group by
- TermAggregation:按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一个月为一组
- 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等。只能根据数值字段做聚合
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求 max、min、avg、sum 等
- 管道(pipeline)聚合:其它聚合的结果为基础做聚合
7.2 DSL 实现聚合
注意:text 类型是不支持聚合的
7.2.1 DSL 实现 Budget 聚合
比如,统计酒店品牌有几种。DSL 语法如下:
GET /indexName/_search
{
"size": 0, //搜索时分页的参数,为0表示显示的文档数量为0,表示结果中不包含文档,只包含聚合结果
"aggs": { //定义聚合
"brandAgg": { //给聚合起个名字,这是自定义的
"terms": { //聚合类型,按照品牌值聚合,所以选择term
"field": "brand", //参与聚合的字段
"size": 20 //希望获取的聚合结果数量
}
}
}
}
下面实战演示:

上图中可以看到,自定义的聚合名称,在结果中展示了出来。key 是字段值,也就是品牌名称,doc_count 是文档数量
默认情况下,Budget 聚合会统计 Budget 内的文档数量,记为_count,并且按照 _count 降序排序,我们可以修改结果排序方式:
GET /indexName/_search
{
"size": 0, //搜索时分页的参数,为0表示显示的文档数量为0,表示结果中不包含文档,只包含聚合结果
"aggs": { //定义聚合
"brandAgg": { //给聚合起个名字,这是自定义的
"terms": { //聚合类型,按照品牌值聚合,所以选择term
"field": "brand", //参与聚合的字段
"size": 20, //希望获取的聚合结果数量
"order": {
"_count": "asc" //按照 _count 升序排序
}
}
}
}
}
默认情况下,Budget 聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加 query 条件即可。
GET /indexName/_search
{
"query": {
"range": {
"price": {
"lte": 200 //只对200元以下的文档聚合
}
}
}
"size": 0, //搜索时分页的参数,为0表示显示的文档数量为0,表示结果中不包含文档,只包含聚合结果
"aggs": { //定义聚合
"brandAgg": { //给聚合起个名字,这是自定义的
"terms": { //聚合类型,按照品牌值聚合,所以选择term
"field": "brand", //参与聚合的字段
"size": 20, //希望获取的聚合结果数量
"order": {
"_count": "asc" //按照 _count 升序排序
}
}
}
}
}
7.2.2 DSL 实现 Metrics 聚合
例如,我们要获取每个品牌的用户评分的 min、max、avg 等值。我们可以利用 stats 聚合,其 DSL 语法如下:
GET /indexName/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": { //是brands聚合的子聚合,也就是分组后对每组分别计算
"score_stats": { //聚合名称
"stats": { //聚合类型,这里stats可以计算min、max、avg等
"field": "score" //聚合字段,这里是score
}
}
}
}
}
}
7.3 自动补全
什么是自动补全?如下图所示:我输入 s,立马就出现跟 s 有关的搜索项供我们选择

7.3.1 拼音分词器
要实现根据字母做补全,就必须对文档按照拼音分词。在 Github 上恰好有 Elasticsearch 的拼音分词插件。地址是:https://github.com/medcl/elasticsearch-analysis-pinyin
安装方式跟 ik 分词器一样,分三步:解压、上传到 Elasticsearch 的 es-plugins 目录、重启 Elasticsearch

上图是解压后,文件夹重命名为 py,表示拼音。然后上传到 es-plugins 目录下
下面实战演示:可以看到拼音分词器把中文都分成了拼音,甚至有把每个中文的首字母拼在一起

7.4 数据同步
Elasticsearch 如何与 MySQL 保持数据同步呢?有以下几种方式:
- 同步调用,写入 MySQL 后,立马写入 es。业务耦合性太强,不推荐
- MQ 异步,写入 MySQL 后,发送消息到 MQ,消费者负责把数据写到 es 中。虽然复杂度上升了,但是解耦了
- 监听 MySQL 的 binlog,使用 canal
8 集群 Cluster
8.1 相关概念
8.1.1 集群
一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是 elasticsearch。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
为什么要引入集群?因为单节点存在以下问题:
- 单节点故障问题。比如自然灾害、进程崩溃等导致单节点出现故障
- 存在单节点并发压力
- 存在单节点物理上限问题,比如单节点最多能存 2T 的数据,随着业务量增多,很快这 2T 的磁盘不够用
8.1.2 节点
一个节点是你进群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点
8.1.3 索引
一组相似文档的集合
8.1.4 映射
用来定义索引存储文档的结构,如:字段、类型等
8.1.5 文档
索引中一条记录,可以被索引的最小单元
8.1.6 分片
Elasticsearch 提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的 “索引”,这个 "索引"可以被放置到集群中的任何节点上
8.1.7 复制
Index 的分片中一份或多份副本
更多推荐
所有评论(0)