MongoDB学习笔记(一)

来源:互联网 发布:无法安装java 编辑:程序博客网 时间:2024/06/06 01:10

虽然在TTMS这个项目中,我已经使用了MongoDB数据库,但是我觉得我还不是很了解它,所以在项目结束的第一天,我打算重新认识MongoDB

一.认识MongoDB

NoSQL

NoSQL(Not Only SQL,非关系型数据库),主要特点是非关系型的,分布式的,开源的,水平可扩展的。

这里写图片描述

关系型数据库,我目前只使用过MySQL,SQL Server,但是关系型和非关系型两种在使用的时候的差别就比较明显了

NoSQL特点:

  • 可以处理超大量的数据
  • 它运行在便宜的PC服务器集群上
  • 它击碎了性能瓶颈
  • 它没有过多的操作
  • 它的支持者源于社区

这里写图片描述

MongoDB特点及功能

MongoDB最大的特点是支持的查询语言非常强大,其语法有点类似关系数据库单表查询的绝大部分功能,而且支持对数据建立索引,它是一个面向集合的,模式自由的文档型数据库

  • 面向集合

    数据被分组存储在数据集中,被称为一个集合,每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档,集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定义任何模式(schema)

  • 模式自由

    意味着对于存储在MongoDB数据库中的文件,你不需要知道它的任何结构定义,这点我在使用过程中也深有体会,保存在MongoDB数据库中某个集合下的文档它的键可以是不一样的

    {“welcome” : “Beijing”}
    {“age” : 25}

  • 文档型

    我们存储的数据是键-值对的集合,键是字符串,值可以是数据类型集合里的任何类型,包括数组和文档,我们把这个数据格式称为“BSON”,即“Binary serialized document notation”

MongoDB的特点:

  • 面向集合存储,易于存储对象类型的数据
  • 模式自由
  • 支持动态查询
  • 支持完全索引,包含内部对象
  • 支持查询
  • 支持复制和故障恢复
  • 使用高效的二进制数据存储,包括大型对象(如视频等)
  • 自动处理碎片,以支持云计算层次的可扩展性
  • 支持Python,PHP,Ruby,Java,C,C#,JavaScript,Perl及C++语言的驱动程序,社区也提供了对Erlang及.NET等平台的驱动程序
  • 文件存储格式为BSON(一种JSON的扩展)
  • 可通过网络访问

MongoDB的功能

  • 面向集合的存储:适合存储对象及JSON形式的数据
  • 动态查询:MongoDB支持丰富的查询表达式,查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组
  • 完整的索引支持,支持文档内嵌套对象和数组,MongoDB的查询优化器会分析查询表达式,生成一个高效的查询计划
  • 查询监视:MongoDB包含一系列的监视工具用于分析数据库操作的性能
  • 复制和自动故障转移:MongoDB数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制,复制的主要目标是提供冗余及自动故障转移
  • 高效的传统存储方式:支持二进制数据及大型对象(如照片或图片)
  • 自动分片以支持云级别的伸缩性:自动分片功能支持水平的数据库集群,可动态添加额外的机器

MongoDB适用场合

  • 网站数据:MongoDB非常适合实时的插入,更新和查询,并具备网站实时数据存储所需的复制及高度伸缩性
  • 缓存:由于性能很高,MongoDB也适合作为信息基础设施的缓存层,在系统重启之后,由MongoDB搭建的持久性缓存层可以避免下层的数据源过载
  • 大尺寸,低价值的数据,使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储
  • 高伸缩性的场景:MongoDB非常适合数十或数百台服务器组成的数据库,MongoDB的高线路图中已经包含对MapReduce引擎的内置支持
  • 用于对象及JSON数据的存储:MongoDB的BSON数据格式非常适合文档化格式的存储及查询

MongoDB是一个可移植的数据库,在主流的操作系统上都可以使用,及跨平台特性,MongoDB Shell是MongoDB自带的交互式JavaScript Shell,是对MongoDB执行操作和管理的交互式环境

二.MongoDB入门

体系结构

一个运行着的MongoDB可以看作一个MongoDB Server,该Server由实例和数据库组成。一般情况下,一个MongoDB。一般情况下,一个MongoDB Server机器上包含一个实例和多个与之对应的数据库,但是在特殊情况下,如特殊的情况下,如硬件投入成本有限或特殊的应用需求,也允许一个Server机器上有多个实例和数据库

MongoDB中一系列物理文件(数据文件、日志文件等)的集合或与之对应的逻辑结构(集合、文档等)称为数据库。简单来说,数据库是由一系列与磁盘有关的物理文件组成的。

数据逻辑结构

