4、索引映射基础:Mapping概念、字段类型与映射策略
索引映射(Mapping)是 Elasticsearch 中定义文档结构及其字段类型的过程,类似于关系型数据库中的表结构定义(Schema)。在构建搜索服务时,映射的正确性直接决定了数据的存储效率、查询性能以及搜索结果的准确性。本章将系统讲解 Mapping 的核心概念、字段类型体系以及动态映射与显式映射的适用场景。
4.1 Mapping 核心概念
Mapping 定义了索引中每个字段的名称、数据类型、索引方式以及分析器配置。Elasticsearch 在写入文档时,会根据 Mapping 对字段进行分词、索引和存储。理解 Mapping 的关键在于区分以下三个层次:
| 层次 | 说明 | 类比关系型数据库 |
|---|---|---|
| 索引 (Index) | 存储文档的逻辑容器,包含 Mapping 和 Settings | 数据库 (Database) |
| 映射 (Mapping) | 定义字段名称、类型、分析器、索引选项 | 表结构 (Schema) |
| 字段 (Field) | 文档中的具体属性,每个字段有独立类型 | 列 (Column) |
@Mapping() 装饰器或 Elasticsearch 客户端显式定义 Mapping,避免依赖动态映射带来的类型推断风险。
4.2 核心字段类型详解
Elasticsearch 提供了丰富的字段类型,按用途可分为核心类型、复杂类型和专用类型。以下是搜索服务中最常用的字段类型:
| 字段类型 | 适用场景 | 索引行为 | 典型示例 |
|---|---|---|---|
| text | 全文搜索(文章标题、内容、描述) | 分词后建立倒排索引 | “Elasticsearch 入门指南” |
| keyword | 精确匹配、排序、聚合(标签、状态、ID) | 不分词,整体索引 | “published”、“user_123” |
| integer / long | 数值范围查询、排序、聚合(年龄、价格、计数) | 数值索引,支持范围查询 | 25、1999、100000 |
| float / double | 浮点数计算(评分、金额、坐标) | 浮点数值索引 | 4.5、99.99 |
| boolean | 布尔状态(是否上架、是否删除) | true / false 索引 | true、false |
| date | 时间范围查询、排序(创建时间、更新时间) | 转换为时间戳索引 | “2024-01-15T10:30:00Z” |
| object | 嵌套 JSON 对象(用户信息、地址) | 扁平化存储 | { “name”: “张三”, “age”: 30 } |
| nested | 独立查询的嵌套数组(订单项、评论列表) | 独立文档存储,支持独立查询 | [ { “product”: “A”, “qty”: 2 } ] |
multi-fields 将其同时映射为 text 和 keyword 类型。
4.3 text 与 keyword 的深度对比
text 和 keyword 是搜索服务中最容易混淆的一对类型。它们的核心区别在于是否经过分析器(Analyzer)处理:
| 对比维度 | text | keyword |
|---|---|---|
| 分词 | 是,经过分析器分词 | 否,作为整体索引 |
| 索引结构 | 倒排索引(词条 → 文档) | 文档值(Doc Values)+ 倒排索引 |
| 查询方式 | match、match_phrase、query_string | term、terms、prefix、wildcard |
| 聚合 | 不支持(需开启 fielddata,不推荐) | 支持 terms、cardinality 等聚合 |
| 排序 | 不支持 | 支持 |
| 典型长度 | 长文本(几十到几千字符) | 短文本(通常不超过 256 字符) |
在实际项目中,经常需要对同一个字段同时支持全文搜索和精确聚合。Elasticsearch 提供了 multi-fields 机制:
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
上述映射中,title 字段默认按 text 类型进行全文搜索,而 title.keyword 子字段则支持精确匹配和聚合操作。
4.4 动态映射与显式映射
Elasticsearch 提供了两种映射定义方式:动态映射(Dynamic Mapping)和显式映射(Explicit Mapping)。理解它们的差异对于构建稳定的搜索服务至关重要。
4.4.1 动态映射
动态映射允许 Elasticsearch 在索引文档时自动检测并添加新字段。其推断规则如下:
| JSON 数据类型 | 推断的 ES 字段类型 | 示例值 |
|---|---|---|
| 字符串(可解析为日期) | date | “2024-01-15” |
| 字符串(数字格式) | float 或 long | “123.45” |
| 字符串(其他) | text + keyword 子字段 | “Hello World” |
| 整数 | long | 42 |
| 浮点数 | float | 3.14 |
| 布尔值 | boolean | true |
| 对象 | object | { “key”: “value” } |
| 数组 | 根据第一个非 null 元素推断 | [ “a”, “b” ] |
- 字符串可能被错误推断为 date 或 float,导致后续查询失败。
- 字段类型一旦创建,无法直接修改(需 reindex)。
- 动态映射会消耗额外的集群资源,影响写入性能。
4.4.2 显式映射
显式映射要求开发者在创建索引时手动定义所有字段的类型和参数。这是生产环境的推荐做法:
// NestJS 中使用 Elasticsearch 客户端创建索引并定义 Mapping
async createProductIndex() {
const index = 'products';
const exists = await this.elasticsearch.indices.exists({ index });
if (!exists) {
await this.elasticsearch.indices.create({
index,
body: {
settings: {
number_of_shards: 3,
number_of_replicas: 1
},
mappings: {
dynamic: 'strict', // 严格模式:禁止动态添加字段
properties: {
productId: { type: 'keyword' },
name: {
type: 'text',
fields: {
keyword: { type: 'keyword', ignore_above: 256 }
}
},
description: { type: 'text', analyzer: 'ik_max_word' },
price: { type: 'double' },
category: { type: 'keyword' },
tags: { type: 'keyword' },
stock: { type: 'integer' },
createdAt: { type: 'date', format: 'yyyy-MM-dd HH:mm:ss' },
isActive: { type: 'boolean' },
attributes: {
type: 'object',
properties: {
color: { type: 'keyword' },
size: { type: 'keyword' }
}
}
}
}
}
});
}
}
4.4.3 动态映射的三种模式
Elasticsearch 允许通过 dynamic 参数控制动态映射的行为:
| 模式 | 行为 | 适用场景 |
|---|---|---|
| true(默认) | 自动检测并添加新字段 | 开发环境、日志索引 |
| false | 忽略新字段,不索引但可写入 | 需要写入但不需要搜索的字段 |
| strict | 遇到新字段时抛出异常 | 生产环境、核心业务索引 |
dynamic: 'strict' 显式映射;对日志或事件类索引可使用 dynamic: true 配合索引模板控制字段数量。
4.5 映射的更新与限制
Mapping 一旦创建,某些属性就无法修改。理解这些限制有助于在设计阶段做出正确决策:
| 操作 | 是否允许 | 说明 |
|---|---|---|
| 添加新字段 | ✅ 允许 | 可随时添加,不影响已有数据 |
| 修改字段类型 | ❌ 不允许 | 需创建新索引并 reindex |
| 修改分析器 | ❌ 不允许 | 需创建新索引并 reindex |
| 修改 ignore_above | ❌ 不允许 | 需创建新索引并 reindex |
| 修改 dynamic 模式 | ✅ 允许 | 可通过 PUT mapping API 更新 |
| 添加 multi-fields | ✅ 允许 | 可通过 PUT mapping API 添加子字段 |
@nestjs/elasticsearch 的 reindex 方法实现。
4.6 本章小结
- Mapping 是 Elasticsearch 索引的核心结构定义,决定了字段的存储和查询行为。
- text 用于全文搜索,keyword 用于精确匹配和聚合,两者可通过 multi-fields 共存。
- 动态映射 适合开发阶段和日志场景,但存在类型推断风险。
- 显式映射 是生产环境的标准做法,配合
dynamic: 'strict'可避免字段类型混乱。 - 映射一旦创建,字段类型不可修改,变更需通过 reindex 实现。
- 在 NestJS 中,建议在模块初始化时通过 Elasticsearch 客户端统一创建索引和 Mapping。