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

