4、索引映射基础:Mapping概念、字段类型与映射策略

索引映射(Mapping)是 Elasticsearch 中定义文档结构及其字段类型的过程,类似于关系型数据库中的表结构定义(Schema)。在构建搜索服务时,映射的正确性直接决定了数据的存储效率、查询性能以及搜索结果的准确性。本章将系统讲解 Mapping 的核心概念、字段类型体系以及动态映射与显式映射的适用场景。

核心要点:Mapping 决定了字段如何被索引、存储和检索。错误的映射可能导致查询结果不准确或性能下降,因此在生产环境中应优先使用显式映射。
Mapping 核心概念与字段类型体系 索引映射 (Mapping) 字段类型体系 text / keyword integer / long float / double boolean date object / nested geo_point / geo_shape ip / version array / range 动态映射 自动推断字段类型 显式映射 手动定义字段类型与参数 生产环境推荐:显式映射 + 动态映射严格模式(dynamic: strict)

4.1 Mapping 核心概念

Mapping 定义了索引中每个字段的名称、数据类型、索引方式以及分析器配置。Elasticsearch 在写入文档时,会根据 Mapping 对字段进行分词、索引和存储。理解 Mapping 的关键在于区分以下三个层次:

层次 说明 类比关系型数据库
索引 (Index) 存储文档的逻辑容器,包含 Mapping 和 Settings 数据库 (Database)
映射 (Mapping) 定义字段名称、类型、分析器、索引选项 表结构 (Schema)
字段 (Field) 文档中的具体属性,每个字段有独立类型 列 (Column)
最佳实践:在 NestJS 中,建议在创建索引时通过 @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 } ]
常见陷阱:text 类型字段默认不支持聚合和排序。如果需要对某个字段同时进行全文搜索和精确聚合,应使用 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)。理解它们的差异对于构建稳定的搜索服务至关重要。

动态映射 vs 显式映射 对比流程 动态映射流程 1. 写入文档(包含新字段) 2. ES 自动推断字段类型 3. 自动添加字段到 Mapping 风险:类型推断可能错误 显式映射流程 1. 创建索引时定义 Mapping 2. 手动指定字段类型与参数 3. 写入文档时严格校验类型 优势:类型可控,性能稳定

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 遇到新字段时抛出异常 生产环境、核心业务索引
推荐策略:在 NestJS 搜索服务中,对核心业务索引(如商品、订单、用户)使用 dynamic: 'strict' 显式映射;对日志或事件类索引可使用 dynamic: true 配合索引模板控制字段数量。

4.5 映射的更新与限制

Mapping 一旦创建,某些属性就无法修改。理解这些限制有助于在设计阶段做出正确决策:

操作 是否允许 说明
添加新字段 ✅ 允许 可随时添加,不影响已有数据
修改字段类型 ❌ 不允许 需创建新索引并 reindex
修改分析器 ❌ 不允许 需创建新索引并 reindex
修改 ignore_above ❌ 不允许 需创建新索引并 reindex
修改 dynamic 模式 ✅ 允许 可通过 PUT mapping API 更新
添加 multi-fields ✅ 允许 可通过 PUT mapping API 添加子字段
映射变更策略:当需要修改字段类型时,推荐使用 Elasticsearch 的 Reindex API 创建新索引,迁移数据后切换别名(Alias),实现零停机迁移。NestJS 中可通过 @nestjs/elasticsearch 的 reindex 方法实现。

4.6 本章小结

  • Mapping 是 Elasticsearch 索引的核心结构定义,决定了字段的存储和查询行为。
  • text 用于全文搜索,keyword 用于精确匹配和聚合,两者可通过 multi-fields 共存。
  • 动态映射 适合开发阶段和日志场景,但存在类型推断风险。
  • 显式映射 是生产环境的标准做法,配合 dynamic: 'strict' 可避免字段类型混乱。
  • 映射一旦创建,字段类型不可修改,变更需通过 reindex 实现。
  • 在 NestJS 中,建议在模块初始化时通过 Elasticsearch 客户端统一创建索引和 Mapping。