Mongodb基础 学习小结
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展
的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰
富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以
存储比较复杂的数据类型。
Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,
几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
MongoDB主要场景如下:
1)网站实时数据处理。非常适合实时的插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
2)缓存。由于性能很高,它适合作为信息基础设施的缓存层。在系统重启之后,由它搭建的持久化缓存层可以避免下层的数据源过载。
3)高伸缩性的场景。非常适合由数十或数百台服务器组成的数据库,它的路线图中已经包含对MapReduce引擎的内置支持。
不适用的场景如下:
1)要求高度事务性的系统。
2)传统的商业智能应用。
3)复杂的跨文档(表)级联查询。
/*********** Windows版
Windows 安装 msi 包
下载:https://www.mongodb.com/download-center#community
选择 Community Server 选择一个稳定版
安装...
然后找个合适位置 建立目录,用于存放数据和日志:
md "\data\db" "\data\log"
测试启动:(必须指定db路径) */
"C:\Program Files\MongoDB\Server\3.6\bin\mongod.exe" --dbpath="c:\data\db"
/*将 MongoDB 安装为 Windows 服务:
创建配置文件:C:\Program Files\MongoDB\Server\3.6\mongod.cfg
内容如下:(指定了log和db的位置) */
systemLog:
destination: "file"
path: "c:\\data\\log\\mongod.log"
storage:
dbPath: "c:\\data\\db"
// 安装服务:
sc.exe create MongoDB binPath= "\"C:\Program Files\MongoDB\Server\3.6\bin\mongod.exe\" --service --config=\"C:\Program Files\MongoDB\Server\3.6\mongod.cfg\"" DisplayName= "MongoDB" start= auto
// 如果成功则显示:CreateService SUCCESS
// 以后就可以系统服务中管理了。
// 卸载服务:(需要先停止服务)
sc.exe delete MongoDB
/**
管理工具 Robomongo :
下载:https://robomongo.org/download
MongoChef
下载:http://3t.io/mongochef/#mongochef-download-compare
*/
// ----------------------------------------------------------------------------
//-------------------------------------- Linux版 -------------------------------
// Mongodb 需要64位linux 本次使用ubuntu18.0.4 如果是centos,某些命令会不同。
// 到官网下载tar包 tgz, 注意选择OS版本
// https://www.mongodb.com/download-center
tar zxvf mongodb-linux-x86_64-3.6.4.tgz
mv mongodb-linux-x86_64-3.6.4 mongodb364
cd mongodb364
// 建立所需的db文件夹和logs文件夹,在根目录创建,是因为停止服务时,不带参数,会默认寻找此路径
mkdir -p /data/db
mkdir -p /data/logs
// 修改环境变量,vim /etc/profile 添加
export PATH=/home/mongodb364/bin:$PATH
source /etc/profile // 生效
// ------------如果不能启动服务,ubuntu18 可能需要安装某些依赖。--------------
// 如果 apt-get 出现 E: Unable to locate package 问题, 则执行以下命令:
add-apt-repository main
add-apt-repository universe
add-apt-repository restricted
add-apt-repository multiverse
apt-get update
// 如果不能解决,再执行这条:
apt-get upgradee
// 移除某个包:
apt-get remove xxx
// 错误: libcurl.so.4: version `CURL_OPENSSL_3' not found
apt-get install curl
apt-get install libcurl3
// CentOS的依赖
yum install libcurl openssl // 安装依赖
// 启动服务 指定路径 --port 10086 指定端口 bin/mongod --dbpath=/opt/mongodb/data/db --logpath=/opt/mongodb/logs/mongo.log --fork bin/mongo --help // 使用客户端帮助 bin/mongo // 首次本机登录无需认证 bin/mongod --config /etc/mongod.conf // 或者指定配置文件启动,通常在设置安全后 // 停止服务 mongod --shutdown // 不指定dbpath, 默认会寻找 /data/db // 停止服务 mongod --shutdown --dbpath /home/mongodb364/data/db // 指定dbpath // 停止服务 mongod --shutdown --config=/etc/mongod.conf // 也可指定配置文件
/************* 安全性流程: 1.创建超级管理员 2.修改配置文件,启用身份验证 3.重启服务 4.使用超级管理员登录 5.创建并使用普通用户 *************/
// -------------------------------使用配置文件:--------------------------
vim /etc/mongod.conf // 较简单的格式
bind_ip = 0.0.0.0 port=27017 dbpath=/data/db logpath=/data/logs/mongod.log pidfilepath=/opt/mongodb/mongod.pid fork=true logappend=true auth=true
// ---------------------------YAML格式配置文件--------------------------
如果是 rpm / yum 方式安装,则自动添加 /etc/mongo.conf ,示例:
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /data/logs/mongod.log
# Where and how to store data.
storage:
dbPath: /data/db
directoryPerDB: true
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:
# how the process runs
processManagement:
fork: true # fork and run in background
pidFilePath: /opt/mongodb/mongod.pid # location of pidfile
timeZoneInfo: /usr/share/zoneinfo
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
security:
authorization: "enabled"
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options
#auditLog:
#snmp:
bin/mongod --config /etc/mongod.conf // 指定配置文件启动
// 如果启动失败,请检查配置中文件路径是否正确
//------------------------配置远程连接--------------------------------- systemctl status ufw // 查看防火墙状态 systemctl stop ufw // 停止防火墙 ufw allow 27017 // 防火墙允许端口 // --------上面是ubuntu的,下面是centos7的。-------------------------- firewall-cmd --permanent --add-port=27017/tcp // 添加3306 systemctl restart firewalld.service // 重启fw firewall-cmd --list-all // 查看fw
如果需要,也可将Mongodb安装为服务,便于管理
############# 在 CentOS7 中安装为服务 # 1. 创建用户和组 groupadd -g 1002 mongogrp useradd -u 1002 -g mongogrp -G mongogrp mongouser passwd mongouser # 2. 更改mongodb文件夹拥有者 chown -R mongouser:mongogrp mongod/ # 3.编辑服务文件 /etc/systemd/system/mongod.service 并保存 # 写User和Group是限制指定用户可以操作
[Unit] Description=MongoDB Server After=network.target remote-fs.target nss-lookup.target [Service] User=mongouser Group=mongogrp Type=forking ExecStart=/data/mongod/bins/mongod -f /data/mongod/conf/mongod.conf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/data/mongod/bins/mongod --shutdown -f /data/mongod/conf/mongod.conf PrivateTmp=true LimitFSIZE=infinity LimitCPU=infinity LimitAS=infinity LimitNOFILE=64000 LimitNPROC=64000 [Install] WantedBy=multi-user.target
# 4. 试试用 mongouser 操作服务 sudo systemctl daemon-reload sudo systemctl start mongod sudo systemctl status mongod sudo systemctl stop mongod sudo systemctl enable mongod # 开机启动 sudo systemctl disable mongod # 关闭开机启动 sudo systemctl is-enabled mongod # 检查是否开机启动 sudo systemctl kill mongod # 杀死进程mongod sudo systemctl list-units -t service # 列出已激活的服务 sudo systemctl list-units -t service -a # 列出所有服务
//########################### mongo shell ####################################
mongo // 登录本机(未开启认证时) // 执行以下命令,可以添加 管理员 use admin; db.createUser( { user: "admin", pwd: "admin", roles: [ { role: "root", db: "admin" } ] } ); /** 下面是成功时返回的提示: Successfully added user: { "user" : "admin", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }; **/ db.auth("admin","admin") // 认证测试,若 1 则成功 //----------------------------------------------------------------------------- // 如开启认证: use admin // 进入管理库 db.auth("username","pwd") // 认证登录 // 或直接认证连接: 本地方式 mongo --username admin --password --authenticationDatabase admin mongo -u admin -p --authenticationDatabase admin // 远程方式 mongo "mongodb://admin:pwd@localhost:27017/?authSource=admin"
// 更多连接实例 // 参考: https://www.mongodb.org.cn/tutorial/7.html //连接本地数据库服务器,端口是默认的。 mongodb://localhost //使用用户名fred,密码foobar登录localhost的admin数据库。 mongodb://fred:foobar@localhost //使用用户名fred,密码foobar登录localhost的baz数据库。 mongodb://fred:foobar@localhost/baz //连接 replica pair, 服务器1为example1.com服务器2为example2。 mongodb://example1.com:27017,example2.com:27017 //连接 replica set 三台服务器 (端口 27017, 27018, 和27019): mongodb://localhost,localhost:27018,localhost:27019 //连接 replica set 三台服务器, 写入操作应用在主服务器 并且分布查询到从服务器。 mongodb://host1,host2,host3/?slaveOk=true //直接连接第一个服务器,无论是replica set一部分或者主服务器或者从服务器。 mongodb://host1,host2,host3/?connect=direct;slaveOk=true //当你的连接服务器有优先级,还需要列出所有服务器,你可以使用上述连接方式。 //安全模式连接到localhost: mongodb://localhost/?safe=true //以安全模式连接到replica set,并且等待至少两个复制服务器成功写入,超时时间设置为2秒。 mongodb://host1,host2,host3/?safe=true;w=2;wtimeoutMS=2000
// Shell 常用操作 db show dbs show collections use local show users // 查看当前库中用户 db.test.insert({'a':'333323'}) // 插入数据时会自动创建库和Collections db.test.find() // 显示所有数据 exit // 退出mongo shell // 最后用Robo 3T 测试连接
/*************** 如果需要重置密码: ***************/ // 1. 停止服务 mongod --shutdown --config=/etc/mongod.conf // 2. 修改conf文件,关掉security和authorization // 3. 启动服务 mongod --config /etc/mongod.conf // 4. mongo 登录,use admin , show users // 显示本库用户 db.system.users.find() // 查看全部用户信息。 db.dropUser(用户名) // 删除指定的用户 // 5. 使用上面的 db.createUser 添加用户: use admin db.createUser( { user: "admin", pwd: "admin", roles: [ { role: "root", db: "admin" } ] } ); // 或者修改用户: db.updateUser('admin', { pwd: "admin", roles: [{ role: "root", db: "admin" } ] } ) // 6. 重新开启配置文件的安全选项,并重启服务
/*********** Mongodb内置的用户角色:
1. 数据库用户角色:read、readWrite
2. 数据库管理角色:dbAdmin、dbOwner、userAdmin
3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager
4. 备份恢复角色:backup、restore
5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
6. 超级用户角色: root (最大权限,可访问所有库)
间接或直接提供了系统超级用户访问的角色:dbOwner、userAdmin、userAdminAnyDatabase
7. 内部角色:__system
# 创建一个 __system 用户,可以访问 所有元数据
db.createUser({user:"sa", pwd:"123456", roles:[{role:"__system", db:"admin"}]})
获得数据库的所有用户权限信息:db.getUsers()
获得某个用户的权限信息:db.getUser()
创建角色: db.createRole()
更新角色:db.updateRole()
删除角色:db.dropRole()
获得某个角色信息:db.getRole()
删除用户:db.dropUser()
删除所有用户:db.dropAllUsers()
将一个角色赋予给用户:db.grantRolesToUser()
撤销某个用户的某个角色权限:db.revokeRolesFromUser()
更改密码:db.changeUserPassword()
***********/
// 例1:给test库添加用户,只能访问test 即 --authenticationDatabase test use test db.createUser( { user: "test1", pwd: "test1", roles: [ { role: "readWrite", db: "test" } ] } ); // 例2:修改user1权限 --authenticationDatabase demo use demo db.updateUser('user1',{ roles: [{role:"dbOwner", db:"demo"}] }) // 例3:修改test用户权限,读写所有库 use admin db.updateUser('test',{ roles: [{role:"readWriteAnyDatabase", db:"admin"}] }) /************ 其它命令 ****** 创建 capped collection 上限集合: size:大小KB,如果size字段小于或等于4096,则集合的上限为4096字节 max: 最大文档数。size参数总是必需的,即使指定max的文件数量。 如果集合在达到最大文档计数之前达到最大大小限制,MongoDB将删除旧文档。 参考官方文档 或 https://www.jianshu.com/p/86942c09094a ************/ db.createCollection("sub",{capped:true,size:100000,max:5}) db.sub.isCapped() // 检查是否 capped // 转换为 capped 集合 db.runCommand({"convertToCapped": "test", size: 100000, max:10 });
/**************
*** CURD ***
**************/
//----------------------------------- 增 ---------------------------------------
// Mongodb 数据库名称不区分大小写
// 数据库不必创建,直接 use databasename 即可
// 集合名称可以db.createCollection创建,
// 一般集合也可不创建,直接在插入数据时自动生成。
// 集合名称只能字母或下划线开头,不能包含 . $ 和空字符,不能 system. 开头
// 更多限制,参考官文:https://docs.mongodb.com/manual/reference/limits/
// 插入多条 db.users.insert([ {name:'Allan',age:32,gender:1}, {name:'Bob',age:25,gender:1}, {name:'Celine',age:32,gender:2}, {name:'Dennis',age:12,gender:1}, ]) // 以下两种插入方法是为了语义清晰 // db.test.insertOne() // db.test.insertMany() // ================================== 查 ======================================= db.test.find({}) // 空对象或空,查询所有的 db.test.find({name:"Jerry"}) // 返回数组 db.test.findOne({name:"Jerry"}) // 返回对象 db.test.find({}).count() // 数量 或 db.test.count() db.users.count({age:{$gt:25}}) // 条件数量 db.test.find({age:{$gt:25}}) // 大于 或者 $gte 即 >= db.test.find({create_ts:{$gt:ISODate("2019-09-19T00:00:00.000Z")}}).pretty() //大于某时间
db.test.find({age:{$eq:25}}) // 等于 或者 $ne 不等于 db.test.find({age:{$lt:25}}) // 小于 或者 $lte 即 <= db.test.find({age:{$gt:20, $lt:30}}) // 大于且小于 db.demoes.find({$or:[{num:{$lt:10}},{num:{$gt:1990}}]}) // 或 $or:[{},{},] db.users.find({age:{$in:[25,32]}}) // 在in 或 不在nin db.users.find({name:/^C/}) // 正则,以C开头 db.users.find({name:{$regex:"^A"}}) // 正则, 以A开头 db.users.find({name:{$regex:/EV/, $options:'mi'}}) // 正则,包含EV的,选项m 包含换行符,i不区分大小写 db.users.find({nickname:{$exists: false}}) // 找出不存在昵称字段的 (true 存在)
db.users.find({hobby:{$elemMatch:{$eq: "football"}}}) // 找出爱好中包含足球的。查找数组中包含某个。
// 虽然可用JS语法,但是效率低 db.users.find({$where:function(){return this.age>20}}) db.users.find({$where:function(){return this.name.startsWith("C")}}) db.test.find().limit(10) // 前10条 db.test.find().skip(10).limit(10) // 跳过10条
// 官方文档也提到 skip() 由于是从头开始数,所以,当数据量变大时,会变得非常慢。
// 我测试的时候,大于5万条数据,已经很慢了。 所以,尽量使用 条件查询,避免使用 skip() // skip( (页码 -1) * 每页条数).limit(每页条数) // limit()与skip()或sort() 前后无关,mongodb自动调整 db.test.find().sort({age:-1}) // 按年龄降序 // 1取0不要,只有_id需要特别指定为0 db.test.find({},{name:1, _id:0, age:1}) // 投影:只要name和age,不要_id db.users.distinct("gender", {age:{$lt:20}}) // 年龄小于20的性别 // --------------------------------- 改 ---------------------------------------- // db.test.update({_id:ObjectId("5cede5d81f3b03073319abd0")},{age:23}) // 小心,新对象替换旧对象 // 应该使用修改符来操作 db.test.update({_id:ObjectId("5cede5d81f3b03073319abd0")},{$set:{name:"老沙", gender:"男",address:"流沙河"}}) db.test.update({_id:ObjectId("5cede5d81f3b03073319abd0")},{$set:{age:88}}) db.test.update({_id:ObjectId("5cede5d81f3b03073319abd0")},{$unset:{address:0}}) // 删除某字段 只删除第一个匹配到的
// $unset 删除字段时,字段写什么值并不影响结果。
db.test.update({},{$unset:{"Profiles":""}},{multi:true}) // 删除了所有的 Profiles 字段
// db.test.update({},{$unset:{"Profiles":""}})
db.test.update({},{$unset:{"Qr":"SOCKET_EMPTY"}},{multi:true}) // 删除所有的 Qr 字段
db.test.update({name:"AAAA"},{$set:{address:"test"}}) // 默认只改一条 db.test.update({name:"AAAA"},{$set:{address:"999"}},{multi:true}) // 使用multi改所有的
db.test.update({dept:'HR'},{$addToSet:{Groups: "reader"}},{multi:true}); // 给数组字段添加内容。不重复值。使用 push 的话,允许重复。
db.test.updateMany({name:"AAAA"},{$set:{address:"105"}}) // 改所有符合的 db.demo.updateMany({created_at:{$exists:false}},{$set:{created_at:'2019-07-08 16:20:00'}}) //不存在某字段时,加上
db.test.find().forEach(function(item){
db.test.update({"_id":item._id},
{"$set": {"client_ts":item.create_ts}},false,true)
}) // 用某个字段值 更新另一个字段
// 修改某个json字段的值
db.aaa.update({"Header.Tz":{"$exists":true}},{"$set":{"Header.Tz":"+0800"}},{multi:true});
// 修改Data数组中包含 L 项的键名
db.bbb.find({"Data.L":{"$exists":true}}).forEach(function(item) {
for(i = 0; i != item.Data.length; ++i) {
item.Data[i].Lb= item.Data[i].L;
delete item.Data[i].L;
}
db.bbb.update({_id:item._id},item);
});
/* ********************************* 删 ****************************************/
db.test.remove({address:"999"}) // 默认删除全部匹配的行
db.test.remove({create_time:{$lt: ISODate("2020-05-01T00:00:00Z")}}) // 删除某个日期前的全部数据
db.test.update({Section:'TE'},{$pull:{Groups: "Test"}},{multi:true}); // remove a item from array.
db.test.remove({address:"999"}, true) // 只删除符合条件的一行 db.test.remove({},true) // 删除第一行 db.test.remove({}) // 清空集合
db.test.find({client_ts:{$lt:ISODate("2021-07-13T00:00:00.094Z")}}).forEach(function(item){
db.demo.remove({"RootId":item._id})
}) // 先删除关联表的内容, 再删除 test表的内容。
db.test.remove({client_ts:{$lt:ISODate("2021-07-13T00:00:00.094Z")}});
db.test.drop() // 删除集合 db.dropDatabase() // 删当前库(赶紧跑路) // 通常上面的都不会在生产环境中执行, 而使用字段isDel代替 db.test.updateOne({name:"AAAA"},{$set:{isDel:1}}) db.test.find({isDel:0})
// $type 操作符 https://www.mongodb.org.cn/tutorial/15.html db.test.find({"title" : {$type : 2}}) // 条件:字段为string类型
// -----------#-----------#----------- 练习 ------#-----------#-----------#-------
// 属性值也可以是个文档 即 内嵌文档 db.goods.update({name:"CPU"},{$set:{band:{Intel:["I3","I5","I7"],AMD:["R5","R7"]}}}) db.goods.find({'band.AMD':'R5'}) // 查询内嵌时必须引号, 包含 db.goods.find({'band.AMD':'R5'},{'band.AMD.$':1}) // 仅取出AMD.R5的列表元素,不要其它的元素。 db.goods.update({name:"Mem"},{$push:{'band.KST':'3333'}}) // 插入一个,可重复 db.goods.update({name:"Mem"},{$addToSet:{'band.KST':'3333'}}) // 添加到集合,不重复 db.goods.update({name:"Mem"},{$pop:{'band.KST':1}}) // 移除最后一个元素 //# 插入20000条数据 // 1. 笨方法: for(var i=1; i<20001;i++){ db.demos.insert({num:i}) } // 2. 好方法: var arr=[]; for(var i=1; i<20001;i++){ arr.push({num:i}); } db.demoes.insert(arr)
文档间关系
//# 1. 一对一:通过内嵌文档的形式. db.married.insert([ {name:"Tom",guy:{name:"Jerry"}}, {name:"大郎",guy:{name:"金莲"}} ]) //# 2. 一对多, 多对一: db.users.insert([ {username:"swk"}, {username:"zbj"} ]) db.order.insert([ {list:["CPU","Mem","Disk"], user_id: ObjectId("5cef98a9e4a985f4ad3e897c") }, {list:["Apple","Pear","Cherry"], user_id: ObjectId("5cef98a9e4a985f4ad3e897c") } ]) db.order.insert( {list:["Pig","Donkey","Sheep"], user_id: ObjectId("5cef98a9e4a985f4ad3e897d") }) // 查询 var user_id=db.users.findOne({username:"swk"})._id; db.order.find({user_id:user_id}) //# 3. 多对多: db.teacher.insert([ {name:"如来"}, {name:"观音"}, {name:"菩提"} ]) db.students.insert([ {"name" : "Tom", "teacher_ids" : [ ObjectId("5cefbdffe4a985f4ad3e8984"), ObjectId("5cefbdffe4a985f4ad3e8982") ]}, {"name" : "Jerry", "teacher_ids" : [ ObjectId("5cefbdffe4a985f4ad3e8984"), ObjectId("5cefbdffe4a985f4ad3e8983") ]} ]) // 查询: 如来的徒弟 var tid = db.teacher.find({name:"如来"})[0]._id // 或 var tid = db.teacher.findOne({name:"如来"})._id db.students.find({teacher_ids:tid}) //-----------------#----------------- 练习 ------------------#------------------- // 1. 找出财务部员工 var deptno = db.dept.findOne({dname:"财务部"}).deptno db.emp.find({deptno:deptno}) // 2. 低于1000的加400 db.emp.updateMany({sal:{$lt:1000}},{$inc:{sal:400}}) db.emp.updateMany({deptno:2},{$inc:{sal:500}}) // IT部加工资 // 3. 价高于300的减100 db.goods.updateMany({price:{$gt:300}},{$inc:{price:-100}})
聚合,统计
// aggregate 用于聚合统计,$group 类似于SQL的sum(), avg() // 各求男女人数 db.users.aggregate([{$group:{_id:'$gender',counter:{$sum:1}}}]) db.users.aggregate([{$group:{_id:'$gender',counter:{$sum:'$age'}}}]) // 年龄合计 db.users.aggregate([{$group:{_id:'$gender',counter:{$avg:'$age'}}}]) // 平均年龄 db.users.aggregate([{$group:{_id:'$gender',counter:{$max:'$age'}}}]) // 或 min db.users.aggregate([{$group:{_id:'$gender',counter:{$first:'$age'}}}]) // 或 last // 列出各年龄到数组,不去重 db.users.aggregate([{$group:{_id:'$gender',counter:{$push:'$age'}}}]) // 男女分组,列出name到数组 db.users.aggregate([{$group:{_id:'$gender',counter:{$push:'$name'}}}]) // 男女分组,基于整个文档列出所有字段到数组 db.users.aggregate([{$group:{_id:'$gender',counter:{$push:'$$ROOT'}}}]) //## $match db.users.aggregate([{$match:{age:{$gt:25}}}]) // 筛选 // 将筛选后的结果再聚合 db.users.aggregate([ {$match:{age:{$gt:25}}}, {$group:{_id:'$gender',counter:{$sum:1}}} ]) // 筛选,聚合,投影 db.users.aggregate([ {$match:{age:{$gt:25}}}, {$group:{_id:'$gender',counter:{$push:'$name'}}}, {$project:{_id:0,counter:1}} ]) // 排序,和find()中的几乎一样 {$sort:{_id: -1}} 也可以用在多个地方 db.users.aggregate([ {$match:{age:{$gt:25}}}, {$sort:{name: 1}}, {$group:{_id:'$gender',counter:{$push:'$name'}}}, {$sort:{_id:-1}} ]) // 内按name升序,外按_id降序 // 跳过 {$skip:10},限制{$limit:5} 此处区分顺序 /** * {$unwind:"$counter"} 将数组字段拆开成单条 */ db.students.aggregate([ {$match:{name:"WuKong"}}, {$unwind:"$teacher_ids"} ]) db.t3.insert([ {"_id":1,"item":"a","size":["S","M","L"]}, {"_id":2,"item":"b","size":[]}, {"_id":3,"item":"c","size":"M"}, {"_id":4,"item":"d"}, {"_id":5,"item":"e","size":null}, ]) db.t3.aggregate([{$unwind:{path:"$size"}}]) // 取出值 不含空和null以及无字段 // 取出值,不丢任何数据 db.t3.aggregate([ {$unwind:{path:"$size", preserveNullAndEmptyArrays:true}} ])
// 示例1:找出几个字段组合的重复数据
db.products.aggregate([ { '$group': { '_id': { 'Product': '$Product', 'Machine': '$Machine', 'Station': '$Station', 'Status': '$Status', 'Price': '$Price' }, 'uniqueIds': { '$addToSet': '$_id' }, 'count': { '$sum': 1 } } }, { '$match': { 'count': { '$gt': 1 } } }], { allowDiskUse: true });
// 示例2: 排除某个状态的重复数据查找
db.members.aggregate([ {'$match': {'Status': {'$ne': '-1'}}}, { '$group': { '_id': { 'Station': '$Station', 'Name': '$Name' }, 'uniqueIds': { '$addToSet': '$_id' }, 'count': { '$sum': 1 } } }, { '$match': { 'count': { '$gt': 1 } } } ], { allowDiskUse: true });
// 示例3: 某日期之前,产品按名称统计。
db.cms.aggregate([ {'$match':{'create_ts':{'$lt':ISODate('2020-06-01T06:30:29.655Z')}}}, {$group:{_id:'$ProductName',counter:{$sum:1}}}])
索引
/********************************** 索引 **************************************/
注意,索引区分大小写,创建索引时,一定要准确的字段名。否则不生效。
不论是普通索引,还是hash索引,都会带来性能提升
只有日期时间类型可以设置TTL索引,单位为秒
db.t1.ensureIndex({name: 1, background:true }) // 添加索引
db.t2.ensureIndex({cardno:1},{name:'idx_cardno'}) // 创建并指定索引名称 db.t1.ensureIndex({name:1, background:true}, {unique:true}) // 多参数 db.t1.ensureIndex({name:1,age:1, background:true}) // 联合索引 db.t1.getIndexes() // 查询索引 db.t1.dropIndex("name_1") // 删除指定索引 db.t1.dropIndexes() // 删除全部索引,保留默认 _id_ 索引
// 重要:在生产环境中添加索引,一定要使用 background 参数。原因是添加索引时会锁库,影响正常使用。尤其在数据量大时,添加索引时间很长。
// 测试数据 let arr=[]; for(let i=0;i<50000;i++){ arr.push({name:"test"+i, age:i}); } db.t1.insert(arr) // 性能评估 其中结果 executionTimeMillis 为毫秒 db.t1.find({name:'test9999'}).explain('executionStats') db.t1.ensureIndex({name:1}) // 添加索引 // 再次执行 结果 executionTimeMillis 降低很多 db.t1.find({name:'test9999'}).explain('executionStats')
// 删除数据时,索引不会删除,并且体积可能变得更大,必要时可以删除索引,重建索引。
/*************************** 复制集 ******************************/
// 官文 https://docs.mongodb.com/manual/replication/
主备
// 例1:单机演示。(生产环境不适用)使用不同端口,不同数据目录,必须设置相同 replSet mongod --bind_ip 192.168.112.9 --port 27018 --dbpath /data/t1 --replSet rs0 mongod --bind_ip 192.168.112.9 --port 27019 --dbpath /data/t2 --replSet rs0 // 服务运行窗口可以看到后续操作的日志显示 // 然后客户端分别连接服务 mongo --host 192.168.112.9 --port 27018 rs.initiate() // 初始化为master 提示符发生变化 rs.status() // 查看复制集状态 rs0:PRIMARY> rs.isMaster() // 是否主节点 rs.add("192.168.112.9:27019") // 添加slave mongo --host 192.168.112.9 --port 27019 rs.slaveOk() // slave上执行同意 // 例2:不同机器(接近生产环境)。必须设置相同 replSet // 注意OS版本,rs1:PRIMARY 可低版本,rs1:SECONDARY可高版本。反之则rs.add出错 // 本例PRIMARY为112.6(centos6.5), SECONDARY为112.9(centos7.5) mongod --bind_ip 192.168.112.6 --port 27018 --dbpath /data/t3 --replSet rs1 mongod --bind_ip 192.168.112.9 --port 27018 --dbpath /data/t3 --replSet rs1 // 使用客户端分别连接服务 mongo --host 192.168.112.6 --port 27018 mongo --host 192.168.112.9 --port 27018 rs.initiate() // 112.6 为 master rs.status() // 查看复制集状态 rs1:PRIMARY> rs.add("192.168.112.9:27018") // 添加slave rs.slaveOk() // 112.9 上执行同意 // 测试数据 master use demo1 db.users.insert({name:"Allen",age:28}) db.users.insert({name:"Bob",age:38}) // slave上查询 use demo1 db.users.find() // 得到相同结果 // 发现master当机后,某slave则自动变为PRIMARY // 而修好后的master启用,加入到rs1,转为SECONDARY
/////////////////////// 添加一个节点(192.168.112.7:27017) // 新节点 安装,添加目录,然后执行: mongod --bind_ip 192.168.112.7 --port 27017 --dbpath /data/t3 --replSet rs1 // 然后 rs1:PRIMARY> 上执行: rs.add("192.168.112.7:27017") // 添加slave节点 // slave 新窗口上执行: mongo --host 192.168.112.7 --port 27017 > rs.slaveOk() // 即可自动更新所有数据 /////////////////////// 删除一个节点 rs1:PRIMARY> rs.remove("192.168.112.6:27018") // 被删节点将不可用 /////////////////////// 停止节点服务 mongod --bind_ip 192.168.112.7 --port 27017 --dbpath /data/t3 --shutdown // 其它命令:
rs.conf() // 显示复制集信息
rs.printReplicationInfo() // 从primary角度显示复制集信息,显示 oplog 信息
rs.printSlaveReplicationInfo() // 从secondary角度显示复制集信息
db.serverStatus().asserts // 查看断言
rs.stepDown() // 强制primary降为secondary,并引发新的选举. 慎用
#故障案例:某个 slave 节点自动down掉,状态为 recovering 再次启动时过一会还是down掉,检查日志后发现:
[replication-0] We are too stale to use 10.1.1.34:27017 as a sync source. Blacklisting this sync source because our last fetched timestamp:
Timestamp(1584041941, 30) is before their earliest timestamp: Timestamp(1584399943, 455) for 1min until:
解决办法:将此 slave 停止服务, 改名或删除 mongodb 仅数据目录后,重新启动 mongodb, 如果状态显示为 startup2 则表示成功开始同步数据。等待同步完成即可恢复正常。
最近试用了4.4.5版本,直接用配置文件的方式搭了个复制集:
#官网下载 tgz 包,https://www.mongodb.com/try/download/community # 选 centos7.0, 选tgz 下载 cd /data/ tar zxf mongodb-linux-x86_64-rhel70-4.4.5.tgz mv mongodb-linux-x86_64-rhel70-4.4.5 mongo4.4.5 cd mongo4.4.5/ # 新建目录用来存放配置文件,日志,数据 mkdir -p conf logs data cd data/ # 新建3个子目录用来存放3个节点的数据 mkdir -p mongo0 mongo1 mongo2
在 conf目录下添加3个不同的配置文件,mongod0.conf mongod1.conf mongod2.conf 并将内容中的文件名和路径改成各自对应的。以及不同的端口。
systemLog:
path: /data/mongo4.4.5/logs/mongod0.log
logAppend: true
logRotate: rename
destination: file
operationProfiling:
mode: slowOp
slowOpThresholdMs: 300
slowOpSampleRate: 1.0
storage:
dbPath: /data/mongo4.4.5/data/mongo0
journal:
enabled: true
directoryPerDB: true
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 2
directoryForIndexes: true
processManagement:
fork: true
pidFilePath: /data/mongo4.4.5/data/mongod0.pid
net:
port: 27000
bindIpAll: true
maxIncomingConnections: 5120
replication:
oplogSizeMB: 1024
replSetName: testRS
然后,依次按不同的配置文件启动 /data/mongo4.4.5/bin/mongod -f /data/mongo4.4.5/conf/mongod0.conf
三个都启动成功后,会看到各自不同端口的服务。然后初始化:
/data/mongo4.4.5/bin/mongo localhost:27000 > rs.initiate({_id:'testRS',members:[{_id:0,host:'10.170.6.17:27000'},{_id:1,host:'10.170.6.17:27001'},{_id:2,host:'10.170.6.17:27002'}]}) > rs.status() > rs.conf()
好了,可以用了。
备份与恢复
// mongodump -h dbhost -d dbname -o outdir // 备份将在输出目录中生成Collection对应的 bson 和 metadata.json 文件 mongodump -u admin -p admin --authenticationDatabase admin -h 192.168.112.9 -d test -o /bak/mongodb // 恢复 mongorestore -h dbhost -d dbname --dir inputdir mongorestore -u admin -p admin --authenticationDatabase admin -h localhost -d test --dir /bak/mongodb/test/
Python pymongo
// driver for python pip3 install pymongo
/*************************************** demo.py ******************************/
#!/usr/bin/env python # coding:utf-8 from pymongo import * client = MongoClient('mongodb://test1:test1@192.168.112.9:27017/test') db = client.test # 库 users = db.users # Collections # res = users.insert({'name':'python','age':88,'gender':'female'}) # 插入 # print(res) # 返回 id # ret = users.insert_one({'name':'pip3','age':19,'gender':'male'}) # 插入 # print(ret) # 返回 object # ret2 = users.update_one({'name':'python'},{'$set':{'name':'Python3'}}) # 修改 # print(ret2) # ret3 = users.delete_one({'name':'pip3'}) # 删除 # print(ret3) # cursor = users.find_one({'name':'Python3'}) # 只取一条 # print(type(cursor), cursor) # for k, v in cursor.items(): # print(k + " --> " + str(v)) # cursor = users.find({'age':{'$gt':30}}) # 取多条 # for s in cursor: # print(s) # # print(s['name'], s['age']) # cursor = users.find().sort([('gender',1),('age',-1)]) # 多条件降序 # for s in cursor: # print(s) cursor = users.find().sort('age',-1).limit(2).skip(3) # 子集 for s in cursor: print(s)
Node.js mongoose
因为npm 库中的 mongodb没有这个好用,所以直接使用 mongoose
/** 1. 安装 npm i mongoose --save 2. 引入 let mongoose = require("mongoose"); 3. 连接数据库 mongoose.connect('mongodb://root:123456@192.168.112.9/odm_demo?authSource=admin',{useNewUrlParser:true}); 默认27017可以省略 通常只需连接一次,除非项目停止或服务器关闭,否则连接一般不会断开。 4. 断开连接(一般不需要) mongoose.disconnect(); 一般情况下,只需要连接一次; 除非项目停止服务器关闭,否则连接一般不会断开 - 监听Mongodb连接状态 - 在mongoose对象中,有一个属性叫做connection 表示的就是数据库连接 通过监视该对象的状态,可以来监听数据库的连接与断开 - 连接成功事件 mongoose.connection.once("open",function(){}); - 数据库断开事件 mongoose.connection.once("close",function(){}); */ const mongoose = require('mongoose'); mongoose.connect('mongodb://admin:admin@192.168.112.9/admin', {useNewUrlParser: true}); // 正确写法 mongoose.connection.once('open',function () { console.log("连接成功."); }); mongoose.connection.once('close',function () { console.log('数据库已断开.'); }); mongoose.disconnect(); // 一般不会调用
mongoose demo
const mongoose = require('mongoose'); mongoose.connect('mongodb://root:123456@192.168.112.9/odm_demo?authSource=odm_demo',{useNewUrlParser:true}); mongoose.connection.once('open',function (err) { if(err){ console.log(err); }else{ console.log("连接成功~~~"); } }); /** * Schema * Model * Document */ // 1. 定义 Schema let Schema = mongoose.Schema; // 2. 创建模式(约束) Schema 对象 let stuSchema = new Schema({ name: String, age: Number, gender:{ type: String, default:"female" }, address:String }); // 3. 通过Schema创建 Model (Collection),通过Model才能操作数据库 let StuModel = mongoose.model("child", stuSchema); // 集合名自动变复数。
let StuModel = mongoose.model("child", stuSchema,"child"); // 但是,如果第3个参数也传集合名称,则不会变复数。
// 4. 插入文档 Model.create(doc,function (err) {}); StuModel.create({ name:"紫霞仙子", age:18, // gender:"male", address:"水帘洞" },function (err) { if(!err){ console.log("插入成功."); } });
模型用法示例
require("./common/conn_mongo"); let StuModel = require('./model/users'); // 插入 Model.create(doc(s),[callback]) /* StuModel.create([ { name:"沙和尚", age:42, gender:"male", address:"流沙河" } ],function (err) { if(!err){ // console.log('插入成功~~~'); console.log(arguments); // 返回插入的内容 } }); StuModel.create( [{ name:'白骨精', age:90, address:'山洞' },{ name:'女王', age:38, address:'女儿国' }], function (err) { if(!err){ console.log('插入成功.') } } ); */ /** 查询: * Model.find(conditions,[projection],[options],[callback]) * 查询所有符合条件的文档 返回数组 * Model.findById(id,[projection],[options],[callback]) * 根据ID属性查询文档 * Model.findOne([conditions],[projection],[options],[callback]) * 查询符合条件的第一个文档 * * Model.count(conditions, [callback]) // 数量 * * - projection 投影 想要的字段 * 两种方式: {name:1, _id:0} * "name -_id" * - options 选项(skip, limit ) * {skip:2, limit:1} * - callback 回调函数 必传, 不传则不会查询 */ // StuModel.count({},function (err, count) { // count 是旧方法 StuModel.countDocuments({},function (err, count) { // 新方法 if(!err){ console.log(count); } }); StuModel.find({name:"八戒"},function (err,docs) { // 返回数组 if(!err){ if(docs.length > 0){ console.log(docs) // console.log(docs[0].name) }else{ console.log('docs is empty') } } }); StuModel.findOne({name:"唐僧"},function (err, doc) { // 返回文档对象, 就是Document if(!err){ if(doc){ // console.log(doc) console.log(doc.address); console.log(doc instanceof StuModel) // Document对象是Model的实例 }else{ console.log('no result') } } }); StuModel.findById('5d108a9fe15d35242c37666c',function (err, doc) { if(!err){ console.log(doc.name); } }); // // 投影 只取name age 不要 _id // StuModel.find({},{name:1, age:1, _id:0},function (err, docs) { // 对象参数 StuModel.find({},'name age -_id', {skip:1,limit:1}, function (err, docs) { // 字符串参数 if(!err){ if(docs.length > 0){ console.log(docs) } } }); /** 修改 * Model.update(conditions, doc, [options], [callback]) * Model.updateMany(conditions, doc, [options], [callback]) * Model.updateOne(conditions, doc, [options], [callback]) * Model.replaceOne(conditions, doc, [options], [callback]) * - conditions 查询条件 * - doc 修改后的对象 */ StuModel.updateOne({name:"唐僧"},{$set:{age:77}},function (err) { if(!err){ console.log('修改成功.'); } }); /** 删除 一般不用 * Model.remove(conditions, [callback]) * Model.deleteOne(conditions, [callback]) * Model.deleteMany(conditions, [callback]) */ // StuModel.deleteOne({name:'白骨精'},function (err) { // if(!err){ // console.log('删除成功.') // } // });
common/conn_mongo.js
let mongoose = require("mongoose"); mongoose.connect('mongodb://admin:admin@192.168.112.9/odm_demo?authSource=admin',{useNewUrlParser:true}); mongoose.connection.once('open',function (err) { if(err){ console.log(err); }else{ console.log("连接成功~~~"); } });
model/users.js
/** 定义模型 */ const mongoose = require('mongoose'); let Schema = mongoose.Schema; let stuSchema = new Schema({ name: String, age: Number, gender:{ type: String, default:"female" }, address:String }); // 有了model 即可CURD let UsrModel = mongoose.model("users", stuSchema); // 如果是复数,则名称不变 module.exports = UsrModel;
文档用法示例:
require('./common/conn_mongo'); let monster = require('./model/monster'); /** * Document 和 集合中的文档一一对应,Document是Model的实例 * 通过 Model 查询到的结果都是 Document */ let stu = new monster({ name:"奔波儿霸", age:888, gender:"male", address:"碧波潭" }); // console.log(stu); // Document对象的方法 Model#save([options],[fn]) // 而 create 是 Model 的方法 // stu.save(function (err) { // if(!err){ // console.log("写入成功~") // } // }); monster.findOne({},function (err, doc) { if(!err){ /** * update(update,[options],[callback])) * - 修改对象 * save([callback])) * update([callback])) */ // console.log(doc); // doc.update({$set:{age:22}},function (err) { // 过时的方法 // if(!err){ // console.log("修改成功.") // } // }); // doc.updateOne({$set:{address:"高老庄"}},function (err) { // if(!err){ // console.log("修改成功"); // } // }); // // 直接修改 // doc.age = 2000; // doc.save(); // 删除 // doc.remove(function (err) { // if(!err){ // console.log("师傅再见~") // } // }) /** * get(name) * set(name, value) * id 获取文档的 _id 属性 * toJSON() * toObject() document对象方法则失效 */ console.log(doc.get("name")); console.log(doc.address); // doc.set("name","猪悟能"); // 需要 save() 才能修改数据库 // doc.name = "奔波霸"; // doc.save(); console.log(doc.id); console.log(typeof doc); // Object let t = doc.toObject(); // 转为普通 JS 对象 delete t.__v; delete t._id; console.log(t); } });
model/monster.js
const mongoose = require('mongoose'); let Schema = mongoose.Schema; let stuSchema = new Schema({ name: String, age: Number, gender:{ type: String, default:"female" }, address:String }); let MonsterModel = mongoose.model("monsters", stuSchema); module.exports = MonsterModel;