写在前面
本文看下es的nested嵌套对象相关内容。
1:es用了啥范式?
在关系型数据库中定义了6大数据库范式,即1,2,3,BC,4,5的NF(normal form),分别如下:
1NF:每个列都不可拆分,即都是原子的 2NF:在满足1NF的基础上,消除部分函数依赖 3NF:在满足2NF的基础上,消除传递函数依赖 BCNF:在满足3NF的基础上,消除主属性对于码的部分函数依赖和传递函数依赖(此时和非主键列没有关系) 4NF:在满足BCNF的基础上,消除表内的多对多关系 5NF:略
数据库范式的目的在于减少更新的复杂度,以及降低磁盘的存储空间。其中对于第二个问题存储设备目前非常廉价而且容量很大,所以不是什么问题了。对于第一个更新的复杂度问题会带来的查询效率变低的问题,因为需要更多的关联join。那么对于es来说它是使用了哪种范式呢?因为es的设计目标是快速查询,所以使用到是反范式,即冗余存储。比如如下的数据:
如果按照关系型数据库范式来设计,user的信息需要存储到单独的一张表中去,但是在es中就是在一个对象中来存储,对于这种存储,es支持非常方便和高效的查询:
- 准备数据
DELETE blog # 设置blog的 Mapping PUT /blog { "mappings": { "properties": { "content": { "type": "text" }, "time": { "type": "date" }, "user": { "properties": { "city": { "type": "text" }, "userid": { "type": "long" }, "username": { "type": "keyword" } } } } } } # 插入一条 Blog 信息 PUT blog/_doc/1 { "content":"I like Elasticsearch", "time":"2019-01-01T00:00:00", "user":{ "userid":1, "username":"Jack", "city":"Shanghai" } }
- 查询
# 查询 Blog 信息 POST blog/_search { "query": { "bool": { "must": [ {"match": {"content": "Elasticsearch"}}, {"match": {"user.username": "Jack"}} ] } } }
我们再来看一个存储对象数组的例子:
- 准备数据
DELETE my_movies # 电影的Mapping信息 PUT my_movies { "mappings" : { "properties" : { "actors" : { "properties" : { "first_name" : { "type" : "keyword" }, "last_name" : { "type" : "keyword" } } }, "title" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } # 写入一条电影信息 POST my_movies/_doc/1 { "title":"Speed", "actors":[ { "first_name":"Keanu", "last_name":"Reeves" }, { "first_name":"Dennis", "last_name":"Hopper" } ] }
- 查询first_name为Keanu并且last_name为Hopper的文档信息
POST my_movies/_search { "query": { "bool": { "must": [ {"match": {"actors.first_name": "Keanu"}}, {"match": {"actors.last_name": "Hopper"}} ] } } }
按照正常思维,应该查不到才对,但为什么查到了呢?这和es的数据存储方式有关系,对于数组es默认是按照一种扁平结构来存储的,如下:
这种存储结构的好处是可以加快查询的速度,但坏处呢就是上例中反直觉结果。
所以如果能够让内部的对象也按照单独文档来存储,就能解决这个查询错误的问题了,而想要使用单独的文档来存储内部的对象,就需要用到es提供的nested对象功能,继续来看(作为本文的主题,必须单开一部分,还必须是一级标题😀😀😀)。
2:nested对象
nested是一种定义对象的数据类型,比如可通过如下方式来定义一个nested的类型:
在保存时会被保存为单独的文档,查询时通过join的方式来查询,当然此时会牺牲掉部分查询性能。
- 创建如下的mapping
DELETE my_movies # 创建 Nested 对象 Mapping PUT my_movies { "mappings" : { "properties" : { "actors" : { "type": "nested", "properties" : { "first_name" : {"type" : "keyword"}, "last_name" : {"type" : "keyword"} }}, "title" : { "type" : "text", "fields" : {"keyword":{"type":"keyword","ignore_above":256}} } } } }
- 接着来插入测试数据
POST my_movies/_doc/1 { "title":"Speed", "actors":[ { "first_name":"Keanu", "last_name":"Reeves" }, { "first_name":"Dennis", "last_name":"Hopper" } ] }
此时存储结构为红框中所示:
- 查询first_name为Keanu并且last_name为Hopper的文档信息
此时就查询不到了:
POST my_movies/_search { "query": { "bool": { "must": [ { "nested": { "path": "actors", "query": { "bool": { "must": [ { "match": { "actors.first_name": "Keanu" } }, { "match": { "actors.last_name": "Hopper" } } ] } } } } ] } } }
当然如果把"actors.last_name": "Hopper"改为Reeves是能查出来数据的:
写在后面
参考文章列表
关系型数据库MySQL及其优化 。
- 查询first_name为Keanu并且last_name为Hopper的文档信息
- 接着来插入测试数据
- 创建如下的mapping
- 查询first_name为Keanu并且last_name为Hopper的文档信息
- 准备数据
- 查询