一、引言
我们知道,MySQL 是一个关系型数据库管理系统(RDBMS),它遵循 SQL 标准,适合存储结构化数据。MySQL 以其高效的查询处理能力著称,能够处理高并发读写操作。此外,MySQL 提供完整的事务支持,能确保数据的一致性、隔离性和持久性。
但在面对海量数据查询时,关系型数据库的查询效率会大大下降,我们常做的是对查询字段进行索引以提高效率,但这样的方式不足以处理更复杂庞大的数据处理状况,过低的查询效率可能造成数据库崩溃,于是我们引入了今天要介绍的ES(ElasticSearch)。
二、ElasticSearch和倒排索引简介
- ES简介
Elasticsearch(ES)是一个基于 Lucene 构建的开源、分布式、RESTful 风格的搜索和分析引擎,它被设计为易于扩展、高可靠和易于使用,能够处理大规模的数据集,并提供快速的全文搜索功能。Elasticsearch 采用分布式架构,数据通过分片进行分布存储,并支持副本机制。这使得它非常适合处理大规模数据,可以在多个节点间分担查询和索引负载,从而提升查询性能。
- 倒排索引
在正排索引(Forward Index)中,索引的组织方式是文档ID到文档内容的映射。例如,我们有一个文档集合,每个文档都有一个唯一的ID,正排索引会列出每个文档ID及其包含的所有单词。
而ES所用的倒排索引则相反,它将单词作为索引的“主键”,并列出每个单词出现在哪些文档中,以及在文档中的哪些位置。这种结构使得查找包含特定单词的文档变得非常快速。
为了便于大家理解画了一个不太标准的示意图
倒排索引这样的索引方式让我们在面对模糊查询,复杂的聚合查询时,能根据字段内容快速索引到id最终定位到记录(在ES中叫文档,下文不在解释)。
三、Mapping属性与索引库创建
开始之前需要大家自行安装ES,ik分词和可视化工具如kibana。
1.基本的Mapping属性
type
:指定字段的数据类型,如text
、keyword
、integer
、object等。
index
:指定字段是否被索引。true
表示索引,false
表示不索引。
store
:指定字段是否在_source
中存储。true
表示存储,false
表示不存储。
analyzer
:指定字段在索引时使用的分析器。分析器用于将文本字段分解为标记(tokens)。
search_analyzer
:指定字段在搜索时使用的分析器。
normalizer
:指定字段在规范化(normalization)时使用的规范化器。
include_in_all
:指定字段是否包含在_all
字段中。_all
字段是一个特殊的字段,它包含了文档中所有字段的内容。
fielddata
:控制字段数据(fielddata)的行为,字段数据用于排序、聚合等操作。
fields
:允许你在一个字段下定义多个子字段,这些子字段可以有不同的类型和索引设置。
ignore_above
:对于text
类型的字段,指定一个阈值,超过这个阈值的文本将不会被索引。
term_vector
:指定是否存储术语向量(term vectors),这对于某些高级搜索功能(如更多类似)是必要的。
boost
:指定字段的权重,影响搜索结果的相关性评分。
null_value
:指定字段在值为null
时的默认值。
copy_to
:允许你将一个字段的值复制到另一个字段,这在创建_all
字段时很有用。
properties
:用于定义嵌套对象或多字段的属性。
2.创建索引库
-
简单索引库编写
单看概念还是比较抽象,所以这里给出了创建简单索引库的基础语句大家结合属性理解:
注意右侧注释应在运行时删除,此处用作解释
#新建索引库csdn
PUT /csdn #RESTful风格
{
"mappings": { #mapping定义
"properties": { #定义mappings的多字段属性
"title":{ #自定义的一个名为标题的属性
"type": "text", #字段的数据类型,text会被分词用作全文搜索
"analyzer": "ik_smart" #分词器采用ik_smart
#index默认为tru,即字段会被索引表示该字段参与搜索
},
"info":{ #自定义一个名为信息的字段
"type": "text",
"analyzer": "ik_smart"
},
"email":{ #自定义一个名为邮箱的字段
"type": "keyword", #数据类型keyword即不分词,用于精确匹配
"index": false #不索引,即不参与搜索
},
"author":{ #自定义一个名为作者的字段
"type": "object", #数据类型object,可以为对象定义子字段
"properties": {
"nakename":{ #自定义一个名为昵称的字段
"type": "text",
"analyzer": "ik_smart"
},
"article":{
"type": "text",
"analyzer": "ik_smart"
},
"age":{
"type":"integer",
"index": false
}
}
}
}
}
}
-
深入表结构对比
经过上面的解释相信大家已经能理解绝大部分属性了,但是有几个概念可能还是比较模糊,比如:是否分词,是否索引,是否参与搜索这三者有什么关系,下面我带大家结合Mysql的表结构以及现实场景再深入理解下。
以下为表结构DDL
create table tb_hotel
(
id bigint not null comment '酒店id'
primary key,
name varchar(255) not null comment '酒店名称',
address varchar(255) not null comment '酒店地址',
price int not null comment '酒店价格',
score int not null comment '酒店评分',
brand varchar(32) not null comment '酒店品牌',
city varchar(32) not null comment '所在城市',
star_name varchar(16) null comment '酒店星级,1星到5星,1钻到5钻',
business varchar(255) null comment '商圈',
latitude varchar(32) not null comment '纬度',
longitude varchar(32) not null comment '经度',
pic varchar(255) null comment '酒店图片'
)
collate = utf8mb4_general_ci
row_format = COMPACT;
我们简单分析一下,什么样的文本需要分词呢,如酒店名称“郑州嘉锦酒店(万达酒店及度假村)”,只想通过嘉锦、万达这样的关键词找到酒店,那我们就需要将长文本分词索引;什么样的字段需要索引呢,索引是为了便于查找,那么用户能够或者想要搜索的字段就需要索引,清楚了这个概念相信大家已经能处理表中大部分内容了。
#酒店的mapping
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
比较特殊的两处是,geo_point、copy_to属性和all字段,对于表结构的经纬度坐标,在ES中存在
地理点geo_point与之对应。而copy_to属性则是将多个参与搜索的字段一起合并创建倒排索引库all(自定义名),举个例子用户在搜索时选定了商圈、品牌两标签,又指定了酒店名称的部分关键词,他们刚好都被拷贝到all中,这时候就不需要从单个字段的倒排索引库一一查询,而是直接在all的倒排索引库直接查询,大大提高了检索效率。
四、简单查询语句
1.查询模板
- 查询所有文档(替换
<index>
为你的索引名称)
GET /<index>/_search
{
"query": {
"match_all": {}
}
}
-
match
查询(全文搜索,替换<index>
为你的索引名称,<field>
为你想要搜索的字段名称,<text>
为搜索文本)
GET /<index>/_search
{
"query": {
"match": {
"<field>": "<text>"
}
}
}
-
multi_match
查询(多字段全文搜索,替换<index>
为你的索引名称,<text>
为搜索文本,<field1>
,<field2>
,<field3>
为想要搜索的字段名称列表)
GET /<index>/_search
{
"query": {
"multi_match": {
"query": "<text>",
"fields": ["<field1>", "<field2>", "<field3>"]
}
}
}
-
term
精确查询(替换<index>
为你的索引名称,<field>
为你想要精确匹配的字段名称,<exact_value>
为精确值)
GET /<index>/_search
{
"query": {
"term": {
"<field>": {
"value": "<exact_value>"
}
}
}
}
range
范围查询 (替换<index>
为你的索引名称,<field>
为你想要进行范围查询的字段名称,<min_value>
和<max_value>
为范围的最小值和最大值)
GET /<index>/_search
{
"query": {
"range": {
"<field>": {
"gte": "<min_value>",
"lte": "<max_value>"
}
}
}
}
geo_distance
地理距离查询 (替换<index>
为你的索引名称,<distance>
为距离值,<location_field>
为包含地理位置信息的字段名称,<latitude>
和<longitude>
为地理位置的纬度和经度)
GET /<index>/_search
{
"query": {
"geo_distance": {
"distance": "<distance>",
"<location_field>": {
"lat": <latitude>,
"lon": <longitude>
}
}
}
}
geo_bounding_box
地理区块查询 (替换<index>
为你的索引名称,<location_field>
为包含地理位置信息的字段名称,<top_left_latitude>
,<top_left_longitude>
,<bottom_right_latitude>
,<bottom_right_longitude>
为地理区块的左上角和右下角的纬度和经度)
GET /<index>/_search
{
"query": {
"geo_bounding_box": {
"<location_field>": {
"top_left": {
"lat": <top_left_latitude>,
"lon": <top_left_longitude>
},
"bottom_right": {
"lat": <bottom_right_latitude>,
"lon": <bottom_right_longitude>
}
}
}
}
}
2.查询实例
#查询所有
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
#match查询
GET /hotel/_search
{
"query": {
"match":{
"all": "如家外滩"
}
}
}
#multi_match查询
GET /hotel/_search
{
"query": {
"multi_match":{
"query": "如家外滩",
"fields": ["brand","business","name"]
}
}
}
#term精确查询
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
#range精确查询
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 300
}
}
}
}
#distance查询
GET /hotel/_search
{
"query": {
"geo_distance":{
"distance":"5km",
"location":"31.21,121.5"
}
}
}
#box区块查询
GET /hotel/_search
{
"query": {
"geo_bounding_box":{
"location":{
"top_left":{
"lat":31.1,
"lon":121.5
},
"bottom_right":{
"lat":30.9,
"lon":121.7
}
}
}
}
}
那么本次分享到这里就结束了,后续我会结合具体业务场景来为大家展示ES的一些复杂查询和在开发中的应用,谢谢观看。