本学习笔记基于ElasticSearch 7.10版本,旧版本已经废弃的查询功能暂时不做笔记,以后有涉及到再做补充。
参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/joining-queries.html
关系型数据库中有表的关联关系,在 ElasticSearch 中,也会有类似的需求,例如订单表和商品表,我们可以使用以下两种方式实现:
- 嵌套文档(nested)
- 父子文档
1、嵌套文档
嵌套文档 nested
在之前介绍字段类型的笔记中有学习过:ElasticSearch学习笔记六(字段类型 Field data types),这里不重复介绍。
现在,假设有一个电影文档,每个电影都有演员信息:
# 需要将 actors 定义为 nested 类型,否则字段中的关联关系会丢失
PUT movies
{
"mappings": {
"properties": {
"actors": {
"type": "nested"
}
}
}
}
PUT movies/_doc/1
{
"name": "霸王别姬",
"actors": [
{
"name": "张国荣",
"gender": "男"
},
{
"name": "巩俐",
"gender": "女"
}
]
}
嵌套类型的缺点:
首先我们来看一下 movies 索引中的文档数量
GET _cat/indices?v
我们只插入了一篇文档,但是文档数量却变成3:
这是因为 nested
文档在 ElasticSearch 内部其实也是独立的 lucene 文档,也就是说actors 在内部单独保存为两份文档。只是在我们查询的时候,ElasticSearch 内部帮我们做了 join 处理,所以最终看起来就像一个独立文档一样。
如果nested
文档中的数据比较多时,可能会生成多分嵌套文档,所以这种方案性能并不是特别好。
此外,nested
文档更新的时候,也会更新所有的嵌套文档,比较耗性能。
2、嵌套查询
使用嵌套查询 nested
来查询嵌套文档:
GET movies/_search
{
"query": {
"nested": {
"path": "actors",
"query": {
"bool": {
"must": [
{
"match": {
"actors.name": "张国荣"
}
},
{
"match": {
"actors.gender": "男"
}
}
]
}
}
}
}
}
3、父子文档
nested
文档中,嵌套的文档只能是一对一的关系,不能复用。比如“张国荣”还演过其他电影,那只能在每一部电影的 actors 中再添加一次。
而父子文档就可以解决这种问题,相比于嵌套文档,主要有如下优势:
- 更新父文档时,不会重新索引子文档。
- 创建、修改或者删除子文档时,不会影响父文档或其他子文档。
- 子文档可以作为搜索结果独立返回。
例如学生和班级的关系:
PUT stu_class
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"s_c": {
"type": "join",
"relations": {
"class": "student"
}
}
}
}
}
解释一下几个参数的意义:
- s_c :表示定义父子文档的字段名,可以自定义。
- join :表示这是一个父子文档。
- relations :里面分别定义 class 是 parent 父文档,student 是 child 子文档。
插入两个父文档:
PUT stu_class/_doc/1
{
"name": "一班",
"s_c": {
"name": "class"
}
}
PUT stu_class/_doc/2
{
"name": "二班",
"s_c": {
"name": "class"
}
}
插入三个子文档:
PUT stu_class/_doc/3?routing=1
{
"name": "zhangsan",
"s_c": {
"name": "student",
"parent": 1
}
}
PUT stu_class/_doc/4?routing=1
{
"name": "lisi",
"s_c": {
"name": "student",
"parent": 1
}
}
PUT stu_class/_doc/5?routing=2
{
"name": "wangwu",
"s_c": {
"name": "student",
"parent": 2
}
}
注意: 子文档需要和父文档在同一个分片上,而 ElasticSearch 默认使用文档 _id 进行哈希计算后决定存在哪个分片上,所以 routing 关键字的值为父文档的 id 即可。
另外,一个索引只能定义一个 join 字段,可以向一个已经存在的 join 字段上新增关系。
3.1、has_child
通过子文档查询父文档使用 has_child
查询:
GET stu_class/_search
{
"query": {
"has_child": {
"type": "student",
"query": {
"match": {
"name": "lisi"
}
}
}
}
}
3.2、has_parent
通过父文档查询子文档使用 has_parent
查询:
GET stu_class/_search
{
"query": {
"has_parent": {
"parent_type": "class",
"query": {
"match": {
"name": "一班"
}
}
}
}
}
注意: 使用 has_parent
查询不进行评分,这时可以考虑使用 parent_id
查询子文档:
GET stu_class/_search
{
"query": {
"parent_id": {
"type": "student",
"id": 1
}
}
}
4、小结
1、 普通子对象实现一对多(不使用nested
或父子文档),会损失子文档的边界,子对象之间的关联关系丢失;
2、 nested
可以解决关联关系丢失的问题,但是有两个缺点:更新主文档的时候要全部更新,不支持子文档属于多个主文档;
3、 父子文档则可以解决nested
的问题,但是主要适用于写多读少的场景;
版权声明:
本文仅记录ElasticSearch学习心得,如有侵权请联系删除。
更多内容请访问原创作者:江南一点雨
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: