2. Elasticsearch数据模型基础

Elasticsearch 不是关系型数据库,它的数据模型从根上就与“表—行—列”不同。理解这一点,是后续做映射设计、字段分析和查询优化的前提。本节围绕五个相互关联的概念展开:文档导向模型、JSON 文档结构、索引与类型的关系、映射定义,以及动态映射与显式映射的取舍。

文档导向模型:以整份文档为读写单位

在 Elasticsearch 中,最小可存储、可检索、可更新的单元是文档(document)。一份文档通常对应现实中的一个实体,例如一个商品、一条日志、一位用户。文档是自包含的:它把该实体的所有字段放在一起,而不是像关系模型那样把属性拆散到多张表再靠 JOIN 拼回。

这种“文档导向”带来两个直接后果。第一,读写以整份文档为单位:新增是索引一份文档,更新在底层也是“标记旧文档删除 + 写入新版本”,而不是原地改某个字段。第二,检索时不需要跨表连接,一次查询就能拿到完整对象,因此更适合搜索、日志、内容检索这类“按对象取回”的场景。代价是跨实体的强事务与频繁的关联更新不擅长,需要在建模阶段就决定把哪些数据冗余进同一份文档。

JSON 文档结构:字段、类型与嵌套

文档以 JSON 表示,由一组字段名与字段值构成。字段值可以是字符串、数值、布尔、日期、数组或对象。需要特别注意的是:JSON 本身没有类型声明,类型是由映射赋予的。同一个值 "2024-01-01" 可以被当作字符串,也可以被当作日期,取决于映射怎么定义。

数组在 Elasticsearch 中不需要单独声明:任何字段都可以接受多个值,底层按“多值字段”处理。对象则会被“扁平化”成点号路径,例如:

{
  "order_id": "A1001",
  "customer": { "name": "李雷", "city": "杭州" },
  "amount": 268.00,
  "tags": ["数码", "加急"]
}

其中 customer.name 与 customer.city 会成为可独立查询的字段。这里有一个常见陷阱:对象数组(object array)在扁平化后会丢失“哪个值属于哪个子对象”的对应关系。例如两个 customer 对象各自带 name 和 city,扁平化后无法判断“李雷”对应的是“杭州”还是“上海”。若这种对应关系重要,应使用 nested 类型,把每个子对象作为独立隐藏文档保存。

索引与类型的关系:从多类型到单类型

索引(index)是文档的逻辑集合,也是分片、副本、映射和查询的基本单位。早期版本允许一个索引下放多个 类型(type),类似一张表。但从 6.x 起单索引只允许一个类型,7.x 起类型概念被彻底移除,8.x 中已不再使用。因此今天的正确心智模型是:一个索引 ≈ 一类文档的集合,类型不再承担建模职责。

如果确实需要区分不同种类的数据,应使用多个索引,例如 orders-2024、logs-2024.06,再通过索引别名或数据流(data stream)统一对外查询。这样既符合单类型约束,也便于按时间滚动和生命周期管理。

映射定义:字段的类型与行为契约

映射(mapping)定义索引中每个字段的类型与处理方式,是文档能否被正确检索的关键。它主要包含:字段类型(text、keyword、long、double、date、boolean、nested 等)、是否索引、是否可被聚合、分词器、日期格式等。

其中最容易混淆的是 text 与 keyword:text 会分词,适合全文检索;keyword 不分词,适合精确匹配、排序和聚合。一个字段可以同时具备两者,用 fields 定义多字段:

PUT /products
{
  "mappings": {
    "properties": {
      "title":   { "type": "text", "fields": { "raw": { "type": "keyword" } } },
      "price":   { "type": "double" },
      "created": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" },
      "brand":   { "type": "keyword" }
    }
  }
}

这样 title 用于全文搜索,title.raw 用于精确排序或聚合。映射一旦建立,字段类型基本不可修改,只能新增字段,因此映射设计应在写入数据前完成。

动态映射与显式映射的取舍

动态映射指写入文档时,Elasticsearch 自动为新字段推断类型。例如首次遇到 "price": 268.00 会推断为 float,遇到 "2024-01-01" 会推断为 date。它上手快,适合探索性写入和日志场景。但风险也很明显:推断可能不符合预期(如把编号推断成数值导致前导零丢失)、字段爆炸(大量随机字段撑爆映射)、以及后续无法改类型。

显式映射指在创建索引时手动声明字段类型与行为。它可控、可预测,适合生产环境和有明确查询需求的业务数据。实践中常采用折中策略:对核心业务字段显式定义,对不确定的辅助字段允许动态映射,同时用 dynamic 参数控制行为——true 允许自动添加,false 忽略新字段但仍可查询,strict 遇到未知字段直接报错,适合强约束场景。

判断依据:如果字段会被用于排序、聚合或精确匹配,几乎都应显式定义为 keyword 或数值类型;如果只是全文检索,可交给 text 并配置合适分词器。
文档导向模型:从 JSON 文档到索引与映射 JSON 文档(自包含) { "order_id": "A1001", "customer": { "name": "李雷", "city": "杭州" }, "amount": 268.00, "tags": ["数码","加急"] } 写入 索引 index orders-2024 单类型(type 已移除) 分片 / 副本 / 别名 数据流 data stream 一类文档 ≈ 一个索引 约束 映射 mapping title: text + .raw keyword price: double created: date brand: keyword customer: object / nested 类型一旦建立基本不可改 动态映射 dynamic mapping • 写入时自动推断字段类型 • 上手快,适合探索与日志 • 风险:推断偏差、字段爆炸 • dynamic: true / false / strict 示例:268.00 → float,编号可能丢前导零 显式映射 explicit mapping • 建索引时手动声明类型与行为 • 可控、可预测,适合生产环境 • 排序/聚合字段优先 keyword • 全文检索字段用 text + 分词器 折中:核心字段显式,辅助字段动态 取舍

把上述关系串起来看:一份 JSON 文档写入某个索引,索引通过映射决定每个字段如何被解析和检索;映射可以自动生成,也可以显式声明,而生产环境通常选择“核心显式、边缘动态”的折中。理解这条链路,后续的字段类型选择、分词器配置和查询优化才有稳定的落脚点。