尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

第六阶段 51 · join 父子关系与关系建模(ES 里怎么做 JOIN)

第六阶段 51 · join 父子关系与关系建模(ES 里怎么做 JOIN) 51 · join 父子关系与关系建模ES 里怎么做 JOIN阶段第六阶段 / 进阶专题ESjoin字段 has_child/has_parent、反范式、terms lookup、enrich | PostgreSQLJOIN、表继承、物化视图1. 概念ES 没有真正的跨索引 JOIN这是从 PG 转过来最需要转变的观念ES 不擅长 JOIN它鼓励“用存储换查询速度”。处理关系有四条路线按推荐度方案关系落在哪适合代价反范式冗余首选写入时拍平进一篇文档关系简单、读多写少数据冗余、更新要同步nested第 25 篇同一篇文档内的数组对象主文档有限明细一起读写子项更新要重索引整篇join父子同一索引里的独立父/子文档子项需独立频繁更新、一对多量大慢、吃内存、不能跨索引terms lookup / enrich查询时/写入时做轻量关联用维表补字段有限场景一句话能反范式就别 join。join/nested 是不得已才用。2. PostgreSQL 对照-- PG跨表 JOIN 很自然SELECTo.*,c.nameFROMorders oJOINcustomers cONc.ido.customer_id;-- PG 表继承父子CREATETABLEbase_event(...);CREATETABLEclick_event()INHERITS(base_event);ES 对应跨表 JOIN →反范式把customer_name写进 order 文档或 enrich。表继承/父子 →join字段同一索引里父子文档。3.join字段父子关系3.1 mapping声明父子关系PUT orders_idx { mappings: { properties: { my_join: { type: join, relations: { order: item } // order 是父item 是子 }, order_no: { type: keyword }, sku: { type: keyword }, qty: { type: integer } } } }3.2 写入父、写入子子必须带 routing 父ID保证同分片# 父文档 PUT orders_idx/_doc/order-1 { order_no: SO-1, my_join: order } # 子文档routing 指向父parent 指定父ID PUT orders_idx/_doc/item-1?routingorder-1 { sku: A, qty: 10, my_join: { name: item, parent: order-1 } }3.3 查询has_child / has_parent# 找“包含 skuA 子项”的父订单 GET orders_idx/_search { query: { has_child: { type: item, query: { term: { sku: A } }, inner_hits: {} // 可选带出命中的子文档 } } } # 反过来找某父订单下的所有子项 GET orders_idx/_search { query: { has_parent: { parent_type: order, query: { term: { order_no: SO-1 } } } } }4. Spring Boot 实现ComponentpublicclassDoc51Join{AutowiredprivateElasticsearchClientelasticsearchClient;/** has_child找包含指定 sku 子项的父订单 */publicListMapString,ObjectparentsHavingSku(StringindexName,Stringsku)throwsIOException{SearchResponseMaprespelasticsearchClient.search(s-s.index(indexName).query(q-q.hasChild(hc-hc.type(item).query(cq-cq.term(t-t.field(sku).value(sku))).scoreMode(ChildScoreMode.None)// 不需要子分影响父分.innerHits(ih-ih))),// 带出命中的子文档Map.class);returnresp.hits().hits().stream().map(Hit::source).filter(Objects::nonNull).collect(Collectors.toList());}/** 写子文档必须带 routing 父ID */publicvoidindexChild(StringindexName,StringchildId,StringparentId,MapString,ObjectchildDoc)throwsIOException{childDoc.put(my_join,Map.of(name,item,parent,parentId));elasticsearchClient.index(i-i.index(indexName).id(childId).routing(parentId)// ★ 关键与父同分片.document(childDoc));}}importco.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode。子文档写入/查询/删除都要带routing父ID否则找不到或落错分片。5. 反范式与轻量关联更推荐的日常做法5.1 反范式首选写入时就把维表字段冗余进主文档查询零 join// order 文档直接带上客户名而不是只存 customer_id 再去 join{order_no:SO-1,customer_id:C9,customer_name:Acme,amount:1000}维表变了要同步更新冗余字段用_update_by_query第 32 篇。读多写少时这笔账很划算。5.2 terms lookup用一篇文档的值去过滤GET orders_idx/_search { query: { terms: { customer_id: { index: vip_customers, id: batch-1, path: ids // 取那篇文档的 ids 数组做 IN } } } }5.3 enrich processor写入时自动补字段在 ingest pipeline 里配 enrich policy把维表字段在写入时自动拼进文档——相当于 ETL 的 lookup join查询时就是普通字段。6. 坑与最佳实践join父子必须同索引、同分片靠routing父ID保证不能跨索引 join。父子有性能代价has_child/has_parent比普通查询慢、更吃内存量大时评估。一个索引里 join 关系尽量单一多级/多种父子会让维护和性能雪上加霜。优先反范式ES 是查询引擎不是关系库能冗余拍平就别上 join/nested。nested vs join 选型明细跟主文档一起读写 →nested子项要独立高频更新 →join。更新冗余字段用_update_by_query维表变更后批量刷新被冗余的字段。相关文档内数组对象25-nested-嵌套对象与查询.md批量刷新冗余字段32-update_by_query-delete_by_query.md索引重建改关系模型33-index-mapping-管理.md
返回列表