ElasticSearch

版本:7.6.1

Elastic Searcher简称es,是一个开源的高扩展的分布式全文检索引擎,es使用Java开发Lucene作为核心实现所有的索引和搜索功能

Lucene工具包;是一套信息检索工具包,jar包,不包含搜索引擎
包含:索引结构,读写索引的工具,排序,搜索规则。
ElasticSearch是基于Lucene做了一些封装和增强

Elastic Search对比Solr

1.es基本是开箱即用,简单,Solr回稍微复杂
2.Solr利用Zookeeper进行分布式管理,而es自身带有分布式协调管理功能
3.Solr支持更多格式的数据,比如JSON,XML,CSV,而ElasticSearch仅支持JSON文件格式
4.Solr官方提供的功能更多,而Elastic Search本身更注重于黑犀牛功能,高级功能更多的是由插件提供的,例如图形化界面需要Kibana
5.Solr查询块,但更新索引时慢(插入删除慢)用于电商等查询多的应用,
​ ES建立索引块(即查询慢),实时性查询快
​ Solr是传统搜索应用的有利解决方案,但ES更适用于新兴的实时性搜索应用
6.Solr比较成熟,而ES相对开发维护者较少,更新太快,学习成本高

Elastic Search安装

jdk最低1.8

目录

解压即用

bin 启动文件
config 配置文件
​ log4j2 日志文件
​ jvm.options Java虚拟机的相关配置
​ elasticsearch.yml elasticsearch的配置文件
lib 相关jar包
modules 模块
plugins 插件

打开bin目录双击运行elasticsearch.bat

访问端口9200

Snipaste_2022-09-01_22-26-28

head

可视化插件 elasticsearch

headhttps://github.com/mobz/elasticsearch-head/archive/master.zip

解决跨域问题(跨端口 跨IP)

​ 打开ES配置文件elasticsearch.yml
​ http.cors.enabled: true
​ http.cors.allow-origin: “*”
​ 跨域打开 全部可访问

启动可视化
	npm install 
	npm start

image-20220122212606658

索引

Snipaste_2022-09-01_22-28-22

可以把这个索引当做是数据库 可以把这个可视化界面当做是数据展示工具,后面的查询使用kiabana

ELK了解

安装kiabana之前先了解下ELK(Elasticsearch Logstash Kibana) 也称之Elasticseacher Stack, 而Elasticsearch是一个基于Lucene 分布式 通过Restful方式进行交互的近实时搜索平台框架,类似百度 谷歌等搜索大数据搜索引擎的场景都可使用作为底层支持框架,Logstash是ELK的中央数据流引擎,用于不同目标(文件/数据存储/MQ)搜集不同格式的数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等) kiabana可以将ES的数据通过友好的页面展示,提供实时分析功能,

kibana

kibna是一个针对Elascticsearch的开源分析可视化平台,用来搜索 查看交互存储在Elasticsearch索引中的数据。使用kibana可以通过各种图表进行高级数据分析及展示,kibana让海量数据更容易理解,操作简单基于浏览器的用户界面快速的创建仪表盘实时显示Elasticsearch查询动态。

注意安装和Elasticsearch版本

启动

解压后打开bin目录
image-20220122223141818

访问路径:127.0.0.1:5601
找到开发工具 之后在此处操作

国际化

找到kibana的配置文件 kibana.yml最后一行i18n.locale: "zh-CN"即可

Elasticsearch核心

问题

集群,节点,索引,类型,文档,分片,映射是什么?

Elasticsearch和关系型数据与对比

DB

Elasticsearch

数据库(Database)

索引(indices)

表(tables)

types(正在弃用)

行(rows)

documents(文档)

字段(cloumns)

fields

elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可包含多个类型(表),每个类型下又可包含多个文档(行),每个文档中又可包含多个字段(列)

物理设计

elasticsearch在后台把每个索引划分为多个分片,每个分片可以在集群中的不同服务器间迁移(elasticsearch自己也是个集群)

逻辑设计

一个索引类型中,包含多个文档,例如文档1 文档2,当搜索一篇文档时的顺序:索引==>类型==>文档id。通过这个组合我们就能索引到某个具体的文档,注意:id不必是整数,实际上它是个字符串

文档

