Skip to content
鼓励作者:欢迎打赏犒劳

ES常见的坑

Elasticsearch 索引与查询避坑指南

一、keyword 字段的"数组陷阱"

⚠️ 坑点:keyword 类型字段可以同时接受字符串和字符串数组

json
// 这3种写法都合法,但查询方式完全不同!

// 写法1:单字符串
{ "images": "https://img.example.com/p10001_1.jpg" }

// 写法2:字符串数组
{ "images": ["https://img.example.com/p10001_1.jpg", "https://img.example.com/p10001_2.jpg"] }

// 写法3:空数组
{ "images": [] }

🔴 常见错误示例

错误1:误以为数组字段必须用 terms 查询

json
// ❌ 错误:对单值字段用 terms 查询会报错
{
  "query": {
    "terms": {
      "images": ["https://img.example.com/p10001_1.jpg"]
    }
  }
}

// ✅ 正确:统一使用 term 查询,ES会自动处理
{
  "query": {
    "term": {
      "images": "https://img.example.com/p10001_1.jpg"
    }
  }
}

错误2:用 match 查询 keyword 字段

json
// ❌ 错误:keyword 类型不支持分词,match 查询不会报错但结果不对
{
  "query": {
    "match": {
      "images": "example.com"  // 匹配不到任何结果
    }
  }
}

// ✅ 正确:keyword 必须用 term/terms
{
  "query": {
    "term": {
      "images": "https://img.example.com/p10001_1.jpg"
    }
  }
}

二、text 与 keyword 的"孪生陷阱"

⚠️ 坑点:product_name 同时拥有 text 和 keyword 两种查询方式

json
// Mapping 定义
"product_name": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword",
      "ignore_above": 256
    }
  }
}

🔴 常见错误

json
// ❌ 错误:想精确匹配 "iPhone 15",但用了 text 字段,会被分词
{
  "query": {
    "term": {
      "product_name": "iPhone 15"  // 用 term 查 text 字段,查不到!
    }
  }
}

// ❌ 错误:想模糊搜索 "iPhone",但用了 keyword 字段
{
  "query": {
    "term": {
      "product_name.keyword": "iPhone"  // 精确匹配才匹配,搜不到 "iPhone 15"
    }
  }
}

// ✅ 正确:全文搜索用 text + match
{
  "query": {
    "match": {
      "product_name": "iPhone 15 Pro"  // 分词匹配
    }
  }
}

// ✅ 正确:精确匹配用 keyword + term
{
  "query": {
    "term": {
      "product_name.keyword": "Apple iPhone 15 Pro Max"  // 完全匹配
    }
  }
}

三、nested 类型查询的"深坑"

⚠️ 坑点:nested 必须用 nested 查询,否则会"跨对象污染"

json
// Mapping 定义
"specs": {
  "type": "nested",
  "properties": {
    "spec_name": { "type": "keyword" },
    "spec_value": { "type": "keyword" }
  }
}

// 示例数据
"specs": [
  { "spec_name": "Color", "spec_value": "Black" },
  { "spec_name": "Storage", "spec_value": "256GB" }
]

🔴 常见错误

json
// ❌ 错误:不用 nested 查询,会匹配到错误的组合
{
  "query": {
    "bool": {
      "must": [
        { "term": { "specs.spec_name": "Color" } },
        { "term": { "specs.spec_value": "256GB" } }  // 这个条件可能匹配到另一个对象
      ]
    }
  }
}
// 结果:可能错误匹配到 "Color: 256GB" 的组合,因为跨对象匹配了

// ✅ 正确:必须用 nested 查询
{
  "query": {
    "nested": {
      "path": "specs",
      "query": {
        "bool": {
          "must": [
            { "term": { "specs.spec_name": "Color" } },
            { "term": { "specs.spec_value": "Black" } }
          ]
        }
      }
    }
  }
}

解决方案:使用 inner_hits 查看匹配详情

json
{
  "query": {
    "nested": {
      "path": "specs",
      "query": {
        "term": { "specs.spec_value": "Black" }
      },
      "inner_hits": {}  // 返回匹配的具体 nested 对象
    }
  }
}

四、object 类型的"扁平化陷阱"

⚠️ 坑点:object 字段会自动 flatten,可能丢失字段关系

json
// Mapping 定义
"supplier_info": {
  "type": "object",
  "properties": {
    "supplier_id": { "type": "keyword" },
    "supplier_rating": { "type": "float" }
  }
}

// 数据
"supplier_info": {
  "supplier_id": "S001",
  "supplier_rating": 4.9
}

🔴 常见误区

json
// ⚠️ 注意:虽然可以这样查询,但 nested 和 object 在这里看似相同
{
  "query": {
    "bool": {
      "must": [
        { "term": { "supplier_info.supplier_id": "S001" } },
        { "range": { "supplier_info.supplier_rating": { "gte": 4.5 } } }
      ]
    }
  }
}
// ✅ 这是可行的,因为 object 是扁平的,不涉及跨对象匹配

// 但如果 supplier_info 是数组:
"supplier_info": [
  { "supplier_id": "S001", "supplier_rating": 4.9 },
  { "supplier_id": "S002", "supplier_rating": 3.8 }
]
// ⚠️ 此时不用 nested 也会出现跨对象污染!

五、float vs double 的精度陷阱

⚠️ 坑点:float 只有 7 位有效数字,double 有 15-16 位

json
// ❌ 错误:用 float 存储价格导致精度丢失
"price": { "type": "float" }
// 存储 1199.99 → 可能变成 1199.989990234375

// ✅ 正确:价格用 double
"price": { "type": "double" }
// 存储 1199.99 → 精确值 1199.9899999999998(但足够精确)

