原文地址

接上一篇

四、模型树结构

父引用的模型树结构

这个数据模型描述了一个树形结构,在子节点中存储父节点的引用。

模式

父引用模式存储每个树节点到文档中,除了树节点外,文档还存储了父节点的id。

考虑以下目录的层级关系。

以下为应用实例

db.categories.insert( { _id: "MongoDB", parent: "Databases" } )
db.categories.insert( { _id: "dbm", parent: "Databases" } )
db.categories.insert( { _id: "Databases", parent: "Programming" } )
db.categories.insert( { _id: "Languages", parent: "Programming" } )
db.categories.insert( { _id: "Programming", parent: "Books" } )
db.categories.insert( { _id: "Books", parent: null } )

查询一个节点的父节点变得快速且直观

db.categories.findOne( { _id: "MongoDB" } ).parent

还可以对parent字段创建索引以提高查询速度

db.categories.createIndex( { parent: 1 } )

通过parent字段查询其直接子节点

db.categories.find( { parent: "Databases" } )

这种父引用模式是实现树存储的简单方法,缺点是获取子树时则需要多个查询。

子引用的模型树结构

这个数据模型描述了树形结构,存储子节点的引用到父节点中。

模式

子引用模式存储每个树节点到一个文档中,除了树节点,文档还存储了包含其子节点id的数组。

考虑与上一个图中相同的目录层级关系,其子引用模式的实现如下

db.categories.insert( { _id: "MongoDB", children: [] } )
db.categories.insert( { _id: "dbm", children: [] } )
db.categories.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ] } )
db.categories.insert( { _id: "Languages", children: [] } )
db.categories.insert( { _id: "Programming", children: [ "Databases", "Languages" ] } )
db.categories.insert( { _id: "Books", children: [ "Programming" ] } )

查询获取一个节点的直接子节点则变得快速且直观

db.categories.findOne( { _id: "Databases" } ).children

创建children字段上的索引以实现快速查询

db.categories.createIndex( { children: 1 } )

通过children信息查询其父节点以及兄弟节点信息

db.categories.find( { children: "MongoDB" } )

子引用模式提供了一种树存储的合适方案,只要不需要对子树操作就行(单个查询无法保证获取一个节点的所有子节点)。对于存储图像,其中一个节点可能有多个父节点,那这个模式也是很好的方案。

祖先数组的模型树结构

这个数据模型描述了树形结构,使用对父节点的引用和一个数组来存储所有的祖先节点。

模式

文档中除了存储每个树节点,还存储了一个数组,其他暴露了所有祖先节点的id或者路径(祖先节点的id按顺序排序组成路径)。

考虑跟上面图中相同的目录层级关系

以下实际实例中,文档除了ancestors字段,还有parent字段,parent字段存储了直接父节点的引用。

db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } )
db.categories.insert( { _id: "Books", ancestors: [ ], parent: null } )

查询获取一个节点的祖先节点或者路径则变得快速而直观

db.categories.findOne( { _id: "MongoDB" } ).ancestors

对ancestors字段创建索引提高查询速度

db.categories.createIndex( { ancestors: 1 } )

查询ancestors字段以查找它的所有后代节点

db.categories.find( { ancestors: "Programming" } )

祖先数组模式提供了查询某节点的后代节点以及某节点的祖先节点的有效方案,所以当需要对子树节点进行操作时,这个模式是一个很好的选择。

祖先数组模式比具体化路径(Materialized Paths)模式稍慢,但是使用更直观。

具体化路径(Materialized Paths)的模型树结构

这个数据模型描述了树形结构,存储文档间的全关系路径。

模式

具体化路径中,文档除了存储树节点之外,还存储了节点的祖先的id或者路径。尽管具体化路径模式要求额外的步骤处理字符串和正则表达式,这个模式也提供了处理路径的灵活性,如通过部分路径查找节点。

考虑与上面的图中相同的目录层级关系

具体化路径模式中,存储路径到path字段中,路径使用逗号作为分隔符

db.categories.insert( { _id: "Books", path: null } )
db.categories.insert( { _id: "Programming", path: ",Books," } )
db.categories.insert( { _id: "Databases", path: ",Books,Programming," } )
db.categories.insert( { _id: "Languages", path: ",Books,Programming," } )
db.categories.insert( { _id: "MongoDB", path: ",Books,Programming,Databases," } )
db.categories.insert( { _id: "dbm", path: ",Books,Programming,Databases," } )

通过path字段查询所有的节点

db.categories.find().sort( { path: 1 } )  // 按path的升序

对path字段使用正则表达式查找Programming的后代

db.categories.find( { path: /,Programming,/ } ) // 路径包含",Programming,"

查找Books的后代,因为Books是最高节点,故正则表达式以",Books,"开头

db.categories.find( { path: /^,Books,/ } )

对path字段创建索引

db.categories.createIndex( { path: 1 } )

根据具体查询,这个索引可能会提高性能:

  • 对于根节点Books的子树上的查询(例如,/^,Books,/ 或者 /^,Books,Programming,/),path字段的索引会明显提高查询性能。
  • 对于未提供到根节点的路径的子树上的查询(例如, /,Databases,/),或者类似的子树查询,节点在加索引的字符串的中间,这时查询必须扫描所有索引。对这些查询来说,如果索引比整个集合小很多,则索引会提高查询性能。

