1

mongo数据模型

文档与集合

文档是mongo的核心概念,本质是是一种BSON(Binary JSON)数据,BSON是一种类JSON的二进制数据,可以在为JSON基础上添加了一些新的数据类型,包括日期、Int32、Int64,常被作为数据存储和网络数据交换的格式,缺点是空间利用率并不理想(存在键名的冗余信息)。BSON由多组键值对组成,具有轻量性、可遍历性和高效性,其中可遍历性是mongo将其作为数据存储的主要原因

mongo和关系型数据库可以按照下图进行理解:

mongo和rdb的对照

mongo文档的注意事项 

  1. 写操作的原子性限制在文档级别

  2. 单个文档的存储大小限制为16MB(防止读取大文档时对内存和网络造成压力)

  3. mongo会尽量保持插入文档时键值对的顺序,但是更新操作可能会改变字段的顺序,因为可能会导致文档内存的重新分配

文档键的命名

  1. _id是系统保留关键字,默认主键,集合中唯一,一旦生成不可更改

  2. 键名不能包含\0或者空字符,因为mongo依靠此判断键的结尾

  3. 键名不能以$开头,不能包含.

  4. 键名区分大小写

  5. _开头的键通常是保留的,并不推荐使用

集合

把一组相关文档放到一起就组成了集合。我们可以将集合类比成RDB中的表,文档则是一条条的记录。在mongo中集合也是自由的,一个集合中的文档可以是不同的。mongo提供了一些特殊功能的集合:capped collection(固定集合),system.indexessystem.namespaces(这两个用于存储集合的元数据)。集合名字中的.表示子集合。

数据库

多个文档组成集合,多个集合组成数据库。一个mongo instance可以承载多个数据库,每个DB拥有不同的权限,不同的DB也可以分散在DB的各个目录(启动时需要directoryperdb指定),数据库名最终会变成文件系统中的文件,mongo中保留了一些数据库:adminlocal(本地数据库,不会被复制)、config(本地使用保存分片信息)。

命名空间

把数据库名添加到集合名前面,中间用点号连接,得到集合的全限定名就是namespace,例如:test_db.testcollection
需要说明的是,.还可以出现在集合名字中,例如:test_db.blog.poststest_db.blog.authors,我们可以将postsauthors看做是blog集合的子集合

mongo数据类型

mongo支持的数据类型非常丰富:
mongo数据类型
如下的命令db.collection.find({name:{$type:2}})将查找name字段为String类型的文档。由于mongoshell默认是javascript shell,所以我们插入的数据是double类型,如果需要插入int类型可以使用NumberInt(x).
ObjectId可以保证在分布式环境下同一集合唯一,由24个16进制字符组成,总共需要12字节存储空间:
mongo-object-id

> x = ObjectId()
ObjectId("562f741336aeb42b3fdef2da")
> x.getTimestamp()
ISODate("2015-10-27T12:54:43Z")
> x.valueOf()
562f741336aeb42b3fdef2da

mongo内嵌文档

文档可以作为键的值,这样的文档叫做内嵌文档。内嵌文档使得数据不用保存成扁平结构的键值对,从而使数据组织方式更加自然

mongo基本操作

关闭mongoDB

# 切换到admin,执行db.shutdownServer()
use admin
db.shutdownServer()

# 先查看mongo的pid,然后kill此进程
ps -ef | grep mongodb
kill 25414

mongo常用命令

show dbs          # 查看数据库
show collections  # 查看所有的集合
use test          # 切换数据库(需要的时候自己创建)
db.dropDatabase() # 删除当前使用的数据库

向mongo中写入数据

mongo中的collection类似于RDBMS中的表,写入数据的语法:

db.collection_name.insert(BSON)

插入的时候如果没有指定_id字段,则mongo会自动生成一个id,我们也可以自己指定_id,该字段默认创建索引。除了insert我们也可以使用save,当文档不存在时save函数调用insert执行插入,当文档存在时save函数执行update进行更新操作

我们可以使用js语法插入多条数据

for(var i = 0;i < 10;i++) db.test_collection.insert({x:i})

我们也可以使用bulk函数将多个数据的更新(增删改)操作放到一个待执行的列表中批量执行,bulk分为顺序(一个操作失败剩下的不会执行)执行的bulk和并行(随机性)执行的bulk。

# 初始化bulk
# 初始化bulk
var bulk = db.test_collection.initializeUnorderedBulkOp() # 并行bulk
#db.test_collection.initializeOrderedBulkOp()             # 顺序bulk

# 向bulk中添加数据更新操作
bulk.insert({name:"xiaomi",age:1});
bulk.insert({name:"huawei",age:2});
bulk.insert({name:"zte",age:3});

# 执行更新
var result = bulk.execute();
printjson(result);

# 测试执行结果
var cursor = db.test_collection.find({});
print(cursor.toArray());

在mongo中查找数据

查找数据使用find()命令

db.colletion_name.find() # 查询条件为空返回所有文档
db.collection.findOne()  # 返回第一条数据

我们可以对find操作产生的文档进行额外的统计

