mongodb
# typora-root-url: ./images
# MongoDB
# 1. 概念分析
通过和 sql 概念比较进行理解
| SQL 概念 | MongoDB | 说明 |
|---|---|---|
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 数据记录行/文档 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| table joins | 表连接,MongoDB 不支持 | |
| primary key | primary key | 主键,MongoDB 自动将_id 字段设置为主键 |
MongoDB 是为现代应用程序开发人员和云时代构建的基于文档的通用分布式数据库。MongoDB 是一个基于文档的数据库,它将数据存储为一种类似 JSON 的文档。这种数据表现更自然,比传统的行列关系型数据库更具表现力。网站 https://www.mongodb.com/
# 2. 命令语句分析
# 2.1 库和表的修改
| SQL | MongoDB | 说明 |
|---|---|---|
| create database runoob; | use runoob | 创建 数据库,sql 语句需要以分号(;)结尾,mongodb 不用 |
| drop database runoob; | db.dropDatabase() | 删除数据库 |
| show databases; | show dbs | 查看数据库 |
| show tables; | show tables 或 show collections | 查看表 |
| create table{...}; | db.createCollection("tbtest") | mongo 中直接创建集合,而不用指定集合格式,集合的创建可指定其他选项,指定集合大小和索引. |
| 例:db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } ) 创建固定集合 mycol,集合空间 6142800KB,文档最大个数为 10000 个 | ||
| drop table tbtest; | db.tbtest.drop() | 删除表 |
# 2.2 数据表(文档)操作
MongoDB 中数据表成为集合,数据表中的行成为文档。文档数据是 BSON(Binary JSON)格式,即一种类似 JSON 的二进制形式的存储格式,BSON 有自我描述的特性,所以不需要像 SQL 一样预先定义表格式。
# 2.2.1 表插入语法比较
# mysql,插入前表结构必须存在
insert into mytbl(`id`, `name`) values(1,"gao"),(2,"shuai");
# mongo,要插入的表如果不存在会自动创建
db.mytbl.insert(document)
2
3
4
实例
# 直接插入
db.col.insert({title:"MongoDB 学习", name:"gaoshuai"})
# 先赋值到变量,再插入
data=({title:"MongoDB 学习", name:"gaoshuai"})
db.col.insert(data)
db.col.save(data)
# 查询
db.col.find()
2
3
4
5
6
7
8
9
10
# 2.2.2 更新表方法比较
# mysql
update table set name="" where name="gao" limit 1;
# mongo
db.collection.update(
<query>,
<update>,
{
upsert:<bollean>,
multi:<boolean>,
writeConcern:<document>
}
)
2
3
4
5
6
7
8
9
10
11
12
13
更新参数说明:
- query:update 的查询条件,类似 sql 的 where
- update:update 的对象和一些更新操作(如$,$inc,$set...)等,类似于 mysql 的 set
/chat/s2c_chatroomenter/
- upsert:可选参数,当 query 未查询到要更新的记录是,是否插入
- multi:可选参数,是否操作多行,类似于 mysql 的 limit 限制
- writeConcern:可选参数,抛出异常级别
例:
> db.col.update({"name":"gaoshuai"},{$set:{"name":"gao"}})
> db.col.update({"name":"gaoshuai"},{$set:{"name":"gao"}},{multi:true})
> db.col.update({"name":"gao"},{$set:{"name":"gao2"}},false,true)
2
3
可以看出 update 的可选操作可以显示指定,也可以顺序使用
通过==save==方法更新已有文档
db.collection.save(<document>,
{ writeConcern:<document> }
)
2
3
参数说明:
- document:文档数据
- writeConcern:可选参数,抛出异常级别
> db.col.save({
"_id" : ObjectId("5e952b55e69093e64642d744"),
"title" : "MongoDB 学习 save",
"name" : "gao"
})
2
3
4
5
该方法当找到相同主键时会更新记录,若未找到相同主键,会更新记录
# 2.2.3 查询方法比较
# mysql
select * from tblname where ...
# mongo
db.collection.find(query, projection)
2
3
4
参数说明:
- query:可选,查询条件
- projection:可选,使用投影操作符指定返回的键
示例
# 查询命令,pretty用于格式化显示文档,作用类似于mysql中以 \G 结尾
> db.col.find({"name":"gao"}).pretty()
> db.col.find({"name":"gao", "age":20}) # And 查询
2
3
mongo 除了 find()方法外,还有一个 findOne() 方法,只返回一个文档。但是 findOne 的查询结果,不能调用 pretty()方法。
查询语句比较
| 操作 | 格式 | 范例 | RDBMS 查询 |
|---|---|---|---|
| 等于 | { | db.col.find({"name":"gao"}) | where name="gao" |
| 小于 | { | db.col.find({"likes":{$lt:50}}) | where likes < 50 |
| 小于或等于 | { | db.col.find({"likes":{$lte:50}}) | where likes <= 50 |
| 大于 | { | db.col.find({"likes":{$gt:50}}) | where likes > 50 |
| 大于或等于 | { | db.col.find({"likes":{$gte:50}}) | where likes >= 50 |
| 不等于 | { | db.col.find({"likes":{$ne:50}}) | where likes != 50 |
| 与 | { | db.col.find({"name":"gao", "age":20}) | where name="gao" and age=20 |
| 或 | {$or:[{ | db.col.find({$or:[{"name":age},{"age":20}]}) | where name="gao" or age=20 |
| 类型查询 | $type | {"name":{$type:2}} |
$type 查询可使用下面命令
> db.col.find({"title" : {$type : 2}})
# 或
> db.col.find({"title" : {$type : 'string'}})
2
3
其中 type 后可以使用数字表示数据类型,也可以使用类型描述符,部分类型和描述符如下
| 类型 | 数字 | 类型 | 数字 |
|---|---|---|---|
| Double | 1 | String | 2 |
| Object | 3 | Array | 4 |
| ... | ... | ... | ... |
# 2.2.4 其它查询限定
limit 方法和 skip 方法,limit 方法用于限定显式数量,skip 用于跳过指定数量的数据。
# mysql > select * from col limit 2 # 限定2条 > select * from col limit 1,2 # 跳过1条后,限定2条 # mongo > db.col.find().limit(2) # 限定2条 > db.col.find().limit(2).skip(1) # 跳过1条后,限定2条1
2
3
4
5
6sort 方法,sort 方法用于指定排序的字段,1 为升序排列,-1 为降序排列。
# mysql > select * from col order by asc; # 升序 > select * from col order by desc; # 降序 # mongo > db.col.find().sort({name:1}) # 升序 > db.col.find().sort({_id:-1}) # 降序1
2
3
4
5
6createIndex 创建索引,索引可以指定索引排序和一些其他参数。
# mysql > create index index_name on mytable(name(length)); # 建表时增加的索引 > alter table mytable add index index_name(name); # 修改表结构时增加索引 # mongo > db.col.createIndex({"name":1}) # 升序索引 > db.col.createIndex({"name":1,“title”:-1},{backgrouond:true}) # 建立多字段索引,并在后台创建1
2
3
4
5
6aggregate 聚合方法,用于处理数据(如平均值,求和等),类似于 mysql 的 count,sum 等。
# mysql > select name as id, count(*) as num from db; # 以name分组统计 # mongo > db.col.aggregate([{$group: {id:"$name", num:{$sum:1}}}]) # 以name分组统计 # 好奇使用下面的语句试了一下,竟然报错了 > db.col.find(...).aggregate([{$group: {id:"$name", num:{$sum:1}}}])1
2
3
4
5
6后来发现在使用 aggregate 时需要使用管道方法进行查询和统计
> db.articles.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, # match过滤数据 { $group: { _id: null, count: { $sum: 1 } } } # group 分组归档统计 ] );1
2
3
4聚合方法有 sum, avg, min, max, push, addToSet, first, last 管道操作有 project, match, limit, skip, unwind, group, sort, geoNear 聚合和管道的使用可参照链接 https://www.runoob.com/mongodb/mongodb-aggregate.html
# 2.3 再看索引管理
db.col.getIndexes() # 查看集合索引
db.col.totalIndexSize() # 查看集合索引大小
db.col.dropIndexes() # 删除集合所有索引
db.col.dropIndex("index_name") # 删除集合指定索引
db.col.createIndex({"name":1})
2
3
4
5
mongo 的索引和 mysql 的索引很像,可以建立单字段的索引,也可以建立多字段的索引。单字段索引可以指定升序排列还是降序排列。当建立多字段索引时,例如对{A,B}列建立索引,则当查询 A 或 A,B 时,会触发使用索引,而查询 B 或 B,A 时不会触发使用索引。索引的使用和索引建立的顺序与查询的顺序是一致的。
# 2.4 删除方法比较
# mysql
delete from tabname where ...;
# mongo
db.collection.remove(
<query>,
{ justOne:<boolean>, writeCocern:<document> }
)
2
3
4
5
6
7
参数说明:
- query:要删除文档的条件
- justOne:(可选)删除一条数据还是多条,默认 false,删除所有匹配
- writeConcern:(可选)抛出异常的级别
示例:
> db.col.remove({"name":"gao"})
> db.col.remove({}) # 删除所有数据,类似mysql的truncate操作
> db.getCollection("user_equipment").update({_id:10869},{$inc:{equipmentCount:-2},$unset:{"2":0,"4":""}}) #删除装备
2
3
# 3. 数据库备份
mysql 使用 musqldump 进行备份
# 备份
mysqldump -h localhost -P 3306 -uroot -P --database mydatabase > /data/dbbackup
# 恢复
mysql -uroot -p mydatabase < /data/db/backup
2
3
4
mongo 使用 mongodump 进行备份
# 备份
> mongodump -h localhost -d runoob -o /data/dump/
# 恢复
> mongorestore -h localhost -d runoob /data/dump/
2
3
4
redis 备份 使用 save 或 bgsave 命令,恢复只需要被备份的 dump.rdb 放到数据库目录就可以了
# 4. 在 python 中使用 mongo
看一个例子吧
import pymongo
myclient = pymongo.MongoClient("mongodb://localhost:27017/")
mydb = myclient["test"]
mycol = mydb["runoob"]
x = mycol.find_one()
print(x)
for x in mycol.find():
print(x)
for x in mycol.find({"name":"gao"}):
print(x)
for x in mycol.find({},{"_id":0, "name":1, "age":1}):
print(x)
for x in mycol.find({},{"_id", "name", "age"}): #find执行通过,mongo中不支持的projection语法
print(x)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
查询操作和 mongo 的 find 语法是一样的,有意思的是,查询后的映射语法这里是支持 set 格式的,而在 mongo 中不支持。python 在初始化查询操作前后检查参数格式,接收的参数格式是 dict 或 set,如果是 set,会自动转为 dict。
下面是一个使用副本集的例子:
import pymongo
from pymongo import WriteConcern, ReadPreference
url = '''mongodb://localhost:27011,localhost:27012,localhost:27013/?replicaSet=rs0'''
myclient = pymongo.MongoClient(url)
mydb = myclient.get_database("test", read_preference=ReadPreference.PRIMARY)
status = myclient.admin.command('replSetGetStatus')
mycol = mydb.get_collection(name="runoob", read_preference=ReadPreference.PRIMARY)
# 直接查询
for i in mycol.find():
print i
# 使用session查询,具体和上面有多大区别还不太清楚
with myclient.start_session() as session:
with session.start_transaction():
cursor = mycol.find({"name":{"$type":2}}, session=session)
for i in cursor:
print(i)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5. 原子操作
mongodb 不支持事务,但是 mongodb 提供了许多原子操作,比如文档的保存、修改、删除等
{$set: {field: value }} # 给键设定一个值,若值不存在就创建一个
{$unset:{field:1}} # 删除一个键
{$inc: {field: value}} # 对数字型的值递增
{$push: {field: value}} # 把value追加到field里面去,要求field是数组,若field不存在,创建一个
{$pushAll :{field: value_array}} # 追加多个值到数组字段
{$pull : {field: value}} # 从数组内删除一个等于value的值
{$addToSet:{field:value}} # 增加一个值到数组内,而且只有当这个值不在数组内才增加
{$pop: {field:1}} # 删除数组的最后一个元素
{$rename: {old_field_name: new_field_name}} # 修改字段名
{$bit: {field: {add: 5}}} # 对integer的位操作
# 使用示例
> t.update({"names":{$type:2}}, {$rename:{"names":"name"}}) #修改字段名
2
3
4
5
6
7
8
9
10
11
12
13
# 6. 副本集
# 6.1 MongoDB 副本集
monbodb 的副本集采用投票机制确定主节点,所以副本集节点的数量应该是基数个。节点间同步通过同步操作 oplog 实现,结构图如下:

使用 docker 测试副本集,先创建 3 个容器(注意端口号上限)
$ docker run --name mongo1 -p 27017:27017 -d mongo --replSet "rs0"
$ docker run --name mongo2 -p 37017:27017 -d mongo --replSet "rs0"
$ docker run --name mongo3 -p 47017:27017 -d mongo --replSet "rs0"
2
3
进入容器,启动 mongo 客户端程序,进行测试,发现报错了,我们需要关注 errmsg 信息
> show dbs
2020-04-15T09:07:08.792+0000 E QUERY [js] uncaught exception: Error: listDatabases failed:{
"operationTime" : Timestamp(0, 0),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(0, 0),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
初始化副本集,
> var config={"_id":"rs0","members":[{_id:0,host:"172.17.0.4:27017"},{_id:1,host:"172.17.0.2:27017"},{_id:2,host:"172.17.0.3:27017"}]}
> rs.initiate(config)
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1586942602, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1586942602, 1)
}
# 测试一下副本集主节点
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
rs0:PRIMARY> db.runoob.insert({"name":"马云"})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.runoob.find()
{ "_id" : ObjectId("5e96d357eacf82178316aaad"), "name" : "高帅" }
{ "_id" : ObjectId("5e96d3dceacf82178316aaae"), "name" : "马云" }
# 测试副本集 辅助节点,初始节点是不能查询的,可以使用rs.slaveOk() 开启查询
rs0:SECONDARY> show dbs
2020-04-15T09:31:02.876+0000 E QUERY [js] uncaught exception: Error: listDatabases failed:{
"operationTime" : Timestamp(1586943053, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1586943053, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
rs0:SECONDARY> use test
switched to db test
rs0:SECONDARY> db.runoob.find()
{ "_id" : ObjectId("5e96d357eacf82178316aaad"), "name" : "高帅" }
{ "_id" : ObjectId("5e96d3dceacf82178316aaae"), "name" : "马云" }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 6.2 MongoDB 管理命令
> rs.conf() # 查看副本集配置
> rs.status() # 查看副本集众泰
> rs.initiate(config) #初始化副本集
> rs.isMaster() # 查看是否为主节点
> db.getMongo().setSlaveOk(); # 赋予副本集副本节点查询数据库的权限
> rs.add("ip:port") # 新增副本集节点
> rs.remove("ip:port") # 删除副本集节点
> rs.addArb("ip:port") #添加仲裁节点
> rs.reconfig(conf) # 覆盖原有配置,注意,如果修改的节点为主节点或副节点,若节点数量不符合要求会修改失败
2
3
4
5
6
7
8
9
# 8. 其他
副本集每个成员都有一个状态
| 数字 | 状态(state) | 状态描述(stateStr) |
|---|---|---|
| 0 | STARTUP | 还不是任何集合的活动成员。所有的成员启动所在该状态 |
| 1 | PRIMARY | 主节点,接受写操作 |
| 2 | SECONDARY | 副节点 |
| 3 | RECOVERING | 自检节点,可以选举 |
| 5 | STARTUP2 | 成员加入集合,正在运行初始化同步 |
| 6 | UNKNOWN | 未知 |
| 7 | ARBITER | 仲裁节点,仅参与选举 |
| 8 | DOWN | 不可达,失去连接 |
| 9 | ROLLBACK | 正在回滚,数据不可读 |
| 10 | REMOVED | 将要被移除 |