elasticsearch是面向文档的,之所以这么说,是因为索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要的属性:
1,自我包含,一遍文档同时包含字段和对应的值,也就是同时包含key:value
2,可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这样的;{就是JSON,我们使用框架会自动生成索引}
3,灵活的结构,文档不依赖预先定义的模式,关系数据库中,需要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候我们可以忽略字段或者动态的添加一个新字段

尽管我们可以随意的新增或忽略某个字段,但是每个字段的类型还是很重要,比如一个年龄字段,可以是字符串也可以是整型,因为elasticsearch会保存字段和类型之间的映射及其其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称之为映射类型。

类型

类型是文档的逻辑容器,就像关系型数据库那样,表格是行的容器,类型中对于字段的定义称为映射,比如name映射为字符串类型。文档是无模式的,不需要拥有映射中所定义的所有字段,比如新增一个字段,elasticsearch会自动将新字段加入映射,但是这个字段不确定是什么类型。所以最安全的做法就是提前定义好所需要的映射,然后在使用。

索引

索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置,然后保存到各个分片上。

物理设计:节点和分片怎么工作?

一个集群至少有一个节点,一个节点就是一个elasticsearch进程,节点可以有多个索引,创建一个索引将会有5个分片(primary shard 也称为主分片)来构成,而每个主分片又会有一个副本(replica shard 又称为复制分片)

image-20220123152041521

可以看到主分片和对应的复制分片都不在一个节点内,这样即使是一个节点挂掉,数据也不会丢失。实际上一个分片就是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你那些文档包含特定的关键字。

倒排索引

elasticsearch使用的是一种称为倒排索引的结构,采用的是Lucene倒排索引作为底层。这种结构利于全文检索,一个索引是由文档中所有不重复的列表构成,对于每个词,都有一个包含它的文档列表、

例子:

For the sins I am about to commit	#放入文档1
may James Gosling forgive me		#放入文档2

创建倒排索引,我们需要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复词条的排序列表:

trem

doc1

doc2

For

×

may

×

×

the

James

x

sins

Gosling

×

I

forgive

am

×

me

about

to

x

commit

例如搜索 For the
image-20220123153745485

文档1包含2个词,文档2包含一个词,显然文 档1 的权重更高。

Elasticsearch的索引和Lucene的索引对比

在Elasticsearch中,索引(库)被分为多个分片,每份分片是一个Lucene的索引,所以一个Elasticsearch多个Lucene索引组成的(多个Lucene倒排索引)

IK分词器插件

标准的中文分词器

IK提供两种算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分

安装

解压到elasticsearch的plugins下即可

然后重启

[2022-01-23T19:50:23,025][INFO ][o.e.p.PluginsService ] [LAPTOP-0054K098] loaded plugin [analysis-ik]

代表已经加载了ik分词器

也可已使用elasticsearch-plugin list 查看插件列表

然后重启kiabana

ik_smart测试
image-20220123195906747

ik_max_word:最细粒度划分
image-20220123195918278

由分词可看出,ik不是瞎分的,而是有类似字典

当有自己的词不连贯的时候,会一个字一个字的分出来,这是我们需要自己将我们的词加入到ik的分词器字典中

ik分词器增加自定义

1.找到ik的配置文件
image-20220123201009107

2.编写自己的词典(lph.dic)
image-20220123201214280

3.重启
image-20220123201515475

4.测试
image-20220123201815824

发现会变成一个词了

Rest风格

一种软件架构风格,不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互的软件,基于rest风格的代码可以更简洁有层次,更易于实现缓存等机制。

基本的Rest命令说明:

method

URL地址

描述

PUT

localhost:9200/索引名称/类型名称/文档id

创建文档(指定文档id)

DELETE

localhost:9200/索引名称/类型名称/文档id

删除文档

POST

localhost:9200/索引名称/类型名称

创建文档(随机文档id)

GET

localhost:9200/索引名称/类型名称/文档id

根据文档id查询文档

POST

localhost:9200/索引名称/类型名称/文档id/_update

修改文档

POST

localhost:9200/索引名称/类型名称/_Search

查询所有数据

举个栗子

索引相关的基本操作

1.创建一个索引(增)

PUT /索引名称/类型名(在淘汰)/文档id
{

​ “请求体”

​ }

image-20220123204323740

