存储文档的非关系型数据库
MongoDB之安装与使用
MongoDB的安装与配置
docker run --name mymongo -v /mymongo/data:/data/db -d mongo:latest
--name mymongo:自定义容器的名字
-v /mymongo/data:/data/db:挂载数据目录,使得数据可以在本地查看,不会在docker关闭之后丢失
-d:后台运行容器
mongo-express的安装与配置
docker run --link mymongo:mongo -p 8081:8081 mongo-express
--link mymongo:mongo:使得该容器与另一个容器相关联
-p 8081:8081:使得容器的端口暴露,可以被外界访问
docker stop mymongo
docker start mymongo
交互访问容器
docker exec -it mymongo mongo
MongoDB 之 Create
- 创建一个文档
- db.<collection>.insertOne(<document>,{writeConcern:<documnet>})
- writeConcern:定义了安全写级别,安全级别用于判断一次写是否成功,同时安全级别越高,丢失数据的风险越低,写入操作的延迟也越高,不提供的话mongoDB使用默认的安全写级别。
- db.accounts.insertOne( {_id:"account1",name:"alice",balance:100})
- { "acknowledged" : true, "insertedId" : "account1" }
- 创建多个文档
- db.<collection>.insertMany( [<document1>,<document2>,...],{ writeConcern:<document>,ordered:<boolean> })
- db.accounts.insertMany([ { name:"charlie", balance:500 }, { name:"david", balance:200 } ] );
- ordered:用于决定mongoDB是否按顺序来写入文档,定义为true(默认值)时会严格按照我们提供的顺序写入文档,一旦遇到错误操作会退出,剩余文档不会写入数据库,当定义为false时,剩余的正确文档会被写入。
- 创建单个或多个文档
- db.<collection>.insert(<document or array of documents>, {writeConcern:<document>,ordered:<boolean> })
- try{ db.accounts.insert({_id:"account10",name:"alice",balance:100}) }catch(e){print(e); };
- 区别:只有insert支持db.collection.explain()命令
- 更新文档
- try{ db.accounts.save({_id:"account2",name:"alice",balance:100}) }catch(e){print(e); };
- 处理一个新文档的时候,它会调用insertOne()命令
- 复合文档
- db.accounts.insert({ _id:{type:"saving",accountNo:"1"}, name:"tom", balance:600})
- WriteResult({ "nInserted" : 1 })
- 当使用文档作为文档主键的时候,用作主键的文档字段顺序不同,创建时不会报错
- ObjectId() 对象主键
- ObjectId().getTimestamp();
MongoDB 之 Read
- 读取文档
- db.<collection>.find();
- 匹配查询
- db.<collection>.find(<query>,<projection>)
- <query>定义了读取操作时筛选文档的条件
- <projection>定义了对读取结果进行的投射操作
- db.accounts.find({name:"alice"}) { "_id" : "account1", "name" : "alice", "balance" : 100 }
- 复合主键的匹配查询 db.accounts.find({"_id.type":"saving"})
- 查询操作符
- 比较操作符
- {<field>:{$<operator>:<value>}}
- <operator>:
- $eq(匹配与字段相等的文档),
- db.accounts.find( {name:{$eq:"tom"}} );
- $ne (匹配与字段不等的文档),
- ps:会筛选出不包含查询字段的文档!!!就是说有文档并没有这个字段也能被筛选出来。
- db.accounts.find( {type:{$ne:"saving"}} );
- $gt,匹配字段值大于查询值的文档,
- $gte,匹配字段值大于或等于查询值的文档,
- $lt,匹配字段小于查询值的文档,
- $lte,匹配字段小于或等于查询值的文档,
- $in 匹配字段值与任一查询值相等的文档,ps:传入字段是数组
- db.accounts.find({"name":{$in:["charlie"]}})
- $nin 匹配字段值与任何查询值都不等的文档,
- 需要注意的是,和$ne一样,$nin也会筛选出并不包含查询字段的文档!!!
- 逻辑操作符
- $not 筛选条件不成立的文档
- {field:{$not:{<operator-expression>}},$not也和$ne一样同样会筛选出不包含查询字段的文档
- db.accounts.find({"balance":{$not:{$lt:500}}})
- $and 匹配多个筛选条件全部成立的文档
- {$and:[{<expression1>},{<expression2>},...]}
- 全写 db.accounts.find({$and:[{"balance":{$gt:100}} , {"name":{$gt:"fred"}}]})
- 简写
- db.accounts.find({balance:{$gt:100},name:{$gt:"fred"}})
- db.accounts.find({balance:{$gt:100,$lt:500}})
- $or 匹配至少一个筛选条件成立的文档
- db.accounts.find({$or:[{"name":{$eq:"david"}},{"balance":{$eq:200}}]})
- $nor 匹配筛选条件全都不成立的文档,
- 需要注意的是,和$ne一样,$nor也会筛选出并不包含查询字段的文档
- db.accounts.find({$nor:[{name:"alice"},{name:"charlie"},{balance:{$lt:100}}]});
- 字段操作符
- $exists 匹配包含查询字段的文档
- {field:{$exists:<boolean>}
- db.accounts.find({$nor:[{"_id.type":{$exists:true}}]})
- db.accounts.find({"_id.type":{$ne:"checking",$exists:true}});
- type $type 匹配字段类型复合查询值的文档
- {field:{$type:<BSON type>}}
- db.accounts.find({_id:{$type:["object"]}})
- {field:{$type:[<BSON type1>,<BSON type2>,...]}}
- db.accounts.find({_id:{$type:["objectId","object"]}})
- 数组操作符
- db.accounts.find({ contact:{$all:[ {$elemMatch:{$gt:"10000000",$lt:"20000000"}}, {$elemMatch:{$gt:"20000000",$lt:"30000000"}} ]}});
- $all 匹配数组字段中包含所有查询值的文档
- {field:{$all:[<value1>,<value2>,...]}}
- db.accounts.find({contact:{$all:[["2222222","3333333"]]}})
- $elemMatch 匹配数组字段中至少存在一个值满足筛选条件的文档
- {field:{$elemMatch:{<query1>,<query2>,...}}}
- db.accounts.find({ contact: {$elemMatch:{$gt:"10000000",$lt:"22222222222222222222"}}});
- 运算操作符
- $regex 匹配满足正则表达式的文档
- {field:{:/pattern/,:'<options>'}}
- db.accounts.find({name:{$regex:/LIE/,$options:'i'}})
- {field:{:/pattern/<options>}},在和$in操作符一起使用时,只能使用该语法
- db.accounts.find({name:{$in:[/^c/,/^j/]}})
- db.collection.find()返回一个文档集合游标,在不迭代游标的情况下,只列出前20个文档
- 遍历完游标中所有的文档之后,或者在10分钟之后,游标便会自动关闭。
- db.accounts.find().noCursorTimeout(); ,使用noCursorTimeout() 函数来保持游标一直有效。
- var myCursor = db.accounts.find()
- myCursor.next();
- myCursor.hasNext();
- myCursor.forEach(printjson);
- db.accounts.find({name:"jack"}).skip(2); 跳过2个
- db.accounts.find({name:"jack"}).limit(1);
- db.accounts.find({name:"jack"}).count();
- db.accounts.find({name:"jack"}).limit(1).count(true); 为false,即cursor.count()不会考虑cursor.skip()和cursor.limit()的效果。
- db.accounts.find().sort({balance:-1,name:1}); 1表示正向排序,-1表示逆向排序
- *** 游标函数执行顺序 cursor.sort(),cursor.skip() , cursor.limit() , !!!
- 文档投影 db.collection.find(<query>,<projection>)
- 不使用投影时,返回符合筛选条件的完整文档。
- 使用投影可以选择性的返回文档中的部分字段
- db.accounts.find({},{contact:1}) 默认返回主键_id
- db.accounts.find({},{contact:2,_id:0}) 设置不返回主键_id
- db.accounts.find({},{name:0,_id:0,balance:1}) 除了文档主键_id,投影文档不允许混合使用包含和不包含
- 在数组字段上使用投影
- $slice操作符可以返回 数组字段中的部分元素
- 当$slice的参数为n时,表示投射数组中前n个元素 db.accounts.find({},{ name:1,contact:{$slice:1}})
- 当$slice的参数为-n时,表示投射数组中倒数n个元素 db.accounts.find({},{ name:1,contact:{$slice:-1}})
- 当$slice的参数为[n,m]时,表示数组进行skip(1)和limit(m)操作 db.accounts.find({},{ name:1,contact:{$slice:[1,2]}})
- $elemMatch和$操作符可以返回数组字段中满足筛选条件的第一个元素,当对文档的筛选条件和在投射操作中对数组的筛选条件一致的时候,可以使用$操作符
- db.accounts.find({},{ name:1,contact:{$elemMatch:{$gt:"Alabama"}}}) 额外使用一个筛选条件
- db.accounts.find({contact:{$eq:"Alabama"}},{_id:0,name:1,"contact.$":1}) 使用的是find筛选条件里的
- 左边是mongodb查询语句,右边是sql语句。对照着用,挺方便。
- db.users.find() select * from users
- db.users.find({"age" : 27}) select * from users where age = 27
- db.users.find({"username" : "joe", "age" : 27}) select * from users where "username" = "joe" and age = 27
- db.users.find({}, {"username" : 1, "email" : 1}) select username, email from users
- db.users.find({}, {"username" : 1, "_id" : 0}) // no case // 即时加上了列筛选,_id也会返回;必须显式的阻止_id返回
- db.users.find({"age" : {"$gte" : 18, "$lte" : 30}}) select * from users where age >=18 and age <= 30 // $lt(<) $lte(<=) $gt(>) $gte(>=)
- db.users.find({"username" : {"$ne" : "joe"}}) select * from users where username <> "joe"
- db.users.find({"ticket_no" : {"$in" : [725, 542, 390]}}) select * from users where ticket_no in (725, 542, 390)
- db.users.find({"ticket_no" : {"$nin" : [725, 542, 390]}}) select * from users where ticket_no not in (725, 542, 390)
- db.users.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]}) select * form users where ticket_no = 725 or winner = true
- db.users.find({"id_num" : {"$mod" : [5, 1]}}) select * from users where (id_num mod 5) = 1
- db.users.find({"$not": {"age" : 27}}) select * from users where not (age = 27)
- db.users.find({"username" : {"$in" : [null], "$exists" : true}}) select * from users where username is null // 如果直接通过find({"username" : null})进行查询,那么连带"没有username"的纪录一并筛选出来
- db.users.find({"name" : /joey?/i}) // 正则查询,value是符合PCRE的表达式
- db.food.find({fruit : {$all : ["apple", "banana"]}}) // 对数组的查询, 字段fruit中,既包含"apple",又包含"banana"的纪录
- db.food.find({"fruit.2" : "peach"}) // 对数组的查询, 字段fruit中,第3个(从0开始)元素是peach的纪录
- db.food.find({"fruit" : {"$size" : 3}}) // 对数组的查询, 查询数组元素个数是3的记录,$size前面无法和其他的操作符复合使用
- db.users.findOne(criteria, {"comments" : {"$slice" : 10}}) // 对数组的查询,只返回数组comments中的前十条,还可以{"$slice" : -10}, {"$slice" : [23, 10]}; 分别返回最后10条,和中间10条
- db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"}) // 嵌套查询
- db.blog.find({"comments" : {"$elemMatch" : {"author" : "joe", "score" : {"$gte" : 5}}}}) // 嵌套查询,仅当嵌套的元素是数组时使用,
- db.foo.find({"$where" : "this.x + this.y == 10"}) // 复杂的查询,$where当然是非常方便的,但效率低下。对于复杂查询,考虑的顺序应当是 正则 -> MapReduce -> $where
- db.foo.find({"$where" : "function() { return this.x + this.y == 10; }"}) // $where可以支持javascript函数作为查询条件
- db.foo.find().sort({"x" : 1}).limit(1).skip(10); // 返回第(10, 11]条,按"x"进行排序; 三个limit的顺序是任意的,应该尽量避免skip中使用large-number
MongoDB 之 UPDATE
- db.collection.update()
- db.accounts.update({name:"leon"},{name:"leon",balance:200})
- 如果有多条文档,使用update默认只更新第一条
- db.<collection>.update(<query>,<update>,<options>)
- 更新特定字段
- $set 新增字段
- db.accounts.update({name:"leon"},{$set:{"info.dateOpened":new Date("2016-01-11T16:00:00Z")}})
- db.accounts.update({name:"leon"},{$set:{"info.balanch.liubi.0":[2,3,4]}}) .0更新指定数组元素
- $unset 删除字段
- db.accounts.update({name:"leon"},{$unset:{"info.balanch":""}})
- db.accounts.update({_id:ObjectId("5dd0f1e5062477b5f487f828")},{$unset:{"contact.0":""}}) 删除数组元素,数组元素的长度不会改变值为null
- $rename 重命名字段
- { $rename: { <fields1>: <newName1>,<field2>:<newName2>,...}}
- 如果rename命令要重命名得字段并不存在,那么文档内容不会被改变。
- db.accounts.update( {name:"leon"}, {$rename:{ "name":"name1" } });
- 如果新得字段名已经存在,那么原有得这个字段内容会被覆盖。
- 当$rename命令中得新字段存在得时候,$rename命令会先$unset新旧字段,然后再$set新字段。
- db.accounts.update({name:"karen"},{$rename:{"info.branch":"branch","balance":"info.balance"}});
- $rename 命令新旧字段均不可以指向数组元素。
- 更新操作符
- { $inc : { <field1> : <amount1>,...}}
- db.accounts.update({name:"david"},{$inc:{balance:-0.5}}) balance值-0.5
- { $mul: { <field1> : <number1>,..}}
- db.accounts.update({name:"david"},{$mul:{balance:2}}); balance值*2
- 只能应用在数字上
- 如果被更新得字段不存在 $inc会创建字段,并将字段值设为命令中得增减值,而$mul会创建字段,但是会把字段值设为0.
- { $min:{<field1>:<value1>, ...}}
- db.accounts.update({name:"karen"},{$min:{"info.balance":5000}});
- 与原来得balance进行比较,谁小取谁。
- db.accounts.update({name:"karen"},{$min:{"info.dateOpene":ISODate("2013-10-01T16:00:00Z")}})
- 可以比较时间
- { $max:{ <field1>:<value1>, ...}}
- db.accounts.update({name:"karen"},{$max:{"info.balance":5000}});
- 与原来得balance进行比较,谁大取谁。
- 如果被更新得字段不存在,$min和$max命令会创建字段,并且将字段值设为命令中得更新值。
- 如果被更新得字段类型与更新值类型不一致,$min和$max命令会按照BSON数据类型排序规则进行比较。
- 排序规则:
- 最小:Null,
- Numbers(ints, longs, doubles, decimals)
- Symbol,String,
- Object,
- Array,,
- BinData,
- Boolean,
- Date,
- Timestamp
- 最大:Regular Expression
- $addToSet 数组中添加字段
- db.accounts.update({name:"karen"},{$addToSet:{contact:"China"}})
- 如果添加得新值已经存在于数组中,则不会重复插入。
- db.accounts.update({name:"karen3"},{$addToSet:{contact:{primaryEmail:"xxx@gmail.com",secindaryEmail:"yyy@gamil.com"}}})
- db.accounts.update({name:"karen3"},{$addToSet:{contact:["contact1","contact2"]}})
- db.accounts.update({name:"karen3"},{$addToSet:{contact:{$each:["contact1","contact2"]}}})
- $pop
- db.accounts.update({name:"karen3"},{$pop:{"contact.3":-1}}) 删除内嵌数组第一个元素
- {$pull: {<fields1>:<value|condition>,...}} 删除特定元素
- db.accounts.update({name:"lawrence"},{$pull:{contact:{"primaryEmail":"xxx@gamil.com"}}})
- db.accounts.update({name:"karen"},{$pull:{contact:{$regex:/be/}}}) 删除 contact数组中包含 be得元素
- db.accounts.update({name:"karen"},{$pull:{contact:{$elemMatch:{$eq:"2222222"}}}}) 删除contact数组中,数组元素包含 222222得元素
- db.accounts.update({name:"lawrence"},{$pull:{contact:{"secindaryEmail":"yyy@gamil.com","primaryEmail":"xxx@gamil.com"}}})
- db.accounts.update({name:"lawrence"},{$pull:{contact:{"primaryEmail":"xxx@gamil.com"}}})
- pull 命令删除只要和原文档能匹配就能删。。。
- 复制文档 db.accounts.find({name:"karen3"},{_id:0}).forEach(function(doc){var newDoc = doc;newDoc.name="lawrence";db.accounts.insert(newDoc)})
- {$pullAll:{<field1>:[ <value1>,<value2>...].,..}} 只会删去字段和字段排列顺序都完全匹配的文档元素
- 相当于{ $pull : { <field1 > : { $in :[ <value1>,<value2> ] } } }
- 如果要删去的元素是一个数组,数组元素的值和排列顺序都必须和被删除的数组完全一样
- db.accounts.update({name:"lawrence"},{$pull:{contact:{$in:[2222,3333]}}})
- db.accounts.update({name:"lawrence"},{$pullAll:{contact:[[2222,3333]]}})
- {$push:{<field1>:<value1>,...}}
- db.accounts.update({name:"lawrence"},{$push:{newArray:"new element"}})
- db.accounts.update({name:"lawrence"},{$push:{newArray:{$each:[2,3,4]}}});
- db.accounts.update({name:"lawrence"},{$push:{newArray:{$each:["pos1","pos2"],$position:0}}} );
- 使用position 指定新插入字段的位置。
- db.accounts.update({name:"lawrence"},{$push:{newArray:{$each:[11],$sort:1}}})
- 插如新元素对数组进行排序,1为正序,-1为倒序
- db.accounts.update({name:"lawrence"},{$push:{newArray:{$each:[{key:"sort",value:100}],$sort:{value:-1}}}})
- 数组插入内嵌文档,指定排序为 内嵌文档value的值
- db.accounts.update({name:"lawrence"},{$push:{newArray:{$each:[3],$slice:-3}}})
- 数组插入 并截取按指定长度截取
- 这三个操作符的执行顺序 : $position $sort $slice写在命令中的操作符顺序并不会影响到执行顺序
- 一定要注意
- db.collection.update({<array>:<query selector>},{<update operator>:{"<array>.$":value}})
- 更新数组中的特定元素 $是数组中第一个符合筛选条件的数组元素的占位符
- db.accounts.update({name:'lawrence',newArray:"push1"},{$set:{'newArray.$':"updated"}})
- 把push1 更新成了 updated
- db.accounts.update({name:"bill"},{$set:{ "newArray.$[]":'value'}})
- 数组全部更新成value
- db.<collection>.update(<query>,<update>,<options>) 更新文档选项
- { multi:<boolean> } 更新多个文档
- db.accounts.update({name:"lawrence"},{$set:{currency:"USD"}},{multi:true})
- 注意 MongoDB 只能保证*单个*文档操作的原子性,不能保证*多个*文档操作的原子性(中间有可能被打断,可能被其他线程更新)。更新多个文档的操作虽然在单一线程中执行,但是线程在执行过程中可能被挂起,以便其他线程也有机会对数据进行操作。
- 如果需要保证多个文档操作时的原子性,就需要使用MongoDB4.0版本引入的事务功能进行操作。
- { upsert : <boolean> } 更新或创建文档 upsert设置为true,如果update命令中的筛选条件没有匹配文档,则会新建新文档(筛选条件也会新建)
- db.accounts.update({name:"meggie"},{$set:{balance:800}},{upsert:true})
- db.accounts.update({balance:{$gt:20000}},{$set:{name:"nick"}},{upsert:true})
- 如果无法从筛选条件中推断出确定的字段值,新创建的文档将不会包含筛选条件涉及的字段,但仍然会创建新文档 。{ "_id" : ObjectId("5dd6b160ebece8a7ca7cab20"), "name" : "nick" }
- db.<collection>.save(<document>)
- 如果document文档中包含了_id字段,save()命令将会调用db.collection.update()命令(upsert:true)
- db.accounts.save({_id:"account1",name:"davidd",balance:100})
MongoDB 之 DELETE
- db.collection.remove() 删除文档 但是不会删除集合
- db.accounts.remove({balance:200}) 会删除所有满足条件的文档
- db.accounts.remove({balance:{$lt:200}},{justOne:true})
- 如果只想删除满足筛选条件的*第一篇*文档,可以使用justOne选项
- db.accounts.remove( {} ) 删除所有文档,但是并不会删除集合
- db.collection.drop() 删除集合
- db.<collection>.drop( { writeConcern : <document> } )
- writeConcern文档定义了本次集合删除操作的安全写级别
- 如果集合中文档数量很多,使用remove命令删除所有文档的效率不高,这种情况下,更加有效率的方法。是使用drop命令删除集合,再创建新集合。