db.test_collection.find().count()      # 计数
db.test_collection.find().skip(3)      # 跳过前3条数据
db.test_collection.find().limit(5)     # 仅返回前5条数据
db.test_collection.find().sort({x:-1}) # 以x字段降序排列,如果指定1则表示升序排列

以上的操作可以连缀进行,例如:

db.test_collection.find().skip(3).limit(4).sort({x:1})

更新mongo数据

更新数据使用update,需要指明查询条件和更新条件,例如:

更新一个文档对象

db.test_collection.update({x:2},{x:'hello'})

注意:与RDBMS不同的是mongo默认更新一条数据,这样设计的初衷是防止不小心更新多条数据。如果需要更新多条数据参见更新多条数据

更新一个文档的部分字段

db.test_collection.insert({x:100,y:200})         # { "x" : 100, "y" : 200 }
db.test_collection.update({x:100},{$set:{y:-1}}) # 仅更新y字段,{ "x" : 100, "y" : -1 }

# 如果使用以下的语法将会完全替代原来的数据
db.test_collection.update({x:100},{y:-1})        # { "y" : -1 }

更新一个文档,如果文档不存在则创建之

只需要指定update的第三个参数为true即可。

db.test_collection.update({x:1111},{x:9999},true) # 数据{ x:1111 }原先并不存在

批量更新文档

实际上更新数据的update语句还有<span id="multi-update">第4个参数</span>表示更新多条数据。

for(var i = 0;i < 3;i++) db.test_collection.insert({x:888})  # 写入3个文档
db.test_collection.update({x:888},{$set:{x:999}},false,true) # 更新3个文档

更新修改器

更新修改器是原子的高效部分更新。

$inc 修改器

例如网站计数器的文档结构如下:

{
    "_id":ObjectId("5631a71da4d39f72ef3bbaa4"),
    "url":"www.url.com",
    "pageviews":52
}

当有人访问页面的时候可以用$inc修改器增加pageviews的值:

db.web_analytics.update({"url":"www.url.com"},{$inc:{"pageviews":1}})
$set修改器

设置一个键的值,若不存在自动创建,可以用于更新部分字段或者增加键值对。
monog-set

 数组修改器

如果指定的键已经存在,$push会向已有的数组末尾追加一个元素,否则创建一个新的数组。我们也可以使用$addToSet来避免重复。
mongo-push-addToSet.png

删除数据

数据的删除使用remove,与查找数据相比,删除数据有以下不同:

  • 必须传递参数

  • 默认删除所有匹配数据

db.test_collection.remove({x:999})
db.test_collection.remove({}) # 清空集合中的所有文档(保留集合本身和索引)

清除某个集合中(table)

db.test_collection.drop()
show tables                 # 看不到任何数据

切记:删除操作是永久性的,不可撤销,不可恢复。

索引

查看索引

db.test_collection.getIndexes() # 查看索引

mongo索引

创建索引

创建索引

索引的分类

  • _id索引,默认插入数据的时候建立索引

  • 单键索引,该类型的索引需要使用ensureIndex创建

  • 多键索引,和单键索引不同的是值可以是多个记录,例如数组

  • 复合索引,查询条件不止一个。
    复合索引

  • 过期索引(TTL),一段时间后过期,索引过期后相应的数据也被删除。比较适合存储一些一段时间后失效的数据,例如用户登录信息,用户日志。过期索引有以下的限制:

    • 过期索引指定字段的值必须是指定的时间类型,例如ISODate或ISODate数组,不能使用timestamp否则不能自动删除

    • 如果指定了ISODate数组,则按照最小的时间进行删除

    • 过期索引不能是复合索引,原因很简单:不能同时指定多个过期时间

    • 删除的时间是不精确的(删除进程由后台每60s执行一次,而且每次删除也需要时间)
      过期索引

  • 全文索引(文本索引)。一个数据集中只能创建一个全文索引。对字符串或者字符串数组创建全文可搜索的索引,适用情况:

{ author:"", title:"", article:"" }
# 建立方法
db.arcicles.ensureIndex({key:"text"})
db.arcicles.ensureIndex({key_1:"text",key_2:"text"})
db.arcicles.ensureIndex({"$**":"text"})

# 使用全文索引进行查询
db.arcicles.find({$text:{$search:"coffee"}})
db.arcicles.find({$text:{$search:"aa bb cc"}})  # 包含aa或bb或cc
db.arcicles.find({$text:{$search:"aa bb -cc"}}) # 包含aa或bb 但不包含cc
db.arcicles.find({$text:{$search:"\"aa\" \"bb\" \"cc\""}}) # 同时包含aa、bb、cc的需要将字符串包裹起来

全文检索相似度查询$meta,写在查询条件后面可以返回结果的相似度,与sort一起使用可以达到很好的实用效果。我们可以模拟一个类似于百度的搜索

{score:{$meta:"textScore"}}

全文检索相似度查询

  • 全文索引的限制:

    • 每次查询只能使用一次$text

    • $text不能出现在$nor查询中

    • 查询中使用了$text,hint(可强制指定索引)将不再起作用

    • mongoDB全文检索不支持中文

  • 索引属性:
    索引属性