MongoDB数据逻辑结构是面向用户的,用户使用MongoDB开发应用程序使用的是逻辑结构,MongoDB逻辑结构是一种层次结构,由文档(document)、集合(collection)、数据库(database)三部分组成,它们之间三部分的关系如下:

  • MongoDB的文档相当于关系数据库中的一条记录
  • 多个文档组成一个集合,相当于关系数据库中的表
  • 多个集合逻辑上组织在一起就是数据库
  • 一个MongoDB实例支持多个数据库

数据库的每张表都对应一个命名空间,每个索引也有对应的命名空间,MongoDB 内部有预分配空间的机制,每个预分配的文件都用 0 进行填充,由于有了这个机制, MongoDB 始终保持额外的空间和空余的数据文件,从而有效避免了由于数据暴增而带来的磁盘压力过大的问题

由于表中数据量的增加,数据文件每新分配一次,它的大小都会是上一个数据文件大小的 2倍,每个数据文件最大 2G。这样的机制有利于防止较小的数据库浪费过多的磁盘空间,同时又能保证较大的数据库有相应的预留空间使用。

数据类型

MongoDB中的文档可以理解为JSON类型的对象,MongoDB也支持很多额外的数据结构

这里写图片描述

number类型:包括四字节整数、八字节整数、八字节浮点数

date类型:表示日期类型,使用的时候,要new Date(),而不是Date() ,后者返回的是一个字符串,而不是一个Date对象,通过MongoDB Shell查看可得到下面的结果

这里写图片描述

Shell 里用本地区域设置来显示时间,但在数据库里只是记录从纪元开始到现在所过去的毫秒数,所以并没有把区域设置保存进去

array:数组的值可以进行有序处理,类似列表、堆栈、队列;也可以无序处理,类似于集合。这个数组和JavaScript的数组还是有些类似的,比如数组里面的各个元素的类型可以是不一致的,MongoDB还允许数组内部元素的原子更新

embedded document:嵌套文档就是将一个key的值表示为另外一个文档,这样可以让document看起来比一个平面的结构更加自然

操作数据库

如下面所示,通过Shell进行了插入记录和查看记录的操作

1.插入操作

这里写图片描述

这里写图片描述

从此也可以看出:

  • MongoDB是不需要预先创建一个集合,在第一次插入数据时就会自动创建

  • 在文档中可以存储任何结构的数据,但在实际应用中存储的还是相同类型文档的集合,此特征很灵活,不需要类似alter table语句来修改数据结构

  • 每次插入数据时,集合中都会有一个ID(_id)

    可以用for循环来插入数据:

这里写图片描述

MongoDB支持的数据类型中,_id是其自有产物,存储在MongoDB集合中的每个文档(document)都有一个默认的主键_id,这个主键名称是固定的,它可以是MongoDB支持的任何数据类型,默认是ObjectId。在关系数据库Schema设计中,主键大多数是数值型,在MongoDB在设计之初就定位于分布式存储系统,所以它不支持自增主键。

2.查询操作

find的使用:

这里写图片描述

游标风格的输出,hasNext()函数用于判断是否还有数据,如果有则调用Next()函数将数据取出来,当使用的是JavaScript Shell时就可以使用forEach循环输出数据,但forEach()必须定义一个函数供每个游标元素调用,如下面:

这里写图片描述

也可以把游标当成是数组来使用:

这里写图片描述

使用游标的时候要注意占用内存的问题,特别是很大的游标对象,有可能会内存溢出,所以应该用迭代的方式来输出,下面就将游标转换成真实的数组对象类型:

这里写图片描述

3.条件查询

> db.things.find({num: 4}).forEach(printjson)
{ “_id” : ObjectId(“593ce88c1991bcbd477f7a02”), “num” : 4 }

也可以指明只输出某列的值:

> db.things.find({x : 1}, {y:true}).forEach(printjson)
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “y” : 2 }

findOne

为了避免游标可能带来的开销,提供了一个findOne()函数,这个函数和find()函数一样,不过它返回的是游标中的第一条数据,或者返回null,即空数据

> printjson(db.things.findOne({y : 3}))
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3 }

limit限制结果集数量

在需要限制结果集的长度,可以调用limit方法

> db.things.find().limit(3)

{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a14”), “x” : 3, “y” : 4 }

这个可以解决性能问题,通过限制条数来减少网络传输,比如分页

4.修改记录

> db.things.update({x : 3}, {$set:{x : 100}})
WriteResult({ “nMatched” : 1, “nUpserted” : 0, “nModified” : 1 })
> db.things.findOne({x:100})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a14”), “x” : 100, “y” : 4 }

5.删除记录

删除表的记录可以通过remove方法

> db.things.remove({x: 100})
WriteResult({ “nRemoved” : 1 })
> db.things.findOne({x:100})
null


三.高级查询

查询操作符