#! Deprecation: [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}).
{
"_index" : "test1",		#库
"_type" : "type1",		#类型
"_id" : "1",				#id
"_version" : 1,			#版本 未被更新过
"result" : "created",		#创建状态
"_shards" : {
 "total" : 2,
 "successful" : 1,
 "failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}

查看数据
image-20220123204759225

2.基本的数据类型

字符串类型
text,keyword
数值类型
long,integer,short,byte,double,float,half float,scaled float
日期类型
date
te布尔值类型
boolean
二进制类型
binary

3.定义字段类型

image-20220123205643880

4.获得规则

get 后可以使库名或表名
类似于MySQL查看表结构

image-20220123210309056

5.查看默认的信息

image-20220123211136554

如果我们不写类型 Elasticsearch会默认配置字段类型

6.扩展

image-20220123211635330

image-20220123211816808

7.修改(改)

可使用PUT命令覆盖 也可使用UPDATE

PUT方式

version此时改变,result 更新状态

image-20220123212236189

update方式

image-20220123212615228

8.删除(删)

image-20220123212938061

文档相关的基本操作

1.添加数据

# 创建index
PUT /lph/user/1
{
  "name":"铁柱",
  "age":30,
  "info":["钱","美元","欧元"]
}

image-20220123224943182

2.获取数据

# 获取数据
GET /lph/user/1

image-20220123225316405

3.更新 POST _update

image-20220123230213615

如果post不带_update的话 其他会空

4. 简单的搜索

# 搜索
GET /lph/user/_search?q=name:"铁柱"

image-20220123230628511

5.复杂查询

GET 排序 分页 高亮 模糊 精准

# 字段过滤,排序,分页
GET lph/user/_search
{
"query": {		
 "match": {
   "name": "李"
 }
},
"_source": ["name","age"]		# _source 可选	指定输出的字段,不指定输出全部
, "sort": [					# sort 排序	选择字段进行排序 不进行排序有_score 排序没有 
 {
   "age": {
     "order": "desc"
   }
 }
],
"from": 0						#from size 分页参数	from 第几页 size 每页容量
,"size": 2
}
# 返回结果
#! Deprecation: [types removal] Specifying types in search requests is deprecated.
{
"took" : 0,
"timed_out" : false,
"_shards" : {
 "total" : 1,
 "successful" : 1,
 "skipped" : 0,
 "failed" : 0
},
"hits" : {						#JSON对象	 
 "total" : {
   "value" : 3,					#结果总数量
   "relation" : "eq"
 },
 "max_score" : 0.6063718,		#最大匹配度 _score:条件的匹配度
 "hits" : [
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "3",
     "_score" : 0.6063718,
     "_source" : {
       "name" : "李四",
       "age" : 29,
       "desc" : [
         "牛",
         "羊",
         "小何"
       ]
     }
   },
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "1",
     "_score" : 0.6063718,
     "_source" : {
       "name" : "李二",
       "age" : 13,
       "desc" : [
         "钱",
         "美元",
         "欧元"
       ]
     }
   },
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "4",
     "_score" : 0.6063718,
     "_source" : {
       "name" : "李四",
       "age" : 13,
       "desc" : [
         "钱",
         "美元",
         "欧元"
       ]
     }
   }
 ]
}
}


#bool 多条件and查询
GET lph/user/_search
{
"query": {
 "bool": {
   "must": [		# must 类似MySQL的and 所有条件都要匹配
     {
       "match": {
         "name": "李"				#条件1 name包含李
       }
     },
     {
       "match": {
         "age": "13"				#条件2 age=13
       }
     }
   ]
 }
}
}

#bool	多条件or查询
GET lph/user/_search
{
"query": {
 "bool": {
   "should": [				# should 类似MySQL的or条件 满足条件1 或 满足条件2 
     {
       "match": {
         "name": "李"
       }
     },
     {
       "match": {
         "age": "13"
       }
     }
   ]
 }
}
}


#bool 不等于查询
GET lph/user/_search
{
"query": {
 "bool": {
   "must_not": [				# must_not 类似MySQL的!=条件 
     {
       "match": {
         "name": "李"
       }
     } 
   ]
 }
}
}


# bool 过滤查询

GET lph/user/_search 
{
 "query": {
   "bool": {
     "must": [
       {
         "match": {
           "name": "李"
         }
       }
     ],
     "filter": {					#过滤 age>10的
       "range": {
         "age": {
           "gt": 10				# gt,lt,gte,lte...
         }
       }
     }
   }
 }
}