内嵌集(Nested Sets)的模型树结构

这个数据模型描述了树形结构,优化了查找子树的性能,但是也会导致树的易变性的增加。

模式

内嵌集模式对树作一个往返遍历并标示树中每个节点为停留点。应用程序对每个节点访问两次,第一次为去往遍历,第二次为返回遍历。内嵌集模式中文档除了存储树节点,还存储了其父节点的id,存储其去往停留点到left字段,以及存储返回停留点到right字段。

考虑如下目录的层级关系

下面代码演示了内嵌集的实现

db.categories.insert( { _id: "Books", parent: 0, left: 1, right: 12 } )
db.categories.insert( { _id: "Programming", parent: "Books", left: 2, right: 11 } )
db.categories.insert( { _id: "Languages", parent: "Programming", left: 3, right: 4 } )
db.categories.insert( { _id: "Databases", parent: "Programming", left: 5, right: 10 } )
db.categories.insert( { _id: "MongoDB", parent: "Databases", left: 6, right: 7 } )
db.categories.insert( { _id: "dbm", parent: "Databases", left: 8, right: 9 } )

查询获取一个节点的后代

var databaseCategory = db.categories.findOne( { _id: "Databases" } );
db.categories.find( { left: { $gt: databaseCategory.left }, right: { $lt: databaseCategory.right } } );

内嵌集模式提供了为查找子树的一个快速并有效率的方案,但是在修改树结构时较为麻烦。故这种模式适合不会改变结构的静态树。

五、模型特定的程序上下文

原子操作的模型数据

MongoDB中的写操作,例如db.collection.update(),db.collection.findAndModify(),db.collection.remove()在单个文档级别是原子的。对于那些需要一起被更新的字段,将字段嵌入到文档中可以确保对这些字段的更新是原子的。

例如,考虑这样一种情况,当需要维护书的信息时,包括可以检出的书数量以及当前检出信息,如何确定这两个操作整体的原子性?

可获得的书和检出信息必须同步。这样,将available字段和checkout字段嵌入相同的文档以确保更新文档是原子性的。

{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly",
available: 3,
checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]
}

那么,根据新的检出信息,可以使用db.collection.update()进行更新,并且对available字段和checkout字段的更新是原子性的。

db.books.update (
{ _id: 123456789, available: { $gt: 0 } }, //查询条件
{
$inc: { available: -1 },    //available字段减1
$push: { checkout: { by: "abc", date: new Date() } }  // checkout字段数组增加一个元素
}
)

以上操作返回一个WriteResult()对象,包含了操作的有关信息

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

nMatched字段表明1个文档匹配这个更新条件,nModified表明这个操作更新了一个文档。

支持关键字查询的模型数据

关键字搜索与文本搜索或者全文搜索不同,它不提供词干提取或者其他文本处理特性。

此模式描述了支持关键字搜索的方法,以支持程序搜索功能,这个搜索方法使用存储在相同文档中的数组中的关键字,而这个数组作为这个文档的文本字段。与多键索引结合后,这个模式可以支持程序的关键字搜索操作。

模式

为了向文档中增加结构以支持基于关键字的查询,首先向文档中创建一个数组字段,并将关键字以字符串形式添加到数组中。然后可以对这个数组创建多键索引,并创建查询从这个数组中选择数值。

实例:

给定一个图书卷的集合,并想提供一个机遇主题的搜索。对每个卷而言,我们增加topics数组,并尽可能的对这个卷添加关键字到数组中。对Moby-Dick卷可以由如下文档表示,

{ title : "Moby-Dick" ,
author : "Herman Melville" ,
published : 1851 ,
ISBN : 0451526996 ,
topics : [ "whaling" , "allegory" , "revenge" , "American" ,
"novel" , "nautical" , "voyage" , "Cape Cod" ]
}

然后对topics数组创建多键索引

db.volumes.createIndex( { topics: 1 } )

多键索引对topics数组中的每个关键字创建索引项。例如,索引中有一个是关于"whaling"的索引项,另一个是关于"allegory"的索引项。

然后基于关键字的查询示例如下

db.volumes.findOne( { topics : "voyage" }, { title: 1 } )

提示:数组中的元素数量很大时,如有几百或上千的关键字,那插入操作会导致加索引花费很大。

关键字索引的限制

使用特定的数据模型和多键索引,MongoDB可以支持关键字搜索。然而,这些关键字索引在以下方面与全文搜索比较则显得不足或者无法相比:

  • 词干提取。关键字查询无法解析关键字为根或者有关词语。
  • 同义。关键字搜索特性必须在应用层提供同义支持或相关查询。
  • 排序。关键字查询不提供判断结果权重的方式。
  • 异步索引。MongoDB同步创建索引,这意味着为关键字使用的索引总是处于当前的并可实时操作。然而,异步创建的索引在某种内容和工作负荷下效率更高。