1.条件查询符

常用的条件查询符:<( $lt) 、<=($lte)、>($gt )、>=($gte)

db.things.find({“x”: {$gt: 3}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }
db.things.find({x:{\$lt: 5}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

3< x < 5

db.things.find({x: {$gt: 3, $lt:5}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

2. $all匹配所有

这个与SQL语法中的in类似,不同的是in只需要匹配括号内的某一个值,而$all必须满足括号中的所有值,如下面的代码所示:

> db.things.find({x: {$all:[1,1]}})

{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2 }

3.$exists判断字段是否存在

此操作符用判断某个字段是否存在

> db.things.find({x : {$exists:true}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

如果查询所有不存在某个字段的记录,只需要将true改为false就可以了

4. null值处理

此操作符用于处理null值,null的处理稍微复杂

> db.things.find({age: null})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

null会找到对应字段age为null值的情况,也可以找到不存在age字段的记录,当只需要找到第一种情况下的文档的时候,只需要用$exists限制一下就好

> db.things.find({age: {$in:[null], “$exists”:true}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3, “age” : null }

5.$mod取模运算

> db.things.find({age : {$mod : [10, 1]}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2, “age” : 11 }

6.$ne不等于

此运算符用于布尔不等于的运算,例如,age不等于11

> db.things.find({age: {$ne: 11}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3, “age” : null }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

7.$in包含

此操作符与SQL标准语法的用途一样,即要查询的数据在一个特定的取值范围内

> db.things.find({x: {$in : [1, 2, 3]}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2, “age” : 11 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3, “age” : null }

8.$nin不包含

此操作符与SQL标准语法的用途是一样的,即要查询的数据在一系列枚举的范围外

> db.things.find({x: {$nin : [1, 2, 3]}})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }

9.$size匹配数组元素个数

此操作符用于统计数组中的元素个数

> db.things.find({label : {$size : 3}})
{ “_id” : ObjectId(“593d33bb9f76736827f8b1ec”), “label” : [ 1, 2, 4 ] }

10.count查询记录条数

此操作符用于统计记录的条数

> printjson(db.things.find().count())
4

11.skip限制返回记录的条数

skip(n).limit(m),从第n条开始,返回m条数据

> db.things.find().skip(1).limit(3)
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3, “age” : null }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }
{ “_id” : ObjectId(“593d33bb9f76736827f8b1ec”), “label” : [ 1, 2, 4 ] }

12.sort排序

此操作符用于将结果集排序,例如,以x降序排序

> db.things.find().sort({x:-1})

{ “_id” : ObjectId(“593cf4401991bcbd477f7a15”), “x” : 4, “y” : 5 }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a13”), “x” : 2, “y” : 3, “age” : null }
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2, “age” : 11 }
{ “_id” : ObjectId(“593d33bb9f76736827f8b1ec”), “label” : [ 1, 2, 4 ] }

升序排列,将-1改为1就可以了

13.distinct去掉重复值

在表中可能会包含重复值,distinct操作符用于过滤掉多余的重复记录,对于重复记录只保留一条,通常用 它来返回不重复的记录的条数,但是此操作在数据量大的表里比较耗时,慎用

x不重复的记录有三条

> db.things.distinct(“x”)
[ 1, 2, 4 ]

14.group分组统计

group by子句主要用于对where得到的结果进行分组,也就是说,它在where子句之后执行,对经过where子句筛选后的结果按照某些列进行分组,之后进行相对应的处理工作

select a, b, sum(c)  csum from coll where active=1 group by a,b
db.coll.group({    {key : {a:true, b:true}},     cond: {active : 1},     reduce : function(obj, prev) {                prev.csum += obj.c              },     initial : {csum : 0}})

参数说明:

  • key : 要分组的列,如上面的a,b
  • cond: 分组条件,可以理解为where后面的 active=1
  • reduce : 分组计算的方法,sum(c)
  • initial: 分组计算的初始值,csum的值从0开始计算

查询语法

MongoDB最大的特点是,它支持的查询语言非常强大,其语法类似于面向对象的查询语言,不但可以实现关系数据库查询的大部分功能,而且还支持对数据建立索引

1.数组内容的查询

> db.things.find({x : 1})
{ “_id” : ObjectId(“593cf4401991bcbd477f7a12”), “x” : 1, “y” : 2, “age” : 11 }

2.内嵌文档的查询

数据库中存在这样一个文档

{    "_id" : ObjectId("593d44099f76736827f8b39e"),    "authorA" : {        "name" : "Amy",        "age" : 29    },    "authorB" : {        "name" : "Lindy",        "age" : 30    }}

当我们想要查询A作家的名字的时候

> db.things.find({“authorA.name”: “Amy”})
{ “_id” : ObjectId(“593d44099f76736827f8b39e”), “authorA” : { “name” : “Amy”, “age” : 29 }, “authorB” : { “name” : “Lindy”, “age” : 30 } }

查询内嵌属性需要加上引号“”

3.正则表达式匹配

模糊查询,匹配查询name=“v*”开头的文档

> db.things.find({name : /^v.*/})

{ “_id” : ObjectId(“593d463f9f76736827f8b3dc”), “name” : “vamous” }

不是以name=“v*”开头的文档,查询语句向下面一样:

db.things.find({name : {$not: /^v.*/}})

4.$where查询

$where查询是指采用类似SQL的where语句来进行查询,但命令中并不包含where关键字

db.cl.find("this.a > 3"),这种情况下,会将BSON转换为一个JavaScript对象,所以$where查询会比一般的查询慢很多

联合查询

通过连接运算可以实现多个表联合查询,可以用两种方法去应付这类需求:简单手工关联和DBRef方式

加入存在两个集合:一个是用户表、一个是帖子表,通过用户名获得该用户发表过的帖子

首先取得了用户对象”u”,然后通过对象u来取得帖子列表

var u = db.getCollection('user').find({name: "vamous"});for(var p = db.getCollection('postings').find({"name": u._query.name}); p.hasNext();){    printjson(p.next().title)}

DBRef方式关联

就是说在两个collection之间定义一个关联,比如把collectionB的“_id“列的值存在collectionA的一个列中,然后通过collectionA这个列中所存的值在collectionB中找到对应的记录

1.在user这个集合中找到name=vamous的文档

u = db.getCollection('user').find({"name": "vamous"})[0];printjson(u._id);  //得到objectId//ObjectId("593d4e1d1991bcbd477f7a17")

2 在postings这个集合中做关联

db.getCollection('postings').insert({title : "vue", user : new DBRef('user', u._id)})
  1. 通过帖子查找用户
db.getCollection('postings').find({title : "vue"})[0].user.fetch()

DBRef就是从文档的一个属性指向另一个文档的指针


游标和存储过程

游标是系统为用户开设的一个数据缓冲区,用来存放SQL语句的执行结果,游标提供了一种对从表中检索出来的数据进行操作的灵活手段,hasNext判断是否还有数据,找到的数据通过next方法提取出来,=

for(var p = db.getCollection('postings').find({"name": u._query.name}); p.hasNext();){    printjson(p.next().title)}
db.getCollection('postings').find({"name": u._query.name}).forEach(function(u) { .... })

存储过程:存储过程可有一个应用调用来执行,并且允许用户声明变量,MongoDB的存储过程存储在db.system.js表中

db.system.js.save({_id: "addNumbers", value: function(x, y) {    //保存  return x + y;}}) db.system.js.find()        //查找db.system.js.eval('addNumber(3, 4.2)')         //调用

通过执行db.eval命令调用该方法的接口,也可以把存储过程的逻辑直接放在db.eval()的参数里直接调用

存储过程的优点:

  • 存储过程利用流程控制语句编写,有很强的灵活性,可以完成复杂的判断和运算
  • 通过存储过程可以使相关的动作同时发生,从而维护数据库的完整性
  • 降低网络的通信量

高级更新

更新命令

两个更新命令,一个是update,另外一个是save。

update命令:update(criteria, objNew, upsert, multi)

分别为查询条件(where语句之后的东西),更新的对象和一些更新的操作符(如$$inc等,可以理解为set之后的内容),如果不存在update的记录是否插入(true为插入,false为不插入),默认为false(只找到第一条,true则代表着找到的都更新)

db.things.update({x: 1}, {$set: {x: 88}},true, false)

save命令:save(obj)

obj为单个文档,在collection里如果存在相同的”_id”的记录,MongoDB就会把obj对象代替为collections已经存在的内容

更新操作符

$inc(增加):

db.things.update({x: 88}, {$inc:{x : 2}})

$set(设置为)

db.things.update({x: 1}, {$set: {x: 88}},true, false)

$unset(删除字段)

db.things.update({name:”vamous”}, {$unset: {name: “vamous”}})

这种情况下是删除了这个键值对,文档还是存在的

$push与 $pushAll

{$push: {field: value}},将value追加到field中,fied一定是数组类型,如果field不存在,不增加一个数组类型

{$pushAll: {field : value_Array}},可以追加多个值到一个数组字段中

$pop与 $pull

{$pop: {field : -1}}删除第一个值,为1代表删除最后一个值

{$pull: {field : _value}}从数组中删除一个等于value的值

{$pullAll: {field : value_array}}可以删除数组中的多个值

$rename

{$rename: {old_field_name: new_field_name}}

参考《MongoDB管理与开发精要》