📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: 6.8-8.17

1. 错误异常的基本描述 #

Can only use wildcard queries on keyword and text fields 是 Elasticsearch 在执行通配符查询(wildcard query)时抛出的异常,表示当前查询目标字段的数据类型不支持通配符查询。

该异常通常发生在查询解析阶段,Elasticsearch 在构建查询计划时发现字段类型与 wildcard 查询不兼容,从而直接拒绝请求。这不仅会导致当前搜索失败,还可能影响上层业务功能,因此需要及时定位并修复。

常见现象 #

  • 搜索接口返回 400 Bad Request,响应体中包含 search_phase_execution_exceptionillegal_argument_exception
  • 应用日志中出现如下错误信息:
{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "Can only use wildcard queries on keyword and text fields - not on [price] which is of type [long]"
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "failed_shards": [...]
  },
  "status": 400
}
  • 在 Kibana 或客户端代码中,相关搜索功能突然不可用,影响正常业务查询。

典型报错与异常栈 #

illegal_argument_exception: Can only use wildcard queries on keyword and text fields - not on [age] which is of type [integer]
    at org.elasticsearch.index.query.WildcardQueryBuilder.doToQuery(WildcardQueryBuilder.java:...)
    at org.elasticsearch.index.query.AbstractQueryBuilder.toQuery(AbstractQueryBuilder.java:...)

2. 为什么会发生这个错误 #

wildcard 查询本质上是基于字符串模式匹配的查询,它需要对字段值进行通配符展开(支持 *? 通配符)。因此,Elasticsearch 仅允许在以下字段类型上使用:

支持的字段类型说明
keyword精确值字符串字段,适合做通配符匹配
text全文搜索字段,经过分词器处理

以下字段类型不支持 wildcard 查询:

不支持的字段类型原因
integer / long / short数值类型,无字符串模式匹配语义
float / double浮点数值类型
boolean布尔类型
date日期类型(内部以数值或特定格式存储)
geo_point / geo_shape地理类型
nested / object复合结构类型

常见触发场景 #

  1. 直接在数值/日期字段上使用 wildcard 查询:例如对 priceagecreated_at 等字段使用通配符匹配。
  2. 字段 mapping 与查询预期不一致:代码中期望字段是 keyword 类型,但实际索引中该字段是 integerdate
  3. 动态 mapping 导致字段类型不符合预期:未显式定义 mapping,Elasticsearch 自动推断字段类型为数值,后续查询却按字符串处理。
  4. 跨索引查询时字段类型不一致:多个索引中存在同名但类型不同的字段,查询时在某些索引上触发异常。

3. 如何排查这个异常 #

建议按以下顺序逐步定位问题:

步骤一:确认报错的具体字段和类型 #

从报错信息中提取字段名和类型。例如:

not on [email] which is of type [long]

说明 email 字段被映射成了 long 类型,而业务期望它是一个字符串字段。

步骤二:查看索引的 mapping 定义 #

# 查看指定索引的 mapping
GET /your_index/_mapping

# 查看特定字段的 mapping
GET /your_index/_mapping/field/your_field_name

重点关注字段的 type 值,确认是否与查询预期一致。

步骤三:检查是否存在跨索引字段类型冲突 #

如果查询涉及多个索引,逐一检查各索引的 mapping:

# 查看所有相关索引的 mapping
GET /index1,index2,index3/_mapping/field/your_field_name

若不同索引中同名字段的类型不一致,需要统一处理。

步骤四:确认是否为动态 mapping 导致 #

检查索引创建时是否未显式定义 mapping,导致 Elasticsearch 自动推断字段类型:

# 查看索引的动态 mapping 配置
GET /your_index/_settings | grep dynamic

4. 如何解决这个错误 #

根据排查结果,选择以下对应方案进行修复。

方案一:修改查询方式(推荐,无需重建索引) #

如果字段本身存储的是数值或日期,不应使用 wildcard 查询,而应根据实际场景改用合适的查询:

