44 · Painless 脚本与 runtime fields(运行时字段)
阶段:第五阶段 / 进阶
ES:script fields / script query / update script / runtime fields | PostgreSQL:表达式列 / 生成列 /UPDATE SET col = expr
1. 概念
Painless是 ES 内置脚本语言(类 Java)。四个高频用途:
| 用途 | 作用 | SQL 类比 |
|---|---|---|
| script field | 查询时算一个临时字段返回 | SELECT price*qty AS amount |
| script query | 用脚本做过滤条件 | WHERE price*qty > 1000 |
| update script | 按脚本原地改文档 | UPDATE SET n = n + 1 |
| runtime field | mapping 里定义查询时计算的字段(不占存储) | 生成列(虚拟) |
原则:脚本灵活但慢,能用普通字段/查询解决就别上脚本。
2. PostgreSQL 对照
-- 表达式列SELECTprice*qtyASamountFROMsales;-- 生成列(存储或虚拟)≈ runtime fieldALTERTABLEsalesADDCOLUMNamountnumericGENERATED ALWAYSAS(price*qty)STORED;-- 脚本更新UPDATEsalesSETview_count=view_count+1WHEREid=1;3. ES DSL
3.1 script field(查询时算字段)
GET sales_idx/_search { "script_fields": { "amount": { "script": { "source": "doc['price'].value * doc['qty'].value" } } } }3.2 script query(脚本过滤)
GET sales_idx/_search { "query": { "bool": { "filter": { "script": { "script": "doc['price'].value * doc['qty'].value > params.min", "params": { "min": 1000 } } } } } }3.3 update by script(自增)
POST sales_idx/_update/1 { "script": { "source": "ctx._source.view_count += params.n", "params": { "n": 1 } } }3.4 runtime field(查询时计算的虚拟字段,推荐)
PUT sales_idx/_mapping { "runtime": { "amount": { "type": "double", "script": { "source": "emit(doc['price'].value * doc['qty'].value)" } } } } GET sales_idx/_search { "query": { "range": { "amount": { "gte": 1000 } } } } // 像普通字段一样用4. Spring Boot 实现
@ComponentpublicclassDoc44Script{@AutowiredprivateElasticsearchClientelasticsearchClient;/** 用 params 传参的脚本过滤:price*qty > min */publicList<Map<String,Object>>byAmountScript(StringindexName,doublemin)throwsIOException{SearchResponse<Map>resp=elasticsearchClient.search(s->s.index(indexName).query(q->q.bool(b->b.filter(f->f.script(sc->sc.script(scr->scr.source("doc['price'].value * doc['qty'].value > params.min").params("min",JsonData.of(min))))))),Map.class);returnresp.hits().hits().stream().map(Hit::source).filter(Objects::nonNull).collect(Collectors.toList());}}import:
co.elastic.clients.json.JsonData。动态值一律走params,别拼进source
字符串——既能被脚本缓存复用,又避免注入。
5. 坑与最佳实践
- 能不用脚本就不用:脚本不走倒排、逐文档执行,慢且难缓存。
- 一定用
params传参:相同source+ 不同params才会命中脚本编译缓存。 doc['field']vsparams._source:doc[...]读 doc_values(快,text不可用);ctx._source/params._source读原文(慢)。过滤/排序用doc[...]。- runtime field 换取灵活性:查询时算、零存储、可随时改定义;代价是每次查询都算,重查询慎用。
- update script 注意并发:高并发自增用
_update+ 乐观锁(if_seq_no/if_primary_term)。