mongo数据模型
文档与集合
文档是mongo的核心概念,本质是是一种BSON(Binary JSON)数据,BSON是一种类JSON的二进制数据,可以在为JSON基础上添加了一些新的数据类型,包括日期、Int32、Int64,常被作为数据存储和网络数据交换的格式,缺点是空间利用率并不理想(存在键名的冗余信息)。BSON由多组键值对组成,具有轻量性、可遍历性和高效性,其中可遍历性是mongo将其作为数据存储的主要原因
mongo和关系型数据库可以按照下图进行理解:
mongo文档的注意事项
写操作的原子性限制在文档级别
单个文档的存储大小限制为16MB(防止读取大文档时对内存和网络造成压力)
mongo会尽量保持插入文档时键值对的顺序,但是更新操作可能会改变字段的顺序,因为可能会导致文档内存的重新分配
文档键的命名
_id
是系统保留关键字,默认主键,集合中唯一,一旦生成不可更改键名不能包含
\0
或者空字符,因为mongo依靠此判断键的结尾键名不能以
$
开头,不能包含.
键名区分大小写
以
_
开头的键通常是保留的,并不推荐使用
集合
把一组相关文档放到一起就组成了集合。我们可以将集合类比成RDB中的表,文档则是一条条的记录。在mongo中集合也是*的,一个集合中的文档可以是不同的。mongo提供了一些特殊功能的集合:capped collection
(固定集合),system.indexes
、system.namespaces
(这两个用于存储集合的元数据)。集合名字中的.
表示子集合。
数据库
多个文档组成集合,多个集合组成数据库。一个mongo instance可以承载多个数据库,每个DB拥有不同的权限,不同的DB也可以分散在DB的各个目录(启动时需要directoryperdb
指定),数据库名最终会变成文件系统中的文件,mongo中保留了一些数据库:admin
、local
(本地数据库,不会被复制)、config
(本地使用保存分片信息)。
命名空间
把数据库名添加到集合名前面,中间用点号连接,得到集合的全限定名就是namespace,例如:test_db.testcollection
。
需要说明的是,.
还可以出现在集合名字中,例如:test_db.blog.posts
、test_db.blog.authors
,我们可以将posts
和authors
看做是blog
集合的子集合
mongo数据类型
mongo支持的数据类型非常丰富:
如下的命令db.collection.find({name:{$type:2}})
将查找name字段为String类型的文档。由于mongoshell默认是javascript shell,所以我们插入的数据是double类型,如果需要插入int类型可以使用NumberInt(x)
.ObjectId
可以保证在分布式环境下同一集合唯一,由24个16进制字符组成,总共需要12字节存储空间:
> 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
修改器
设置一个键的值,若不存在自动创建,可以用于更新部分字段或者增加键值对。
数组修改器
如果指定的键已经存在,$push
会向已有的数组末尾追加一个元素,否则创建一个新的数组。我们也可以使用$addToSet
来避免重复。
删除数据
数据的删除使用remove
,与查找数据相比,删除数据有以下不同:
必须传递参数
默认删除所有匹配数据
db.test_collection.remove({x:999})
db.test_collection.remove({}) # 清空集合中的所有文档(保留集合本身和索引)
清除某个集合中(table)
db.test_collection.drop()
show tables # 看不到任何数据
切记:删除操作是永久性的,不可撤销,不可恢复。
索引
查看索引
db.test_collection.getIndexes() # 查看索引
创建索引
索引的分类
_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:
...
})
2Dsphere索引
2Dsphere索引是球面地理位置索引,创建方式:
db.collection.ensureIndex({"w":"2dsphere"})
位置的表示方式不再是经度和纬度,而是一种名为GeoJSON的数据结构,GeoJSON用来描述一个点、一条直线、多边形等形状。格式如下:
{type:"",coordinates:[<coordinates>]}
2Dsphere索引的查询方式和2d索引的查询方式类似,支持$minDistance
和$maxDistance
索引的mongo性能分析
索引的利弊
利:加快索引相关的查询
弊:增加磁盘空间的消耗,降低写入性能
评判索引构建情况
mongostat工具
mongostat是查看mongo运行状态的程序,我们可以使用mongostat --help
查看mongo的使用帮助,flushes
表名将内存中的数据刷入硬盘(越高则性能越低);faults
换页,越高性能越低;ids miss
查询没有命中索引的比例;qr|qw
读写队列,较高时性能明显下降。ids miss
的提高会导致qr
的提高导致明显的性能问题。profile集合
profile集合是mongo的慢操作日志
当mongo的级别为0的时候不会记录任何操作,2则记录所有的操作。我们的操作就是被记录在system.profile
集合中。
profile主要用于测试阶段,生产环境中并不推荐profilemongo日志
在mongo的配置文件mongod.conf
中可以配置verbose
来记录日志的详细程度。explain分析
在特定查询的时候可以指定explain()
来查看特定查询的情况:
mongo安全
mongo开启权限认证:
在mongod.conf
中配置auth = true
开启权限认证。之后重启mongo,查看mongo日志,搜索auth
发现已经有了鉴权启动的标志。
创建用户
mongo2.6之后使用createUser
命令创建用户,之前的版本只有简化的addUser
。createUser
接收的参数:
{
user:"<name>",
pwd:"<cleartext password>",
customData:{<any information>},
roles:[{role:"<role>",db:"<database>"}]
}
mongo角色
mongo的角色类型有很多,mongo内建的类型有:
数据库角色:
read
、readWrite
、dbAdmin
、dbOwner
、userAdmin
。集群角色:
clusterAdmin
、clusterManger
...备份角色:
backup
、restore
特殊权限:
DBAdminAnyDatabase
、ReadAnyDatabase
...mongo内置的特殊角色:
root
、--system
使用mongo -u test_user -ptest_user_admin
命令登录mongo。
我们可以使用createRole
命令创建自定义角色。