地理位置索引

将一些点的位置存储在mongo中,创建索引后可以按照位置来找到其他点。地理位置索引有2个子分类:

  • 2D索引,平面

  • 2Dsphere索引,球面

2D索引

查找方式:
  • 查找距离某个点一定距离的点

  • 查找包含在某个区域内的点

2D索引的创建方式
db.collection.ensureIndex({"w":"2d"})

位置的表示方式是经纬度[经度,纬度],取值范围经度[-180,180],纬度[-90,90]

2D索引查询的方式
  • $near:查询距离某个点最近的点(默认返回100个,可以使用$maxDistance进行限制)

  • $geoWithin:查询某个形状内的点,mongo中形状的表现方式有:

    • $box。矩形,使用{$box:[[<x1>,<y1>],[<x2>,<y2>]]}表示。

    • $center。圆形,使用{$center:[[<x>,<y>],r]} 表示.

    • $polygon。多边形,使用{$polygon:[[<x1>,<y1>],[<x2>,<y2>],[<x3>,<y3>]]}表示。

# 创建2D索引
db.location_collection.ensureIndex({"w":"2d"})

# 插入数据
db.location_collection.insert({w:[1,1]})
db.location_collection.insert({w:[1,2]})
db.location_collection.insert({w:[3,2]})
db.location_collection.insert({w:[90,50]})

# 使用$near查询距离(1,1)最近的点
db.location_collection.find({w:{$near:[1,1]}}) # 返回4条数据

# 使用$maxDistance进行限制
db.location_collection.find({w:{$near:[1,1],$maxDistance:10}}) # 返回前3条数据

# 查询位于[0,0]-[3,3]矩形内的点
db.location_collection.find({w:{$geoWithin:{$box:[[0,0],[3,3]]}}}) # 返回前3条数据

# 查询圆内的点
db.location_collection.find({w:{$geoWithin:{$center:[[0,0],2]}}}) # 1条结果

# 位于多边形内的点
db.location_collection.find({w:{$geoWithin:{$polygon:[[0,0],[1,1],[2,5]]}}}) # 2条
geoNear查询

该查询可以看做是$near查询的进化版,geoNear查询使用runCommand命令

db.runCommand({
    geoNear:<collection>,
    near:[x,y],
    minDistance:(对2D索引无效),
    maxDistance:
    num:
    ...
})

geoNear查询

2Dsphere索引

2Dsphere索引是球面地理位置索引,创建方式:

db.collection.ensureIndex({"w":"2dsphere"})

位置的表示方式不再是经度和纬度,而是一种名为GeoJSON的数据结构,GeoJSON用来描述一个点、一条直线、多边形等形状。格式如下:

{type:"",coordinates:[<coordinates>]}

2Dsphere索引的查询方式和2d索引的查询方式类似,支持$minDistance$maxDistance

索引的mongo性能分析

索引的利弊

  • 利:加快索引相关的查询

  • 弊:增加磁盘空间的消耗,降低写入性能

评判索引构建情况

  1. mongostat工具
    mongostat是查看mongo运行状态的程序,我们可以使用mongostat --help查看mongo的使用帮助,flushes表名将内存中的数据刷入硬盘(越高则性能越低);faults换页,越高性能越低;ids miss查询没有命中索引的比例;qr|qw读写队列,较高时性能明显下降。ids miss的提高会导致qr的提高导致明显的性能问题。

  2. profile集合
    profile集合是mongo的慢操作日志
    mongo-profile
    当mongo的级别为0的时候不会记录任何操作,2则记录所有的操作。我们的操作就是被记录在system.profile集合中。
    db.system.profile
    profile主要用于测试阶段,生产环境中并不推荐profile

  3. mongo日志
    在mongo的配置文件mongod.conf中可以配置verbose来记录日志的详细程度。

  4. explain分析
    在特定查询的时候可以指定explain()来查看特定查询的情况:
    使用explain查看特定查询的性能

mongo安全

mongo开启权限认证:

mongod.conf中配置auth = true开启权限认证。之后重启mongo,查看mongo日志,搜索auth发现已经有了鉴权启动的标志。

创建用户

mongo2.6之后使用createUser命令创建用户,之前的版本只有简化的addUsercreateUser接收的参数:

{
    user:"<name>",
    pwd:"<cleartext password>",
    customData:{<any information>},
    roles:[{role:"<role>",db:"<database>"}]
}

mongo角色

mongo的角色类型有很多,mongo内建的类型有:

  • 数据库角色:readreadWritedbAdmindbOwneruserAdmin

  • 集群角色:clusterAdminclusterManger...

  • 备份角色:backuprestore

  • 特殊权限:DBAdminAnyDatabaseReadAnyDatabase...

  • mongo内置的特殊角色:root--system
    mongo创建用户并授予角色
    使用mongo -u test_user -ptest_user_admin命令登录mongo。
    mongo-role

我们可以使用createRole命令创建自定义角色。
创建自定义角色


雨碎江南
1.8k 声望32 粉丝

风风雨雨寒寒暖暖处处寻寻觅觅,