# 匹配多个条件
GET lph/user/_search
{
"query": {
 "match": {
   "desc": "钱 美元"			# desc是数组描述	匹配多个条件可空格分开
 }
}
}

# 返回数据
#! Deprecation: [types removal] Specifying types in search requests is deprecated.
{
"took" : 970,
"timed_out" : false,
"_shards" : {
 "total" : 1,
 "successful" : 1,
 "skipped" : 0,
 "failed" : 0
},
"hits" : {
 "total" : {
   "value" : 3,
   "relation" : "eq"
 },
 "max_score" : 1.510969,
 "hits" : [
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "2",
     "_score" : 1.510969,
     "_source" : {
       "name" : "陈六",
       "age" : 13,
       "desc" : [
         "钱",
         "美元",
         "欧元"
       ]
     }
   },
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "1",
     "_score" : 1.510969,
     "_source" : {
       "name" : "李二",
       "age" : 13,
       "desc" : [
         "钱",
         "美元",
         "欧元"
       ]
     }
   },
   {
     "_index" : "lph",
     "_type" : "user",
     "_id" : "4",
     "_score" : 0.6983144,
     "_source" : {
       "name" : "李四",
       "age" : 13,
       "desc" : [
         "钱",
         "珐琅",
         "欧元"
       ]
     }
   }
 ]
}
}


# 精确查询	(准确的匹配分词器解析的分词)
term查询是直接通过倒排索引指定的词条进行查找
此处需要解释下分词:
	trem:直接查找精确的
	match:会使用分词器解析,先分析文档 在通过分析的文档进行查找

	需要提到的两个类型:
		text:会被分词器解析
		keyword:不会被分词器解析
# 测试

image-20220209221616161

#创建规则
PUT participledb
{
"mappings": {
 "properties": {
   "name":{
     "type": "text"
   },
   "desc":{
     "type": "keyword"
   }
 }
}
}

# 添加数据
PUT participledb/_doc/1
{
"name":"SOD密",		 # text类型
"desc":"SOD密很好"		# keyword类型
}
PUT participledb/_doc/2
{
"name":"SOD密",
"desc":"SOD密不好"
}

#查询测试
GET participledb/_search
{
"query": {
 "term": {
   "name":  "密"			#“sod” or “密”	都可以直接查找出来
 }
}
}

GET participledb/_search
{
"query": {
 "term": {
   "desc":  "SOD密不好"		#查询desc 由于desc字段是keyword类型 不会被分词器解析 所以只能查询到desc=SOD密不好的数据
 }
}
}


#使用trem 进行多条件查询
GET participledb/_search
{
"query": {
 "bool": {
   "should": [
     {
       "term": {
         "name": {
           "value": "sod"
         }
       }
     },
     {
       "term": {
         "desc": {
           "value": "SOD密不好"
         }
       }
     }
   ]
 }
}
}

# 高亮查询

image-20220209224018382

image-20220209224350798

集成springboot

创建空java项目,配置springboot模块后匹配版本号!
集成springboot依赖

1.配置类

package org.ph.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <p>
 *
 * </p>
 *
 * @author lph
 * @package org.ph.config
 * @project elastic-api
 * @since 2022 -02 -13 14:47
 */
@Configuration
public class ElasticSearchClientConfig {
    
    //restHighLevelClient 使用自动注入 默认方法名
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")
                    //集群new多个
                )
        );
        return restHighLevelClient;
    }
}

2.测试APi

package org.ph;

