版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
关系型数据库中往往存在关系,包括1对1,1对多,多对多,通过连接,可以进行多表查询。elasticsearch作为数据存储,搜索,分析的框架,在存储上采用文档式的存储方式,因此再把关系型数据库比如mysql的数据导入elasticsearch时,需要根据数据关系设计索引映射。数据关系,数据特点以及在性能上需求都会影响着我们如何设计索引映射。
对象类型(最擅长处理一对一关系)
将对象作为文档的一个字段值。比如店铺和位置就是一对一的关系,我们可以将位置最为文档的一个字段,而位置可能包括名称,经纬度等字段。
{
"shop_name":"KFC",
"location":{
"location_name":"华润五彩城",
"geolocation":"51.52,-0.099094"
}
}
而为了能够通过location_name搜索到这篇文档,文档最终会将location展开,类似于下面这个样子
{
"shop_name":"KFC",
"location.location_name":"华润五彩城",
"location.geolocation":"51.52,-0.099094"
}
因此在检索的时候我们需要在location.location_name上检索
curl 'host:port/index_name/type_name/_search?pretty' -d '
{
"query":{
"match":{
"location.location_name":"五彩城"
}
}
}
'
对象类型的映射是自动识别的,另外想假设要将一个对象数组作为文档的字段也是可行的,但是会发生什么样的问题呢?像下面这样的。
{
"shop_name":"KFC",
"locations":[{
"location_name":"KFC华润五彩城店",
"geolocation":"51.52,-0.099094"
},
{
"location_name":"KFC国贸店",
"geolocation":"21.52,-0.299094"
}
]
}
在建立索引的时候是无法区分边界的,也就是说可能会出现跨对象但是满足查询条件的文档出现,比如KFC华润五彩城店会和21.52,-0.299094成为一个新的对象,即便这个对象不存在。这是由于 JSON 格式的文档被处理成如下的扁平式键值对的结构。
{
"shop_name":"KFC",
"locations.location_name":["KFC华润五彩城店","KFC国贸店"],
"locations.geolocation":["51.52,-0.099094","21.52,-0.299094"]
}
而嵌套对象恰恰能解决这类问题,这是因为会将对象数组中的对象分别索引到分隔的文档上。
嵌套类型,父子关系(一对多关系)
嵌套类型需要在索引映射上显示定义
"location": {
"type" : "nested",
"properties" : {
"location_name" : { "type" : "string" },
"geolocation" : { "type" : "string"}
}
}
location的type为nested,而对象类型的type为object.
由于嵌套对象 被索引在独立隐藏的文档中,无法直接查询它们。 必须使用 nested
查询去获取它们:
{
"nested": {
"path": "comments",
"query": {
“match”:{
"comment.name":"John"
}
}
}
}
嵌套类型其实是将一对多关系放在一篇文档中,这样做存在优点,也存在缺点,使用者可以根据自己的需求进行选择。
优点:
明确对象数组中对象的边界。
缺点:
一旦子文档发生改变需要重新索引整篇文档
父子关系
父子关系相对于嵌套文档,更为灵活,因为父辈和子辈都是独立的elasticsearch文档,可以自行管理。对于子文档需要在映射中定义_parent字段,在索引的时候需要指定父辈的ID,同样地,父辈的ID和类型会作为子辈的路由值,这在查询的时候非常有益,能够自动地使用这个路由值来查询父辈的分片并获得子辈,或者在查询子辈的分片来获得其父辈。
curl -s -XPOST "$ADDRESS/product" -d@$(dirname $0)/mapping.json
#vim mapping.json
{
"settings" : {
"number_of_shards" : 2,
"number_of_replicas" : 1,
"mappings" : {
"spus": {
"_source" : {
"enabled" : true
},
"_all" : {
"enabled" : true
},
"properties" : {
.....
}
},
"skus" : {
"_source" : {
"enabled" : true
},
"_all" : {
"enabled" : true
},
"_parent" : {
"type" : "spus"
},
"properties" : {
.....
}
}
}
}
这也意味着在查询,更新,删除子文档的时候需要指定_parent字段,比如sku就是spu的子文档。
比如在索引一个子文档时,需要通过_parent字段显示指明父文档ID。
curl -s -XPOST "$ADDRESS/product/skus/100000001?parent=100000" -d'{
"sku_id": "100000001",
"is_hidden": false,
"price": 200.0,
....//子文档字段
"tags":["红色","欧式","皮制"]
}'
父子文档的查询可以独立查询,也可以通过子文档字段查询父文档,或者父文档字段查询所属的子文档。
比如,一个spu对应多个sku,我们可以通过父文档中spu_name=“舒适欧风四人沙发”获得所有sku文档。或者从子文档中sku_tag="红色"的所有父文档。
举个简单明了的例子,比如:
spu_id | spu | sku_id | sku |
---|---|---|---|
1 | 舒适欧风四人沙发 | 10 | 红色,欧式 |
1 | 舒适欧风四人沙发 | 11 | 黄色,欧式 |
假设我们搜索舒适欧风四人沙发,会返回10和11两个子文档,从父文档到子文档的搜索,而搜索红色会返回1一个父文档。
子文档搜索父文档使用has_child查询,父文档的_score是在子文档的基础上计算上的,可以设置score_mode,比如max, avg等。
curl 'host:port/index_name/spus/_search?size=20&pretty' -d
'{
"query":
{
"has_child":
{
"type": "skus",
"score_mode":"max",
"query":
{
"tags":"红色"
}
}
}
}'
curl 'host:port/index_name/spus/search?pretty' -d '
{
"query": {
"has_child": {
"type": "skus",
"score_mode": "max",
"query": {
"match": {
"tag": "红色"
}
}
}
}
}'
父文档查询子文档使用has_parent查询
curl 'host:port/index_name/skus/search?pretty' -d '
{
"query": {
"has_parent": {
"type": "spus",
"score_mode": "max",
"query": {
"match": {
"sup_name": "舒适欧风四人沙发"
}
}
}
}
}'
利用父子文档处理1对多关系存在一些优点,比如
官方文档在使用父子关系时,给出了下面几点建议
反规范化,应用层连接(多对多关系)
针对多对多的关系,冗余大量的数据可能会成为比较好的解决方法,将多对多的关系,在其中一个方向上冗余数据从而变成一对多的关系,然后在根据数据特点和对查询性能,索引性能的需求选择嵌套类型或者时父子关系。