基础
- 概念
database, collection, document, field, index, primary key
> show bds
# admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这 个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
# local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
# config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
> use db_name
> db.dropDatabase()
> dbname.system.*
> db.createCollection(name, options)
> db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } ) #创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个
> db.collection.drop()
> show collections
> db.mycol2.insert({"name" : "MongoDB"}) # insert 自动创建集合
> db.col.find()
> document=({title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: '菜鸟教程',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
});
> db.col.insert(document)
> db.collection.insertOne({"a": 3})
> db.collection.insertMany([{"b": 3}, {'c': 4}])
> var arr = [];
for(var i=1 ; i<=20000 ; i++){
arr.push({num:i});
}
> db.numbers.insert(arr);
# 插入文档你也可以使用 db.col.save(document) 命令。如果不指定 _id 字段 save() 方法类似于 insert() 方法。如果指定 _id 字段,则会更新该 _id 的数据。
- update
> db.col.update( { "count" : { $gt : 1 } } , { $set : { "test2" : "OK"} } ); //只更新第一条记录
> db.col.update( { "count" : { $gt : 3 } } , { $set : { "test2" : "OK"} },false,true ); //全部更新> > db.col.update( { "count" : { $gt : 4 } } , { $set : { "test5" : "OK"} },true,false ); //只添加第一条
> db.col.update( { "count" : { $gt : 5 } } , { $set : { "test5" : "OK"} },true,true ); //全部添加进去
> db.col.update( { "count" : { $gt : 15 } } , { $inc : { "count" : 1} },{upsert:false},{multi:true} ); //全部更新
> db.col.update( { "count" : { $gt : 10 } } , { $inc : { "count" : 1} },false,false ); //只更新第一条记录
> db.collection.save(
<document>,
{
writeConcern: <document>
}
)
> db.col.find().pretty()
> use test
> db.test_collection.insert( [
{"name":"abc","age":"25","status":"zxc"},
{"name":"dec","age":"19","status":"qwe"},
{"name":"asd","age":"30","status":"nmn"},
] )
> db.test_collection.updateOne({"name":"abc"},{$set:{"age":"28"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.test_collection.find()
{ "_id" : ObjectId("59c8ba673b92ae498a5716af"), "name" : "abc", "age" : "28", "status" : "zxc" }
{ "_id" : ObjectId("59c8ba673b92ae498a5716b0"), "name" : "dec", "age" : "19", "status" : "qwe" }
{ "_id" : ObjectId("59c8ba673b92ae498a5716b1"), "name" : "asd", "age" : "30", "status" : "nmn" }
> db.test_collection.updateMany({"age":{$gt:"10"}},{$set:{"status":"xyz"}})
{ "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }
> db.test_collection.find()
{ "_id" : ObjectId("59c8ba673b92ae498a5716af"), "name" : "abc", "age" : "28", "status" : "xyz" }
{ "_id" : ObjectId("59c8ba673b92ae498a5716b0"), "name" : "dec", "age" : "19", "status" : "xyz" }
{ "_id" : ObjectId("59c8ba673b92ae498a5716b1"), "name" : "asd", "age" : "30", "status" : "xyz" }
WriteConcern.NONE:没有异常抛出
WriteConcern.NORMAL:仅抛出网络错误异常,没有服务器错误异常
WriteConcern.SAFE:抛出网络错误异常、服务器错误异常;并等待服务器完成写操作。
WriteConcern.MAJORITY: 抛出网络错误异常、服务器错误异常;并等待一个主服务器完成写操作。
WriteConcern.FSYNC_SAFE: 抛出网络错误异常、服务器错误异常;写操作等待服务器将数据刷新到磁盘。
WriteConcern.JOURNAL_SAFE:抛出网络错误异常、服务器错误异常;写操作等待服务器提交到磁盘的日志文件。
WriteConcern.REPLICAS_SAFE:抛出网络错误异常、服务器错误异常;等待至少2台服务器完成写操作。
> db.col.update({"_id":"56064f89ade2f21f36b03136"}, {$set:{ "test2" : "OK"}}) #数据存在则更新
> db.col.find()
> db.col.update({"_id":"56064f89ade2f21f36b03136"}, {$unset:{ "test2" : "OK"}})
> db.col.find()
> db.collection.update({'title':'MongoDB 教程'}, {$setOnInsert: {'title':'MongoDB'}}) #数据存在时不进行操作
- find
# db.collection.find(query, projection)
> db.col.find({"by":"mongodb"}).pretty() # =
> db.col.find({"likes":{$lt:50}}).pretty() # <
> db.col.find({"likes":{$lte:50}}).pretty() # <=
> db.col.find({"likes":{$gt:50}}).pretty() # >
> db.col.find({"likes":{$gte:50}}).pretty() # >=
> db.col.find({"likes":{$ne:50}}).pretty() # !=
> db.col.find({"likes": {$gt:50}, $or: [{"by": "MongoDB"},{"title": "MongoDB 教程"}]}).pretty()
db.collection.find(query, {title: 1, by: 1}) # inclusion模式 指定返回的键,不返回其他键
db.collection.find(query, {title: 0, by: 0}) # exclusion模式 指定不返回的键,返回其他键
querydb.collection.find({}, {title: 1}) # 若不想指定查询条件参数 query 可以 用 {} 代替,但是需要指定 projection 参数:
db.posts.find( { qty: { $gt: 50 ,$lt: 80}} ) # qty 大于 50 小于 80
db.col.find({title:/教/}) #查询 title 包含"教"字的文档
db.col.find({title:/^教/}) #查询 title 字段以"教"字开头的文档
db.col.find({title:/教$/}) #查询 titl e字段以"教"字结尾的文档
db.col.find({"title" : {$type : 2}}) # 1 Double, 3 Object, 4 Array, 5 Binary data, 7 Object id, 8 Boolean, 9 Date, 10 Null, 11 Regular Expression, 13 JavaScript, 14 Symbol, 16 32-bit integer, 17 Timestamp, 18 64-bit integer
db.col.find({"title" : {$type : 'string'}})
db.col.find().skip(10).limit(100) # sql 中limit (10,100)
db.test.sort({"amount":1}).skip(100000).limit(10) # 183ms sort :1 为升序排列,而 -1 是用于降序排列, 先 sort(), 然后是 skip(),最后是显示的 limit()
db.test.find({amount:{$gt:2399927}}).sort({"amount":1}).limit(10) # 53ms
- delete/remove
# remove 不会真正释放空间
> db.col.remove({'title':'MongoDB 教程'})
# 推荐使用delete
> db.inventory.deleteMany({})
> db.inventory.deleteMany({ status : "A" })
> db.inventory.deleteOne( { status: "D" } )
#回收磁盘空间
> db.repairDatabase()
> db.runCommand({ repairDatabase: 1 })
- index
### 索引
db.col.createIndex({"title":1,"description":-1}) #复合索引
db.values.createIndex({open: 1, close: 1}, {background: true}) #background:true 的选项,让创建工作在后台执行
db.col.getIndexes() #查看集合索引
db.col.totalIndexSize() #查看集合索引大小
db.col.dropIndexes() #删除集合所有索引
db.col.dropIndex("索引名称") #删除集合指定索引
#利用 TTL 集合对存储的数据进行失效时间设置
db.col.createIndex({"createDate": 1},{expireAfterSeconds: 180})
db.col.createIndex({"ClearUpDate": 1},{expireAfterSeconds: 0})
- 聚合
## 聚合
db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}]) // min, max, avg
db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) //在结果文档中插入值到一个数组中
db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) //在结果文档中插入值到一个数组中,但不创建副本。
db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) //根据资源文档的排序获取第一个文档数据。
db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) //根据资源文档的排序获取最后一个文档数据
- 管道
## 管道
db.article.aggregate( { $project : { _id : 0 , title : 1 ,author : 1 }});
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
db.article.aggregate( { $skip : 5 });
db.getCollection('m_msg_tb').aggregate(
[
{$match:{m_id:10001,mark_time:{$gt:new Date(2017,8,0)}}},
{$group: {_id: {$dayOfMonth:'$mark_time'},pv: {$sum: 1}}},
{$sort: {"_id": 1}}
])
时间关键字:
$dayOfYear: 返回该日期是这一年的第几天(全年 366 天)。
$dayOfMonth: 返回该日期是这一个月的第几天(1到31)。
$dayOfWeek: 返回的是这个周的星期几(1:星期日,7:星期六)。
$year: 返回该日期的年份部分。
$month: 返回该日期的月份部分( 1 到 12)。
$week: 返回该日期是所在年的第几个星期( 0 到 53)。
$hour: 返回该日期的小时部分。
$minute: 返回该日期的分钟部分。
$second: 返回该日期的秒部分(以0到59之间的数字形式返回日期的第二部分,但可以是60来计算闰秒)。
$millisecond:返回该日期的毫秒部分( 0 到 999)。
$dateToString: { $dateToString: { format: , date: } }。
db.articles.aggregate([{$match:{score:{ $gt:70, $lte:90 }}},{$group:{_id: null,count:{$sum: 1 }}}]);
db.articles.aggregate([{$group:{_id: null,count:{$sum: 1 }}},{$match:{score:{ $gt:70,$lte:90 }}}]);
-
复制
一主一从、一主多从
复制
mongod --port 27017 --dbpath "\mongodb\data" --replSet rs0
rs.initiate() #启动一个新的副本集
rs.conf() #查看副本集的配置
rs.status() #查看副本集状态
rs.add("mongod1.net:27017") # 副本集添加成员
db.isMaster() #判断当前运行的Mongo服务是否为主节点
- 分片
MongoDB中使用分片集群结构分布
三个主要组件
Shard: 用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障
Config Server: mongod实例,存储了整个 ClusterMetadata,其中包括 chunk信息。
Query Routers: 前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用
分片实例
分片结构端口分布如下:
Shard Server 1:27020
Shard Server 2:27021
Shard Server 3:27030
Shard Server 4:27031
Config Server1 :27100
Config Server2 :27101
Route Process:40000
- 启动Shard Server
1.创建Sharding复制集 rs0
mkdir /data/log
mkdir /data/db1
nohup mongod --port 27020 --dbpath=/data/db1 --logpath=/data/log/rs0-1.log --logappend --fork --shardsvr --replSet=rs0 &
mkdir /data/db2
nohup mongod --port 27021 --dbpath=/data/db2 --logpath=/data/log/rs0-2.log --logappend --fork --shardsvr --replSet=rs0 &
mongo localhost:27020
> rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27020'}, {_id: 1, host: 'localhost:27021'}]})
> rs.isMaster()
2. 创建Sharding复制集 rs1
mkdir /data/db3
nohup mongod --port 27030 --dbpath=/data/db3 --logpath=/data/log/rs1-1.log --logappend --fork --shardsvr --replSet=rs1 &
mkdir /data/db4
nohup mongod --port 27031 --dbpath=/data/db4 --logpath=/data/log/rs1-2.log --logappend --fork --shardsvr --replSet=rs1 &
> rs.initiate({_id: 'rs1', members: [{_id: 0, host: 'localhost:27030'}, {_id: 1, host: 'localhost:27031'}]})
> rs.isMaster()
- 启动Config Server
mkdir /data/conf1
nohup mongod --port 27100 --dbpath=/data/conf1 --logpath=/data/log/conf-1.log --logappend --fork --configsvr --replSet=conf &
mkdir /data/conf2
nohup mongod --port 27101 --dbpath=/data/conf2 --logpath=/data/log/conf-2.log --logappend --fork --configsvr --replSet=conf &
#复制集conf配置
mongo localhost:27100
> rs.initiate({_id: 'conf', members: [{_id: 0, host: 'localhost:27100'}, {_id: 1, host: 'localhost:27101'}]})
> rs.isMaster() #查看主从关系
- 启动Route Process
nohup mongos --port 40000 --configdb conf/localhost:27100,localhost:27101 --fork --logpath=/data/log/route.log --chunkSize 500 --logappend &
# mongos启动参数中,chunkSize这一项是用来指定chunk的大小的,单位是MB,默认大小为200MB
- 设置分片
# mongo localhost:40000
> use admin
> db.runCommand({ addshard: 'rs0/localhost:27020,localhost:27021'})
> db.runCommand({ addshard: 'rs1/localhost:27030,localhost:27031'})
> db.runCommand({ enablesharding: 'test'})
> db.runCommand({ shardcollection: 'test.user', key: {name: 1}})
- 备份(mongodump)与恢复(mongorestore)
> mongodump -h dbhost -d dbname -o dbdirectory
#备份所有MongoDB数据
mongodump --host runoob.com --port 27017
mongodump --dbpath /data/db/ --out /data/backup/
#备份指定数据库的集合
mongodump --collection mycol --db test
> mongorestore -h <hostname><:port> -d dbname <path>
- 监控
mongostat
mongotop 10
mongotop --locks
高级教程
- 关系
嵌入式关系
引用式关系 - 数据库引用
DBRefs
{ $ref : 集合名称 , $id : 引用的id , $db : 数据库名称,可选参数 }
{
"_id":ObjectId("53402597d852426020000002"),
"address": {
"$ref": "address_home",
"$id": ObjectId("534009e4d852427820000002"),
"$db": "runoob"},
"contact": "987654321",
"dob": "01-01-1991",
"name": "Tom Benzamin"
}
手动引用
- 覆盖索引查询
#创建联合索引
db.users.ensureIndex({gender:1,user_name:1})
#该索引会覆盖以下查询
db.users.find({gender:"M"},{user_name:1,_id:0})
- 查询分析
#explain 操作提供了查询信息,使用索引及查询统计等
db.users.ensureIndex({gender:1,user_name:1})
db.users.find({gender:"M"},{user_name:1,_id:0}).explain()
indexOnly: 字段为 true ,表示我们使用了索引。
cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。
n:当前查询返回的文档数量。
nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。
millis:当前查询所需时间,毫秒数。
indexBounds:当前查询具体使用的索引。
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
- 原子操作
{ $set : { field : value } } # $set 指定一个键并更新键值,若键不存在并创建
{ $unset : { field : 1} } # $unset 删除一个键
{ $inc : { field : value } } # $inc可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作
{ $push : { field : value } } # 把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去
{ $pushAll : { field : value_array } } #$pushAll 同$push,只是一次可以追加多个值到一个数组字段内
{ $pull : { field : _value } } # $pull 从数组field内删除一个等于value值
{ $pop : { field : 1 } } # $pop 删除数组的第一个或最后一个元素
# $addToSet 增加一个值到数组内,而且只有当这个值不在数组内才增加
{ $rename : { old_field_name : new_field_name } } # $rename修改字段名称
{$bit : { field : {and : 5}}} # $bit 位操作,integer类型
# 偏移操作符
> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }
> t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )
> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
- 高级索引
db.users.ensureIndex({"tags":1})
db.users.find({tags:"cricket"}).explain() // 检索集合中的字段
db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})
db.users.find({"address.city":"Los Angeles","address.state":"California","address.pincode":"123"}) // 检索子文档中的字段
- 索引限制
额外开销
内存(RAM)使用
查询限制 :正则表达式及非操作符,如 $nin, $not, 等;算术运算符,如 $mod, 等; $where 子句
索引键限制: 插入文档超过索引键限制,
最大范围: 集合中索引不能超过64个; 索引名的长度不能超过128个字符; 一个复合索引最多可以有31个字段 - ObjectId
ObjectId 是一个12字节 BSON 类型数据
4个时间戳+3个机器标识码+2个PID+3个随机数
newObjectId = ObjectId()
ObjectId("5349b4ddd2781d08c09890f4").getTimestamp() //获取文档的创建时间
new ObjectId().str //将objectid 转换成字符串
- Map Reduce
>db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number
}
)
db.users.mapReduce(map,reduce,{out:{inline:1}}); #这个选项只有在结果集单个文档大小在16MB限制范围内时才有效
>db.posts.mapReduce(
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
).find()
- 全文检索
#启动全文检索
db.adminCommand({setParameter:true,textSearchEnabled:true})
mongod --setParameter textSearchEnabled=true
db.posts.ensureIndex({post_text:"text"}) #创建全文索引
>db.posts.find({$text:{$search:"runoob"}}) //使用全文索引
db.posts.getIndexes() //查找索引名
db.posts.dropIndex("post_text_text") // 删除索引
- 正则表达式
>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}}) //不区分大小写的正则表达式
- 管理工具
- GridFs
- 固定集合
>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
>db.cappedLogCollection.isCapped() #判断集合是否为固定集合:
- 自动增长
>function getNextSequenceValue(sequenceName){
var sequenceDocument = db.counters.findAndModify(
{
query:{_id: sequenceName },
update: {$inc:{sequence_value:1}},
"new":true
});
return sequenceDocument.sequence_value;
}
>db.createCollection("counters")
>db.counters.insert({_id:"productid",sequence_value:0})
>db.products.insert({
"_id":getNextSequenceValue("productid"),
"product_name":"Apple iPhone",
"category":"mobiles"})