存储数据:
电影,电影的评论,评论的回复,。。。
使用关系数据库存储,会有多张表关联,相当复杂;
而使用MongoDB存储,一篇文档足以。
#默认安装 当前版本的linux下apt管理的所有版本中最新的mongodb
sudo apt-get install mongodb
sudo apt-get install mongodb=x.x.x #选择一个版本
此时会有一个默认的配置文件,/etc/mongod.conf
3.x版本是/etc/mongodb.conf,它是yaml格式的。
命令行下输入:
mongod 开启单个服务进程
mongos 查询路由进程,集群中使用
mongo 开启客户端shell进程,mongodb基于js解释器,如mongo ./test.js; 可以执行js脚本
mongoexport 导出数据,json、csv格式
mongoimport 导入数据,json、csv格式
mongodump 导出数据,bson格式
mongorestore 恢复数据,bson格式
mongostat 诊断工具
mongotop
由linux系统管理,查找可执行路径、查找配置文件的路径,命令行启动:
mongod --dbpath ~/ --logpath /var/log/mongodb/mongod.log --port 27017 --fork
# 后台启动 数据目录du -h 300MB
也可以通过自定义的配置文件,启动mongod/mongos
如下:
mongod --config myMongod.conf
mongos --config myMongos.conf
#--config或者-f
具体配置项可以参考官网。
ubuntu 通过tgz or zip文件安装
参考链接
window msi文件安装
下载官方的msi文件,进行安装,默认配置文件—安装目录/bin/mongod.cfg
//显式创建集合
>db.createCollection("Stu")
//隐式创建
>db.stu.insert({name:"jack"})
文档之间关系:嵌入、引用
文档键值对有序
mongo shell(基于javascript解释器)中可以直接使用
数据库:use people;//切换、创建
集合: db.records.insert({ })
访问Mongod服务:
mongo --host xxx --port 27017 -u user -p --authenticationDatabase admin
>show dbs;
>use mydb;//插入数据后,show dbs才可以显示出来;
>db.stats();//统计当前数据库的信息
>db.dropDatabase();//删除当前数据库
>//进入mongo shell 即执行db.dropDatabase()
>//默认删除test数据库,存在test情况下,没有也就无所谓了
>show collections;//查看当前数据库中所有的集合
>show tables;
>db.getCollectionNames() //获取所有集合的名字,返回数组
>//第一次插入数据时,隐式创建集合
>db.stu.insert({});
>//显式创建一个集合
>db.createCollection("stu");
//获取所有集合的信息
>db.getCollectionInfos();
>//statistic collection info
>db.myCollection.stats()
//指定返回哪些key的数据
>db.stu.find({},{_id:0,name:1,age:0})
>db.createCollection(name,options)
capped,为true时,则为上限集合,具有存储上限,达到上限则循环覆盖旧数据
size,集合存储容量大小,byte
max,集合中最大文档数
//是否上限集合
db.myCollection.isCapped()
//案例,创建一个上限集合,大小3byte,插入10000条数据,查看效果
for(var i=0;i<10000;i++){
db.lauf1.insert({name:"jack"+i})
}
>db.users1.renameCollection("users")
>db.collection1.drop();//删除集合collection1
>//普通集合转为上限集合,也可更改上限集合的内存大小
>db.runCommand({"convertToCapped":"lauf1",size:10})
insert,若插入时key重复,则DuplicateKeyException
>db.c1.insert({...});//相当于insertOne()
>db.c2.insert([{},{},...])//相当于insertMany()
>db.c3.insert({key:value},{writeConcern:{},ordered:boolean})
//writeConcern 写入的安全机制,
//ordered,true顺序写入文档,有一个文档出错则返回
//ordered,false随机写入文档,出错则忽略,继续写入
writeConcern:添加链接描述
update/save
更新单个文档,满足原子性
更新多个文档,不满足原子性
多进程更新同一文档,需阻塞
>db.users.update({query},{update},{upsert:false,multi:false,writeConcern,collation})
//upsert,boolean 查询结果为空时,是否插入当前update文档
//multi,boolearn 是否更新多个结果,文档整个替换时,不能为true
>db.stu.update({name:"jack"},{"name":"tom",age:20},{upsert:true}) //替换类型
> db.stu.update({name:"jack19"},{$set:{age:20}},{upsert:true,multi:true})//非替换类型,只更新局部,$set设置key的值(可指定各种类型的值)
> db.stu.update({name:"jack19"},{$inc:{age:1}},{upsert:true,multi:true})//增加、减少数值类型,key不存在则创建,并值默认为0,在0基础上增加、减少;若没有满足条件的文档,则不做操作
> db.stu.update({name:"jack19"},{$push:{arr:25}}) //给数组插入值,key不存在则创建
> db.stu.update({name:"jack19"},{$addToSet:{arr:25}})//给数组增加元素,若元素已经存在,则忽略
> db.stu.save({})//保存一个文档,已存在则更新,不存在则插入
>db.users.find().sort({key:1/-1})//升序、降序排序
upsert,没有查询到需更新的记录时,插入当前{update}文档,默认false
multi,是否更新多个文档,默认false
writeConcern,写入的安全机制
collation,按照不同的语言定义排序规则
如{collation:{locale:“zh”}}
>db.users.save({update});//使用该文档替换已有的同名_id文档;若集合内没有同名_id,则插入该{update}文档
remove,deleteOne,deleteMany
delete doc.field
上限集合无法删除数据
>db.users.remove({query},{justOne:false,writeConcern}) //justOne是否只删除一条
>//永久删除数据
>db.users.remove({name:"jack"})//删除name为jack的文档
>db.users.remove({})//删除所有文档
>db.users.remove({age:{$gt:20}})//删除年龄大于20的文档
deleteOne/deleteMany
>db.users.deleteMany({})//删除所有文档
>db.users.deleteMany({age:{$lt:20}})//删除年龄小于20的多个文档
>db.users.deleteOne({age:{$gt:10}})//删除一个
find,返回满足条件的所有文档
findOne,返回一个文档
>db.users.find().pretty()//格式化输出
>db.users.find({query},{projection})//加入查询条件,投影返回的字段
>db.users.find({name:"jack",age:{$lt:20}},{_id:0,name:1,age:1})//过滤key,0不显示
查询条件:
>db.users.find({name:"jack"})//name is jack
>db.users.find({name:"jack",age:23})//name is jack and age is 23
>db.users.find({age:{$lte:20}})//age<=20
>db.users.find({age:{$lt:20}})//age<20
>db.users.find({age:{$gte:20}})//age>=20
>db.users.find({age:{$gt:20}})//age>20
>db.users.find(age:{$ne:20})//age!=20
>db.users.find({age:{$gte:18,$lte:20}})
>db.users.find({$or:[{},{},{},....]})//或
>db.users.find({name:{$in:["jack","tom"]}})//名字在数组中的文档 ,$nin不在数组
>db.users.find({name:{$not:"abc"}})//名字中不包含abc的文档
>db.users.find({addr:null})//addr为null的文档或者无该字段的文档
>db.users.find({name:/regexp/ig})
//key 为数组时的查询
>db.users.find({name:"a"})//查询name数组包含“a”的文档
{ "_id" : ObjectId("611dc688623a0dbae82982c8"), "name" : [ "a", "b", "c" ], "age" : 23 }
>db.users.find({name:{$all:["a","b"]}})//既包含a,又包含b的文档
> db.users.find({name:{$size:3}})//查询name数组长度为3的文档
> db.users.find({"name.2":"b"})//数组的索引2处为b的文档,必须带有引号
>db.users.find().skip(1).limit(3)//跳过一个,然后返回三个文档
>db.users.find().sort({age:1})//以age升序排序
>db.users.find({name:{$regex:"^ab$"}})//匹配姓名中以a开头,以b结尾的文档;若name为数组,则逐个匹配数组中的每一项
>
遍历数据的指针,读取数据的接口
> var cursor=db.users.find()//返回游标
> while (cursor.hasNext()){ //hasNext()打开游标
... var doc=cursor.next();//返回一个文档对象[object BSON]
... print(doc);//tojson(xxx)
... printjson(doc);//以json格式输出
... }
//输出结果
[object BSON]
{
"_id" : ObjectId("611dc14a623a0dbae82982c5"),
"name" : "jack",
"age" : 29
}
[object BSON]
{ "_id" : ObjectId("611dc14a623a0dbae82982c6"), "name" : "tom", "age" : 23 }
以下三种情况,游标被销毁
1)客户端保存的游标变量不在作用域内
2)游标遍历完成/ 主动关闭cursor.close()
3)服务器端10分钟内未对游标进行操作
db.users.aggregate([{$group:{_id:"$name",uage:{$sum:"$age"}}}])//在age字段聚合
{ "_id" : "lucy", "uage" : 10 }
{ "_id" : "tom", "uage" : 35 }
{ "_id" : [ "a", "b", "c" ], "uage" : 23 }
{ "_id" : "jack", "uage" : 47 }
{ "_id" : [ "b", "c" ], "uage" : 0 }
//$group表示分组,_id表示分组字段,uage表示聚合后的key : value为对哪个字段进行聚合
//$match表示匹配
//$sum,$avg,$min,$max
//{$sum:1}统计所有文档数
示例
>db.users.aggregate([
{
$match: { $or: [ { age: { $gt: 70, $lt: 90 } }, { v: { $gte: 1000 } } ] } },
{ $group: { _id: "$name", newKey: { $sum: 1 } }
}
]);//统计每组的文档数 以name的值分组
> db.products.aggregate([{$match:{quantity:{$lt:10}}},{$sort:{quantity:1}},{$skip:1},{$limit:3}])
//匹配、排序、分页
聚合管道,实现排序,跳过几项,返回几项,即分页:
//所有数据
{ "_id" : ObjectId("617929cecab76e646e11e7b4"), "quantity" : 2, "price" : 4, "pnumber" : "p001" }
{ "_id" : ObjectId("6179295dcab76e646e11e7b0"), "quantity" : 2, "price" : 5, "pnumber" : "p003" }
{ "_id" : ObjectId("61792970cab76e646e11e7b2"), "quantity" : 2, "price" : 8, "pnumber" : "p002" }
{ "_id" : ObjectId("617929dfcab76e646e11e7b5"), "quantity" : 4, "price" : 10, "pnumber" : "p003" }
{ "_id" : ObjectId("61792a0ccab76e646e11e7b8"), "quantity" : 5, "price" : 10, "pnumber" : "p002" }
{ "_id" : ObjectId("617929f8cab76e646e11e7b6"), "quantity" : 10, "price" : 20, "pnumber" : "p001" }
{ "_id" : ObjectId("617929fbcab76e646e11e7b7"), "quantity" : 10, "price" : 20, "pnumber" : "p003" }
> db.products.aggregate([{$sort:{price:1}},{$skip:1},{$limit:3}])//price 排序,跳过1项,取3项
{ "_id" : ObjectId("617929cecab76e646e11e7b4"), "quantity" : 2, "price" : 4, "pnumber" : "p001" }
{ "_id" : ObjectId("6179295dcab76e646e11e7b0"), "quantity" : 2, "price" : 5, "pnumber" : "p003" }
{ "_id" : ObjectId("61792970cab76e646e11e7b2"), "quantity" : 2, "price" : 8, "pn
mapReduce操作
>db.users.find()
{ "_id" : ObjectId("611dc14a623a0dbae82982c5"), "name" : "jack", "age" : 29 }
{ "_id" : ObjectId("611dc14a623a0dbae82982c6"), "name" : "tom", "age" : 23 }
{ "_id" : ObjectId("611dd190d0bf16d55c366a29"), "name" : "jack", "age" : 18 }
{ "_id" : ObjectId("611dd1aad0bf16d55c366a2a"), "name" : "tom", "age" : 12 }
{ "_id" : ObjectId("611dd1aad0bf16d55c366a2b"), "name" : "lucy", "age" : 10 }
//使用map-reduce进行聚合年龄
>var mapFunc=function(){
... emit(this.name,this.age); //定义map函数,以name进行分组,name的值为key,以age的值作为新的value,得到结果如:“jack”:[29,18,]
... }
> var reduceFunc=function(key,values){
... return Array.sum(values);//定义reduce函数,对年龄数组求和
//Array.avg()
//Array.min()
//Array.tab键盘查看其他函数
... }
> db.users.mapReduce(mapFunc,reduceFunc,{out:{replace:"map-reduce-result"}})
{ "result" : "map-reduce-result", "ok" : 1 }
索引:存储引擎级别的概念,建立在集合上,基于B-tree数据结构,能够更快地检索数据的方法,存储特定的字段或字段值,且按一定的顺序排序。
建立索引:消耗计算与存储资源
插入文档:引起索引顺序的重排,默认在_id上建立唯一索引
单键索引、复合索引、多键值索引、全文索引、散列索引等
//单键索引
>db.users.createIndex( { age: 1 } ) //1为升序,-1为降序 默认索引名字 age_1
>db.users.createIndex({age:-1},{background:true,name:"ageIndex",partialFilterExpression:{age:{$gt:20}}})
>//background后台创建索引,不影响mongod服务
>//name指定索引名字
>//partialFilterExpression 过滤文档,给其建立索引
>sparse, bool, 只给存在的字段创建索引,不与partialFilterExpression同时使用
>expireAfterSeconds 过期时间 xx秒,字段为日期格式
> db.stu1.find()
{ "_id" : ObjectId("61791890cab76e646e11e7ad"), "name" : "tom", "age" : 23 }
{ "_id" : ObjectId("617bd0f8beb128756dc60543"), "name" : "laufing", "age" : 23, "date" : ISODate("2021-10-29T10:46:16.096Z") }
> db.stu1.createIndex({date:1},{expireAfterSeconds:30}) //必须是日期类型的字段
//过期后,文档数据会删除。
>db.users.find({age:25}).explain()//查看查询过程
//复合索引
>db.users.createIndex({name:1,age:-1})
//多键值索引,即key对应的值为数组,
//无法创建复合的多键值索引,即多个key为数组
//地理索引,平面数据2d格式,球面数据2dsphere
>db.city.insert(
{
loc : { type: "Point", coordinates: [ -73.97, 40.77 ] },
name: "Beijing",
category : "c1"
}
)
>db.city.insert(
{
loc : { type: "Point", coordinates: [ -73.88, 40.78 ] },
name: "Tianjing",
category : "c2"
}
)
>db.city.createIndex( { loc : "2dsphere" } )//在loc字段创建球面索引
>db.city.find({loc:"2dsphere"}).explain()
//全文索引,在所有的字符串中查询
>db.users.createIndex( { name: "text" } )
//散列索引只能用于字段完全匹配的查询,不能用于范围查询
>db.users.createIndex( { _id: "hashed" } )
//稀疏索引
>db.users.createIndex( { "key": 1 }, { sparse: true } )
//唯一索引
>db.users.createIndex( { "key": 1 }, { unique: true } )
//过期索引,索引过期后,对应的数据会删除
//一段时间后会过期的索引,在索引过期后,相应的数据会被删除
//这适合存储一些在一段时间之后会失效的数据,比如用户的登录信息,
//想要用户登录信息2天后失效,需要用户重新登录,或者存储的日志,希望这些日志在一段时间后删除
>db.users.insert({time:new Date()})
>db.users.createIndex( {time: 1 }, { expireAfterSeconds: 5 } )//5s后索引过期,数据删除
>db.users.getIndexes() //查看集合的索引
//查看数据库下的所有集合的索引
db.getCollectionNames().forEach(function(collection)
{
indexes = db[collection].getIndexes();
print("Indexes for " + collection + ":");
printjson(indexes);
});
//删除索引
>db.users.dropIndex({name:1})//删除特定的索引
>db.users.dropIndexes()//删除所有索引,除了_id
//更改现有索引就是删除、重建
看查询使用的字段及其查询使用的频率
如果所有的查询都使用同一个键时,创建一个单键索引。
如果所有的查询都使用多个键时,创建复合索引。
使用索引来排序查询结果,无索引时在内存中排序,若占用内存超出32M,则停止操作。
//单键索引排序
db.users.createIndex( { age: 1 } )
db.users.find().sort( { age: 1 } )
db.users.find().sort( { age: -1 } )
//复合索引排序,就必须按照索引顺序
//索引 { a: 1, b: 1 } 可以支持排序 { a: 1, b: 1 } 但不支持 { b: 1, a: 1 } 排序。
//sort中指定的所有键的排序顺序必须和索引中的对应键的排序顺序 完全相同, 或者 完全相反
如索引 { a: 1, b: 1 }支持排序{a:-1,b:-1}
//复合索引的前缀
>db.users.createIndex( { a:1, b: 1, c: 1, d: 1 } )
//那么,该索引的前缀如下:
{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }
如果排序的键符合 索引的键或者前缀 ,那么MongoDB可以使用索引来排序查询结果;也可以使用非前缀来排序。
db.users.find({a:4,b:3}).sort( { b: 1, c: 1 } )
索引与内存容量相匹配
>db.users.totalIndexSize()//当前索引占用的内存,字节
http://caibaojian.com/mongodb/security-checklist.html
认证,用来识别用户的身份。
授权,控制认证用户的权限
1.用户
数据库管理用户
数据库普通用户
默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,MongoDB不会对连接客户端进行用户验证,可以想象这是非常危险的。为了强制开启用户访问控制(用户验证),则需要在MongoDB实例启动时使用选项–auth或配置文件中添加auth=true。
MongoDB使用的是基于角色的访问控制(Role-Based Access Control,RBAC)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限
https://www.cnblogs.com/dbabd/p/10811523.html
//为数据库创建一个用户
>use people;
>db.createUser({user:"lauf",pwd:"lauf",roles:[{role:"readWrite",db:"people"}]})//readWrite角色
#1.连接服务时,认证
$mongo --host localhost --port 27017 -u lauf -p lauf --authenticationDatabase people
//2.连接后进行认证
lauf@master:/usr/local/mongo$ mongo --host 192.168.43.111 --port 27017
MongoDB shell version v4.4.8
MongoDB server version: 4.4.8
> show dbs;
> db.auth("lauf","lauf")
Error: Authentication failed.
0
> use people;//必须切换到对应的数据库
switched to db people
> db.auth("lauf","lauf")
1
> show dbs;
people 0.000GB
> show tables;
//为什么lauf用户可以操作其他的数据库?因为Mongod没有开启认证
//开启认证后,lauf 登录后只能看到自己的数据库
//增加用户,新版不支持该函数
db.addUser("user", "pwd","readOnly")//是否只读用户true/false
db.addUser("user","newpwd")//更新密码
db.removeUser("user")//删除用户
在刚安装好MongoDB时,没有用户认证,admin(管理用户和权限)下也没有认证的用户,创建好用户后,所有的用户信息存储在admin>system.users集合中
一个用户只能在一个数据库下
//查看用户
>use people;
>db.getUser("lauf")//查看lauf用户
>db.getUsers()//查看所有用户
主节点Primary
一个副本集,只能一个Primary;
Primary,可读取、可写入,同时将更改操作写入oplog;
主节点故障时,自动在从节点中选出一个作为主节点。
从节点Secondary
一个副本集,可以有一个或者多个从节点;
从节点默认不可读,需要设置才可以读取数据;
从节点异步地从主节点同步oplog,然后作用于自己的数据集,保证实现一个完整的数据副本。
仲裁节点Arbiter
不存储数据,只记录副本集中的节点数或者选举主节点时进行仲裁;
具有选举权,但自己不能成为主;
systemLog:
destination: file
path: /var/log/mongodb/mongod1.log
logAppend: true
storage:
dbPath: /usr/local/mongodb4.4.1/data/rs_mongod1
journal:
enabled: true
net:
bindIp: localhost
port: 27017
processManagement:
fork: true
replication:
oplogSimeMB: 100
replSetName: my_repl
#认证
#security:
# keyFile: xxxx.keyfile
# authorization: enabled
sudo mongod --config ./rs_mongod1.conf
sudo mongod --config ./rs_mongod2.conf
sudo mongod --config ./rs_mongod3.conf
mongo --host localhost --port 27017
>show dbs; //无法查询
>var conf = {_id:"my_repl",members:[{_id:1,host:"localhost:27017"},{_id:2,host:"localhost:27018"},{_id:3,host:"localhost:27019"}]}
>rs.initiate(conf)
>rs.status()
>rs.add("ip:port") //在主节点增加从节点
>rs.addArb("ip:port") //在主节点增加仲裁节点
>rs.remove("ip:port") //删除节点
>//将一个从节点改为仲裁节点,删除-->添加
>//主节点插入数据,在从节点读取,复制功能
>rs.slaveOk() //在从节点设置可读
>rs.config() //查看各个节点的属性
>var conf = rs.config()
>conf.members[0].priority = 2
>rs.reconfig(conf)
也可以使用命令行启动一个副本集的mongod实例:
sudo mongod --dbpath /usr/local/mongodb4.4.1/data/mongodx
--logpath /var/log/mongodb/mongodx.log
--logappend
--bind_ip localhost --port 27020
--fork --replSet my_repl --shardsvr
注意:数据目录一定要存在,且当前用户具有写入的权限
- -shardsvr 允许数据库分片
openssl rand -base64 90 > /usr/local/mongodb4.4.1/conf/mongo.keyfile
#设置可读
chmod 400 /usr/local/mongodb4.4.1/conf/mongo.keyfile
security:
keyFile: /usr/local/mongodb4.4.1/conf/mongo.keyfile
authorization: enabled
#连接到主节点
mongo localhost:27017
>show dbs; #无法读取,因为需要认证
>use admin;
>db.createUser({user:"lauf",pwd:"lauf123",roles:[{role:"root",db:"admin"}]})
>db.auth("lauf","lauf123")
>show dbs;
命令行方式认证连接:
mongo localhost:27017 -u lauf -p --authenticationDatabase admin
高可扩展性
组件:复制集、路由器、服务器
实现分布式的读写,数据切片存储
public class App {
public static void main(String[] args) {
try {
//连接MongoDB 服务器,端口号为27017
MongoClient mongoClient = new MongoClient("localhost", 27017);
//连接数据库
MongoDatabase mDatabase = mongoClient.getDatabase("test"); //test可选
System.out.println("Connect to database successfully!");
System.out.println("MongoDatabase inof is : "+mDatabase.getName());
} catch (Exception e) {
System.err.println(
e.getClass().getName() + ": " + e.getMessage());
}
}
}
MongoCollection collection = database.getCollection("myTestCollection");
Document document = new Document("_id", 1999)
.append("title", "MongoDB Insert Demo")
.append("description","database")
.append("likes", 30)
.append("by", "demo point")
.append("url", "http://www.demo.com/mongodb/");
collection.insertOne(document);
List<Document> documents = new ArrayList<Document>();
documents.add(document1);
collection.insertMany(documents);
collection.deleteOne(document);
collection.deleteMany(new Document ("likes", 30));
collection.updataOne(eq ("likes", 30),new Document ("$set", new Document ("likes", 50)));
import com.mongodb.client.MongoCursor
document Doc = (Document) collection.find(eq ("likes", 30)).iterator();
MongoCursor<Document> cursor =collection.find().iterator();
try {
while (cursor.hasNext()) {
System.out.println(cursor.next().toJson());
}
} finally {
Cursor.close();
}
mDatabase.drop();
collection.drop();
mongoClient.close();
import pymongo
conn = MongoClient(host='10.90.9.101',port=27017)
#获取数据库
db = conn["mydb"] #conn.get_database("mydb")
#获取集合
c1 = db["c1"] #db.get_collection("c1")
c1.insert_one({}) #插入一篇文档
c1.insert_many([{},{},....]) #插入多篇
#查询
c1.find_one({}) #查询一个
c1.find({}) #查询多个
#更新数据
c1.update({"name":"jack"},{"$set":{"age":200}}) #将jack的年龄改为200
#删除
c1.remove({"name":"jack"})
c1.delete_one({})
c1.delete_many({})