MongoDB数据模型(二)的更多相关文章

  1. MongoDB(二)-- Java API 实现增删改查

    一.下载jar包 http://central.maven.org/maven2/org/mongodb/mongo-java-driver/ 二.代码实现 package com.xbq.mongo ...

  2. 第二课 MongoDB 数据模型

    1.课程大纲 本课程主要介绍MongoDB数据模型相关知识.包含文档.集合与数据库的基本概念.用法及命名规则:MongoDB主要的数据类型介绍以及MongoDB Shell的简单介绍与使用. 文档 ( ...

  3. MongoDB数据模型和索引学习总结

    MongoDB数据模型和索引学习总结 1. MongoDB数据模型: MongoDB数据存储结构: MongoDB针对文档(大文件採用GridFS协议)採用BSON(binary json,採用二进制 ...

  4. MongoDB Sharding(二) -- 搭建分片集群

    在上一篇文章中,我们基本了解了分片的概念,本文将着手实践,进行分片集群的搭建 首先我们再来了解一下分片集群的架构,分片集群由三部分构成: mongos:查询路由,在客户端程序和分片之间提供接口.本次实 ...

  5. MongoDB数据模型(三)

    六.数据模型引用 文档 我们已经知道MongoDB以文档的形式存储数据,而文档是JSON风格的数据结构,由一系列的“字段名-值”对组成,如下所示 { "item": "p ...

  6. MongoDB数据模型(一)

    原文地址 一.数据模型介绍 MongoDB中的数据有着灵活的架构.与SQL数据库不同,因为SQL数据库必须先定义表结构,然后才能向其中插入数据,而MongoDB的集合不强制任何文档结构.这个灵活性方便 ...

  7. MongoDB系列二(介绍).

    一.特点 学习一个东西,至少首先得知道它能做什么?适合做什么?有什么优缺点吧? 传统关系型数据库,遵循三大范式.即原子性.唯一性.每列与主键直接关联性.但是后来人们慢慢发现,不要把这些数据分散到多个表 ...

  8. Mongodb数据模型

    描述表关系的方式: 方式一:嵌入式 > db.person.find({name:'zjf'}).pretty() { "_id" : ObjectId("592f ...

  9. MongoDB之二基础入门(安装启动)

    mongodb中有三元素:数据库,集合,文档,其中“集合” 就是对应关系数据库中的“表”,“文档”对应“行”. 一. 下载 上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了 ...

随机推荐

  1. 最简单的MFC

    #include <SDKDDKVer.h> #include <afxwin.h> #include <afxext.h> #include <iostre ...

  2. CoreJavaE10V1P3.5 第3章 Java的基本编程结构-3.5 操作符

    最基本的操作为赋值操作,= 即赋值操作符 基本的算术操作为加.减.乘.除取模.除取余数,其对应操作符为 +.-.*./.% 算术操作与赋值操作联合衍生为:+=:-=:*=:/=:%=: 由于处理器硬件 ...

  3. http协议的状态码 403 404 301 302 200 500 502 504 报错显示

    在网站建设的实际应用中,容易出现很多小小的失误,就像MySQL当初优化不到位,影响整体网站的浏览效果一样,其实,网站的常规http状态码的表现也是一样,Google无法验证网站几种解决办法,提及到由于 ...

  4. PyQt5+python3+pycharm开发环境配置

    1.下载PyQt https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.6/PyQt5-5.6-gpl-Py3.5-Qt5.6.0-x32- ...

  5. 【转】SQL Server 2008 事件探查器(SQL SERVER Profiler)

    跟踪数据库sql语句的执行情况.例:一个系统,用到了sql server 数据库,这个系统共有500张表,当用户在前台页面做某一个操作时,比如插入,登录等等,我们想知道此刻是在对哪一张表操作,打开事件 ...

  6. 修改redis端口号

    为redis分配一个8888端口,操作步骤如下:1.$REDIS_HOME/redis.conf重新复制一份,重命名为redis8888.conf.2.打开redis8888.conf配置文件,找到p ...

  7. XTU 1243 2016

    $2016$长城信息杯中国大学生程序设计竞赛中南邀请赛$A$题 循环节. 循环节为$2016$,从数据范围以及题目中的一句话也能间接的体会出应该是有循环节的,并且循环节可能是$2016$. Feel ...

  8. DP! | 不要怂!

    跟一个博客刷: http://blog.csdn.net/cc_again/article/details/25866971 一.简单基础dp 1.递推 HDU 2084 #include <b ...

  9. [Q]如何将图纸打印为黑白的及设置打印样式

    若只是想打印黑白图形,则在“打印样式列表”选择“monchrome.ctb”打印样式即可. 设置其它打印样式:在CAD批量打图精灵主界面下点设置打印样式图标,如下图: 在打开的资源管理器中选择您想要更 ...

  10. vultr vps新增reserved IPs功能,保留服务器原有IP

    高性价比海外vps品牌vultr vps宣布一项新功能叫“reserved IPs”,顾名思义是帮助你保留服务器IP地址,以备后用. 这个需求是因为用户经常新建.删除一个vps服务器,默认分配的是随机 ...