import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class EsApiApplicationTests {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Test
    void contextLoads() {
    }


    @Test
    void createIndex() {
        try {
            //创建索引请求 索引小写
            CreateIndexRequest createIndexRequest = new CreateIndexRequest("es_create_index");
            //执行索引请求
            CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            System.out.println(createIndexResponse);

        } catch (Exception e) {

        } finally {
            try {
                restHighLevelClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
     @Test
    void ExistIndex() {
        try {
            //索引是否存在
            GetIndexRequest es_create_index = new GetIndexRequest("es_create_index");
            boolean exists = restHighLevelClient.indices().exists(es_create_index, RequestOptions.DEFAULT);
            System.out.println(exists);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                restHighLevelClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
         @Test
    void DeleteIndex() {
        try {
            //删除索引对象 可先检查是否存在索引
            DeleteIndexRequest es_create_index = new DeleteIndexRequest("es_create_index");
            // 执行删除
            AcknowledgedResponse delete = restHighLevelClient.indices().delete(es_create_index, RequestOptions.DEFAULT);
            if (delete.isAcknowledged())
                System.out.println("删除成功");
            else
                System.out.println("删除失败");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                restHighLevelClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    } 
        
        
        @Test
    void AddDocument() {
        User tiezhu = new User("铁柱", 32);
        User cuihua = new User("翠花", 12);
        User aiguo = new User("爱国", 52);

        //创建请求
        IndexRequest indexRequest = new IndexRequest("es_create_index");

        //创建规则		超时时间。。
        indexRequest.id("1").timeout(TimeValue.timeValueSeconds(1)).timeout("1s");

        //执行请求
        indexRequest.source(JSON.toJSONString(tiezhu), XContentType.JSON);

        try {
            IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            System.out.println(index.toString());
            System.out.println(index.status());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                restHighLevelClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Test
    void documentIfExists() throws IOException {
        GetRequest es_create_index = new GetRequest("es_create_index", "1");
        //过滤_source的上下文
        es_create_index.fetchSourceContext(new FetchSourceContext(false));

        boolean exists = restHighLevelClient.exists(es_create_index, RequestOptions.DEFAULT);
        System.out.println("exists = " + exists);
    }

    @Test
    void getDocument() throws IOException {
        GetRequest es_create_index = new GetRequest("es_create_index", "1");

        boolean exists = restHighLevelClient.exists(es_create_index, RequestOptions.DEFAULT);
        if (exists) {
            GetResponse documentFields = restHighLevelClient.get(es_create_index, RequestOptions.DEFAULT);
            //documentFields 可以获取很多信息 类型 版本
         	System.out.println("user对象信息" + documentFields.getSourceAsString());
            System.out.println("所有信息" + documentFields);
        } else
            System.out.println("文档不存在");
    }
  @Test
    void updateDocument() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("es_create_index", "1");

        //更新的数据
        User cuihua = new User("翠花", 12);

        updateRequest.doc(JSON.toJSONString(cuihua), XContentType.JSON);

        UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println(update.status());

    }

    @Test
    void deleteDocument() throws IOException {
//        删除文档
        DeleteRequest updateRequest = new DeleteRequest("es_create_index", "01");
        DeleteResponse delete = restHighLevelClient.delete(updateRequest, RequestOptions.DEFAULT);
        System.out.println(delete.status());

    }


    @Test
    void bulkDocument() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        List<User> users = new ArrayList<>();
        User tiezhu = new User("铁柱", 32);
        User cuihua = new User("翠花", 12);
        User aiguo = new User("爱国", 52);
        users.add(tiezhu);
        users.add(cuihua);
        users.add(aiguo);

        for (int i = 0; i < users.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("es_create_index")
                            .id("" + (i + 1))
                            .source(JSON.toJSONString(users.get(i)), XContentType.JSON)
            );
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            System.out.println(bulk.hasFailures());

        }
    }


    @Test
    void search() throws IOException {
        SearchRequest searchRequest = new SearchRequest("es_create_index");
        //条件构造
        SearchSourceBuilder builderQuery = new SearchSourceBuilder();


//        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("userName", "翠");
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        builderQuery.query(matchAllQueryBuilder);

        //分页
//        builderQuery.from();
//        builderQuery.size();

        //超时时间
//        builderQuery.timeout(new TimeValue(60, TimeUnit.SECONDS));


        //将构造器放请求
        searchRequest.source(builderQuery);
        //执行请求
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        System.out.println(JSON.toJSONString(search.getHits().getHits()));

    }
    
}

简单解释一下 text 、keyword、term、match
text和keyword是数据类型,表示在创建的时候是否会分词建立索引。
term和match是指查询时是否启用分词查询。

例如:现在有一个数据 “my cat”是text类型。
那么,使用term查询“my cat”时,失败,因为“my cat”在被创建时分词器将其索引建立为“my”和“cat”。
使用term查询“my”或“cat”时,则会成功,因为索引又能够精确匹配的数据。
使用match查询“my cat”也能成功,因为match是模糊查询,查询语句“my cat”会被分词成为“my”和“cat”和“my cat”,只要有任意一个满足就会返回数据。

如果“my cat”是keyword,那么建立时,只会有一个索引“my cat”
所以此时,不管是term还是match都只有查询“my cat”时才会返回数据,其余的都查询不到。