Elasticsearch 是一个基于 Apache Lucene 搜索引擎库构建的、可扩展的分布式搜索与分析引擎。它具备近实时的全文搜索、分析结构化数据以及处理大规模数据的能力。对于刚接触 Elasticsearch 的开发者而言,理解其基本概念和操作是入门的第一步。如果想了解更多系统性的后端架构知识,欢迎访问 云栈社区 的相关板块。
为了方便理解,我们可以将其核心概念与关系型数据库做一个类比:
| 数据库 |
Elasticsearch |
| 数据库 (Database) |
索引 (Index) |
| 表 (Table) |
索引 (Index) (注:7.0后类比更接近表) |
| 行 (Row) |
文档 (Document) |
| 列 (Column) |
字段 (Field) |
| 表结构定义 (Schema) |
映射 (Mapping) |
数据类型
如上表所示,一个索引包含多个字段,每个字段需要定义其数据类型。Elasticsearch 提供了丰富的数据类型,以下是几种常用的核心类型:
Keyword
用于存储字符类型数据,不会被分词器处理,在存储上做了优化。它非常适合用于精确值匹配、排序、聚合(分组)以及 term 查询。keyword 类型下还有几个子类型:
- keyword: 最常用的类型,适用于身份证号、状态码、标签等具有标识性的数据。(数字类型适合范围查询,而
keyword 类型则对 term 精确匹配查询效率更高,实际使用时需要根据场景选择)。
- constant_keyword: 适用于索引中所有文档在该字段的值都相同的场景。设计目的是为了节约存储空间,因为该值只存储一份。
- wildcard:
keyword 的另一种优化实现,通常在字段值内容区分度高,且计划使用 wildcard(通配符)或 regexp(正则表达式)查询时使用。
Text
用于存储需要被全文检索的文本数据。在保存时,文本会被指定的分析器(analyzer)分词,然后对分词结果建立倒排索引。
- text: 标准的文本类型,适用于文章内容、商品描述等非结构化文本。
- match_only_text: 对
text 类型的优化版本,它不记录词项的位置和频率信息,仅记录词项是否存在。适用于日志内容或仅需判断关键词是否存在的场景,能显著节省磁盘空间。
数字类型
Elasticsearch 支持多种数值类型:long, integer, short, byte, double, float, half_float, scaled_float, unsigned_long。选择原则是:在满足业务需求的前提下,尽量选择范围最小的类型,这有助于提升存储和查询效率。
特别说明一下 scaled_float:它通过一个 scaling_factor(缩放因子)将浮点数转换为整数进行存储和计算,效率比直接存浮点数高。例如,对于数值 2.34,设置 scaling_factor 为 100,实际存储为 234。缩放因子必须设置得足够大,以避免数据精度丢失。
二进制 (Binary)
用于存储 Base64 编码的二进制数据。注意,编码后的字符串不应包含换行符 \n。
布尔 (Boolean)
布尔类型。值 true 或 "true" 会被解释为 true;值 false、"false" 或空字符串 "" 会被解释为 false。
日期 (Date)
Elasticsearch 提供了 date 和 date_nanos 两种日期类型。写入时,可以传入格式化的日期字符串或时间戳数字,ES 会将其统一转换为对应的内部表示(date 为毫秒,date_nanos 为纳秒)。查询时,内部表示会再转换回指定的格式。格式可以通过字段映射中的 format 属性来定义。
对象类型 (Object Types)
用于处理包含嵌套属性的复杂结构。主要分为以下几类:
- Object: 标准的对象类型。写入后,对象的每个子字段会被单独索引和存储。例如以下映射:
"manager": {
"properties": {
"age": { "type": "integer" },
"name": {
"properties": {
"first": { "type": "text" },
"last": { "type": "text" }
}
}
}
}
实际上会创建三个可被独立查询的字段:manager.age, manager.name.first, manager.name.last。
- Flattened: 将整个对象的所有子字段值“扁平化”处理,合并为一个数组,并作为
keyword 类型存储。它适合子字段不需要被独立查询,只需进行存在性检查或简单匹配的场景。例如数据 {"manager": {"sex": "man", "name": {"first": "John", "last": "Smith"}}} 会被存储为 manager: ["man", "John", "Smith"]。
- Nested: 为了解决
Object 类型在处理对象数组时丢失内部对象间关联的问题而设计。当字段值是对象数组时,如果使用 Object 类型,每个对象的属性会被拆分合并,导致对象间的边界消失。使用 Nested 类型,每个内部对象会被独立索引和存储,保持了对象内部的关联性。但查询时必须使用专门的 nested 查询,且开销相对较大。
举例说明区别:
"user" : [
{ "first" : "John", "last" : "Smith" },
{ "first" : "Alice", "last" : "White" }
]
- 如果是
Object 类型,最终存储为:user.first: ["John", "Alice"], user.last: ["Smith", "White"]。此时查询 user.first=John AND user.last=White 也会命中,这显然不符合原始数据中对象的对应关系。
- 如果是
Nested 类型,则两个对象会分别被索引,上述查询不会命中,准确反映了数据关系。
索引与映射操作
与关系型数据库类似,在存入数据前最好先定义索引的映射(Mapping)。虽然 Elasticsearch 支持动态映射(根据写入的数据自动推断类型),但这可能导致推断出的类型不符合预期,因此推荐手动明确定义。
创建映射
使用 RESTful API 创建索引并定义映射。
PUT /your_index_name
{
"mappings": {
"properties": {
"field1": { "type": "keyword" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
增加映射
为已存在的索引添加新的字段。
PUT /your_index_name/_mapping
{
"properties": {
"employee-id": {
"type": "keyword"
}
}
}
修改映射
注意:Elasticsearch 不允许直接修改已有字段的映射定义(如更改数据类型、修改分词器等)。如果需要调整,通常的做法是创建一个新索引并定义正确的映射,然后将数据从旧索引迁移(Reindex)到新索引。另一种折中方案是为字段设置别名(Alias)。
查看映射
# 查看整个索引的映射
GET /your_index_name/_mapping
# 查看索引中特定字段的映射详情
GET /your_index_name/_mapping/field/field_name
文档数据操作 (CRUD)
定义好索引映射后,即可进行文档的增删改查。
新增文档
## 仅当指定文档ID不存在时创建(如果存在则失败)
PUT your_index_name/_create/document_id
{
"field": "value",
"message": "GET /search HTTP/1.1 200 1070000",
"user": {
"id": "kimchy"
}
}
## 使用指定ID,如果ID不存在则创建,如果已存在则执行全量替换更新
PUT your_index_name/_doc/document_id
{
"field": "value",
"message": "GET /search HTTP/1.1 200 1070000",
"user": {
"id": "kimchy"
}
}
修改文档
## 部分更新:只更新请求体中给出的字段
POST /your_index_name/_update/document_id
{
"doc": {
"field": "new_value",
"age": 121
}
}
## 使用脚本更新(可以完成更复杂的逻辑)
POST your_index_name/_update/document_id
{
"script": {
"source": "ctx._source.age = params.count",
"lang": "painless",
"params": {
"count": 4
}
}
}
## 更新或插入(upsert):如果文档存在则更新,否则插入新文档
POST /your_index_name/_update/document_id
{
"doc": {
"product_price": 100
},
"upsert": {
"product_price": 50
}
}
删除文档
DELETE /your_index_name/_doc/document_id