// 错误用法:对数值字段使用 wildcard
{ "wildcard": { "price": { "value": "*100*" } } }

// 正确用法:使用 range 查询
{ "range": { "price": { "gte": 100, "lte": 199 } } }

// 对日期字段使用 range 查询
{ "range": { "created_at": { "gte": "2024-01-01", "lte": "2024-12-31" } } }

方案二:修改 mapping,增加 keyword 多字段(需重建索引或更新 mapping) #

如果业务确实需要对某字段做通配符查询,可以将其改为 keyword 类型,或增加 keyword 多字段:

// 新增 keyword 多字段(适用于已有索引,需结合 reindex)
PUT /your_index/_mapping
{
  "properties": {
    "email": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword"
        }
      }
    }
  }
}

查询时使用 .keyword 子字段:

{
  "wildcard": {
    "email.keyword": {
      "value": "*@example.com"
    }
  }
}

方案三:重建索引(字段类型需要彻底修改时) #

如果原字段类型完全不符合需求,需要重建索引:

# 1. 创建新索引并显式定义 mapping
PUT /your_index_v2
{
  "mappings": {
    "properties": {
      "email": {
        "type": "keyword"
      },
      "price": {
        "type": "long"
      }
    }
  }
}

# 2. 使用 reindex 迁移数据
POST /_reindex
{
  "source": { "index": "your_index" },
  "dest": { "index": "your_index_v2" }
}

# 3. 创建别名指向新索引
POST /_aliases
{
  "actions": [
    { "remove": { "index": "your_index", "alias": "your_alias" } },
    { "add":    { "index": "your_index_v2", "alias": "your_alias" } }
  ]
}

方案四:使用 runtime field(无需修改原索引) #

Elasticsearch 7.11+ 支持 runtime field,可以在查询时动态定义字段类型:

{
  "runtime": {
    "email_as_keyword": {
      "type": "keyword",
      "script": {
        "source": "emit(doc['email'].value)"
      }
    }
  },
  "query": {
    "wildcard": {
      "email_as_keyword": {
        "value": "*@example.com"
      }
    }
  }
}

5. 预防建议与最佳实践 #

  1. 显式定义 mapping:创建索引时始终显式指定字段类型,避免依赖动态 mapping 自动推断,尤其是字符串字段应明确指定 keywordtext
  2. 合理使用多字段(multi-field):对字符串字段同时定义 text(用于全文搜索)和 keyword(用于精确匹配、聚合、通配符查询)子字段。
  3. 查询前校验字段类型:在应用层根据 mapping 信息动态构建查询,避免对不支持的字段类型发送 wildcard 查询。
  4. 优先使用 prefix 查询替代 wildcard:如果只需前缀匹配,使用 prefix 查询性能更好,且同样只支持 keyword/text 字段。
  5. 使用 INFINI Gateway 进行查询审计:在 Elasticsearch 前端部署 INFINI Gateway,可以自动识别并拦截非法查询,防止错误查询到达后端集群。
  6. 统一跨索引字段类型:使用 索引模板(Index Template)确保同一业务场景下各索引的字段类型保持一致。

6. 小结 #

Can only use wildcard queries on keyword and text fields 异常的根本原因是字段类型与查询语义不匹配。解决思路的核心在于:要么调整查询方式以适配字段类型,要么调整字段 mapping 以适配查询需求。在大多数场景下,通过增加 keyword 多字段即可解决问题,无需重建整个索引。

如果您的 Elasticsearch 集群中存在大量类似查询问题,建议使用 INFINI Console 查看索引 mapping 分布情况,快速定位类型不一致的字段,通过 INFINI Gateway 对异常查询进行拦截和改写,提升集群稳定性。

相关错误 #

附:源码上下文 #

以下是触发该异常的 Elasticsearch 源码片段,便于理解其内部逻辑:

@Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
    throw new QueryShardException(context,
        "Can only use wildcard queries on keyword and text fields - not on [" + name
        + "] which is of type [" + typeName() + "]");
}