🔴 比较陷阱

json
// ❌ 错误:用 term 精确匹配浮点数
{
  "query": {
    "term": {
      "price": 1199.99  // 永远匹配不到,因为浮点数存储有误差
    }
  }
}

// ✅ 正确:用 range 范围匹配
{
  "query": {
    "range": {
      "price": {
        "gte": 1199.98,
        "lte": 1200.00
      }
    }
  }
}

// ✅ 或者使用 script 精确比较(性能差)
{
  "query": {
    "script": {
      "script": "Math.abs(doc['price'].value - 1199.99) < 0.001"
    }
  }
}

六、date 字段的格式化陷阱

⚠️ 坑点:日期必须严格匹配定义的 format

json
// Mapping 定义
"create_time": {
  "type": "date",
  "format": "yyyy-MM-dd'T'HH:mm:ss"
}

🔴 常见错误

json
// ❌ 错误1:缺少 T 分隔符
{ "create_time": "2026-01-15 10:30:00" }  // 报错!

// ❌ 错误2:时间部分用空格
{ "create_time": "2026-01-15T10:30:00" }  // ✅ 正确

// ❌ 错误3:带毫秒
{ "create_time": "2026-01-15T10:30:00.123" }  // 报错!

// ❌ 错误4:用时间戳(数字)
{ "create_time": 1737000000000 }  // 报错,不匹配 format!

日期查询的正确写法

json
// ✅ 方式1:字符串格式
{
  "query": {
    "range": {
      "create_time": {
        "gte": "2026-01-01T00:00:00",
        "lte": "2026-12-31T23:59:59"
      }
    }
  }
}

// ✅ 方式2:日期数学表达式
{
  "query": {
    "range": {
      "create_time": {
        "gte": "now-30d/d"  // 30天前
      }
    }
  }
}

七、ignore_above 的截断陷阱

⚠️ 坑点:超过 ignore_above 的值会被忽略,不建立索引

json
// Mapping 定义
"product_name": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword",
      "ignore_above": 256
    }
  }
}

🔴 常见错误

json
// 写入数据
{ "product_name": "这是一段超过256个字符的超长商品名称,包含了各种详细信息......(超过256字符)" }

// 查询
{
  "query": {
    "term": {
      "product_name.keyword": "超长商品名称..."  // 查不到!因为 keyword 字段被截断忽略了
    }
  }
}

// ✅ 解决方案:增大 ignore_above 或使用 text 字段搜索
{
  "query": {
    "match": {
      "product_name": "超长商品名称"  // text 字段不受影响
    }
  }
}

八、数组字段的 null 值陷阱

⚠️ 坑点:空数组和 null 的查询行为不同

json
// 三种数据状态
{ "color_options": [] }          // 空数组
{ "color_options": null }        // null
{ }                              // 字段不存在

🔴 查询陷阱

json
// ❌ 错误:用 exists 查询空数组会返回 false
{
  "query": {
    "exists": {
      "field": "color_options"  // 空数组返回 false,因为 ES 认为没有值
    }
  }
}

// ✅ 正确:要查询所有有值的记录,用 must_not + exists 排除空
{
  "query": {
    "bool": {
      "must_not": [
        { "exists": { "field": "color_options" } }
      ]
    }
  }
}
// 返回:没有 color_options 字段 或 值为 null 的记录
// 注意:空数组 [] 也算"不存在"

九、terms 查询的"大小写陷阱"

⚠️ 坑点:keyword 字段区分大小写

json
// 数据
{ "category": "Electronics" }

// ❌ 错误:大小写不一致
{
  "query": {
    "term": {
      "category": "electronics"  // 匹配不到!
    }
  }
}

// ✅ 正确:完全匹配
{
  "query": {
    "term": {
      "category": "Electronics"
    }
  }
}

解决方案:索引时统一小写或用 match 查询(但会分词)

json
// 方案1:写入时统一小写
{ "category": "electronics" }

// 方案2:使用 match 查询(不推荐,会分词)
{
  "query": {
    "match": {
      "category": "electronics"  // 可以匹配,但会分词
    }
  }
}

十、排序与评分的内存陷阱

⚠️ 坑点:对 text 字段排序会报错

json
// ❌ 错误:对 text 类型排序
{
  "sort": [
    { "description": "asc" }  // 报错!text 字段不能排序
  ]
}

// ✅ 正确:用 keyword 子字段排序
{
  "sort": [
    { "description.keyword": "asc" }
  ]
}

⚠️ 坑点:多字段排序时,第二排序在深度分页时失效

json
// ❌ 问题:深度分页时结果不稳定
{
  "from": 10000,
  "size": 20,
  "sort": [
    { "rating": "desc" },
    { "sales_volume": "desc" }
  ]
}

// ✅ 解决方案:加入唯一ID作为最终排序字段
{
  "sort": [
    { "rating": "desc" },
    { "sales_volume": "desc" },
    { "product_id": "asc" }  // 确保唯一性
  ]
}

📋 快速检查清单

检查项正确做法错误做法
keyword 数组统一用 term 查询误以为数组必须用 terms
text vs keyword搜索用 match + text,精确用 term + keywordterm 查 text
nested 查询必须用 nested + path直接用字段名查询
浮点数比较range + 范围term 精确匹配
日期格式严格按 format 写入混用多种日期格式
ignore_above注意截断,避免用 keyword 查长文本盲目依赖 keyword 精确查询
空数组must_not + exists 检测空值exists 直接查空数组
大小写索引时统一小写,或用 matchterm 查询大小写不一致
排序.keyword 子字段排序直接对 text 排序
深度分页加唯一ID排序只依赖非唯一字段排序

如有转载或 CV 的请标注本站原文地址