首页/数据/NoSQL 速查/脚本字段与 painless 脚本查询
Elasticsearch89 条

脚本字段与 painless 脚本查询

script_fields 在返回结果时用 painless 计算派生字段(不写入索引),script query 则用脚本作为过滤条件;两者都依赖 doc values 或 _source。

NoSQL 速查数据更新于 2026-10-13

脚本字段与脚本查询

script_fields 在返回结果时用 painless 脚本计算派生字段,结果不写入索引;script query 则把脚本当作过滤条件,只有脚本判断为真的文档才被返回。两者都依赖字段的正排存储(doc values)或原始 _source 取值,因此脚本里能取到哪些字段取决于映射配置。

为什么要注意编译缓存

painless 脚本的编译结果会被缓存,但缓存与脚本源码相关:把常量直接写死在 source 里,每个不同取值都会触发一次重新编译;把变量放进 params 传递,才能复用同一份编译产物。这是脚本类查询里最容易踩、也最容易修的性能细节。

基本信息

script_fields返回时计算派生字段,如 doc['price'].value * 1.1
script query用脚本作为过滤条件,命中才返回
取值方式doc[...] 走正排 doc values,params._source 走原始 JSON
性能特征逐文档执行,无法走索引,过滤大结果集时开销明显
适用版本painless 自 5.0 起为默认脚本语言

示例

示例
GET /my-index/_search
{
  "query": { "match_all": {} },
  "script_fields": {
    "price_with_tax": {
      "script": { "lang": "painless", "source": "doc['price'].value * params.rate", "params": { "rate": 1.1 } }
    }
  }
}

# 脚本查询:满足条件才返回
GET /my-index/_search
{ "query": { "script": { "script": { "source": "doc['num'].value > params.min", "params": { "min": 1 } } } } }

# 访问可能缺失的字段前先判断,避免抛异常
# source: "doc.containsKey('num') && doc['num'].value > 1"

典型使用场景

  • 派生展示:含税价、折后金额等只在返回时计算,不占用索引空间
  • 复杂过滤:需要跨字段运算或自定义判断时用脚本查询
  • 临时探索:一次性分析不想为此新增字段
  • 排序策略:按脚本算出的分数排序,注意只在小结果集上使用
  • 口径核对:用脚本输出中间值定位字段计算差异

写法与代价

写法含义代价
script_fields返回时计算,不落索引逐命中计算,命中多时耗时线性增长
script query作为过滤条件无法用索引,逐文档判断
doc['f'].value取正排序值text 字段没有正排,需改取 keyword
params._source.f读取原始 JSON速度更慢,频繁使用要评估
脚本里直接访问缺失字段会抛异常,取值前用 doc.containsKey 判断更稳;对 text 字段用 doc 取值需要开启 fielddata,正确做法是改取 keyword 子字段。
脚本查询绕过倒排索引、逐文档执行,在大数据集上容易变成集群热点;能用普通查询表达的逻辑不要用脚本实现。

易错点

  • 对 text 字段用 doc 取值需要 fielddata,未开启会直接报错,脚本里应改取 keyword 字段
  • 常量写死在 source 里会为每个取值生成新脚本,编译缓存命中不了,性能明显下降
  • 脚本查询无法利用索引,过滤大结果集时开销随文档数线性增长,常被误判成"查询本来就慢"
  • 缺少字段存在性判断会让脚本在部分文档上抛异常,整个请求失败而不是跳过该文档

数据来源与参考

  • Elasticsearch Guide: Script fields
  • Elasticsearch Guide: Script query
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