collection:相当于db的表
document:相当于表的记录
mongod --dbpath D:\programs\mongodb-4.2.8\data\db
replica set模式其实就是主从模式。
做mongo的启动配置文件:
storage:
dbPath: D:\programs\mongodb-4.2.8\data\db1
journal:
enabled: true
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: D:\programs\mongodb-4.2.8\log\mongo1.log
# network interfaces
net:
port: 27018
bindIp: 127.0.0.1
注意
:dbPath和systemLog文件夹要事先建好!
将mongo安装为windows服务:
mongod --config D:\programs\mongodb-4.2.8\config\mongo1.config --serviceName "MongoDB27018" --serviceDisplayName "MongoDB27018" --replSet "myRepl" --install
在任务管理器里手工启动之即可。
类似的,安装并启动复制集里的另外2个节点:
mongod --config D:\programs\mongodb-4.2.8\config\mongo2.config --serviceName "MongoDB27019" --serviceDisplayName "MongoDB27019" --replSet "myRepl" --install
mongod --config D:\programs\mongodb-4.2.8\config\mongo3.config --serviceName "MongoDB27020" --serviceDisplayName "MongoDB27020" --replSet "myRepl" --install
测试3个节点是否正常启动:
mongo --host 127.0.0.1 --port 27018
mongo --host 127.0.0.1 --port 27019
mongo --host 127.0.0.1 --port 27020
在任一节点配置复制集(这一步会分出主从节点):
rs.initiate({
_id: 'myRepl',
members: [
{_id: 0, host: '127.0.0.1:27018'},
{_id: 1, host: '127.0.0.1:27019'},
{_id: 2, host: '127.0.0.1:27020'}]
})
查看复制集状态:
rs.status()
返回结果:
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
...
},
{
"_id" : 1,
"name" : "127.0.0.1:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
...
},
{
"_id" : 2,
"name" : "127.0.0.1:27020",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
...
}
从结果里的stateStr字段可以看出,27018那个节点为主节点,其它两个节点为从节点。
如果我们把主节点down掉,很快就发现,27019被选为主节点。我们接着将27019也停掉,这个时候仅剩的1个节点27020节点就没法被选为主了,因为没达到半数以上(3个节点的半数为2)。
直接执行:
mongo
如果连接的是复制集里的从节点,在执行正式命令前,要先执行:
rs.slaveOk();
才可用。
use testdb;
即可。
#建表
db.createCollection("t_test")
#删表
db.t_test.drop()
#插入记录
db.t_test.insert([{"id":1, "name":"lip", "age":400},{"id":2, "name":"yibb", "age":100}])
# 查询,select * from t_test where id = 1
db.t_test.find({id:1})
# select * from t_test where id > 0 and id < 10
db.t_test.find({id: {$gt:0, $lt:10}})
# update t_test set age=40 where id=1
db.t_test.update({id:1}, {$set : {age: 40}})
# 获得表的索引
db.t_test.getIndexes()
# 对id列创建唯一索引,后续插入如有重复,mongo会报错:dup key
db.t_test.ensureIndex({"id":1},{"unique":true})
db.t_test.createIndex({"id":1},{"unique":true})
oplog在local库里,使用如下命令查看:
use local;
db.oplog.rs.find({"ns":"testdb.t_test"});
ns由“库名.集合名”组成。
结果形如:
{ "ts" : Timestamp(1653546445, 2), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "i", "ns" : "testdb.t_test", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T06:27:25.772Z"), "o" : { "_id" : ObjectId("628f1dcd6b0c17818fe022ba"), "id" : 1, "name" : "liping", "age" : 40 } }
{ "ts" : Timestamp(1653546445, 3), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "i", "ns" : "testdb.t_test", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T06:27:25.772Z"), "o" : { "_id" : ObjectId("628f1dcd6b0c17818fe022bb"), "id" : 2, "name" : "yibei", "age" : 10 } }
{ "ts" : Timestamp(1653548898, 1), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.t_test", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "o2" : { "_id" : ObjectId("628f1dcd6b0c17818fe022ba") }, "wall" : ISODate("2022-05-26T07:08:18.482Z"), "o" : { "$v" : 1, "$set" : { "age" : 45 } } }
{ "ts" : Timestamp(1653549802, 1), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "d", "ns" : "testdb.t_test", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T07:23:22.075Z"), "o" : { "_id" : ObjectId("628f1dcd6b0c17818fe022ba") } }
{ "ts" : Timestamp(1653549802, 2), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "d", "ns" : "testdb.t_test", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T07:23:22.075Z"), "o" : { "_id" : ObjectId("628f1dcd6b0c17818fe022bb") } }
我们先后插入2条记录,更新1条记录,再删除2条记录。
op=i表示insert
op=u表示update
op=d表示delete
特别的,如果我们对集合建索引,生成的oplog为:
{ "ts" : Timestamp(1653546434, 1), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "c", "ns" : "testdb.$cmd", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T06:27:14.938Z"), "o" : { "create" : "t_test", "idIndex" : { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "testdb.t_test" } } }
{ "ts" : Timestamp(1653548658, 2), "t" : NumberLong(1), "h" : NumberLong(0), "v" : 2, "op" : "c", "ns" : "testdb.$cmd", "ui" : UUID("761a9204-fac7-41f1-950b-34e0b9cd0a95"), "wall" : ISODate("2022-05-26T07:04:18.922Z"), "o" : { "createIndexes" : "t_test", "v" : 2, "unique" : true, "key" : { "id" : 1 }, "name" : "id_1" } }
其中,第一条是mongo自动为我们建的_id索引,第二条的id索引是我们自己建的。建索引的op=c
上述结果可用:
db.oplog.rs.find({"ns":"testdb.$cmd"});
命令获得。
mongo里单文档的增删改都是原子性的,无需事务。
事务针对的是多文档的原子性。一个典型的操作是字段自增,要先读出当前值,再自增。这种原子性只能用事务来保证。
单机mongo不支持事务。只有sharding cluster和replica set模式才支持事务。
一个事务的例子:
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
testCollection = session.getDatabase("testdb").t_test;
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );
try {
testCollection.updateOne( { id: 1 }, { $set: { age: 53 } } );
testCollection.insertOne( {"id":2, "name":"xixi", "age":20} );
} catch (error) {
session.abortTransaction();
throw error;
}
session.commitTransaction();
session.endSession();
1)表结构不明确且数据不断变大
MongoDB是非结构化文档数据库,扩展字段很容易且不会影响原有数据。内容管理或者博客平台等,例如圈子系统,存储用户评论之类的。
2)更高的写入负载
MongoDB侧重高数据写入的性能,而非事务安全,适合业务系统中有大量“低价值”数据的场景。本身存的就是json格式数据。例如做日志系统。
3)数据量很大或者将来会变得很大
Mysql单表数据量达到5-10G时会出现明显的性能降级,需要做数据的水平和垂直拆分、库的拆分完成扩展,MongoDB内建了sharding、多数据分片的特性,容易水平扩展,比较好的适应大数据量增长的需求。
4)高可用性
自带高可用,自动主从切换(replica set模式)
不适用的场景:
MongoDB目前不支持join操作,需要复杂查询的应用也不建议使用MongoDB。
一些结论:
与ES一样,mongodb也有所谓分片和replica。
mongodb的分片(以及ES的索引分片),就类似于MySQL的分库分表,只不过mysql的分库分表需要手工做,mongodb和ES则自动为我们做了分片。
mongodb的replica,类似于mysql的主从(可以是一主多从)。
一般来说,MongoDB将有关联的数据存储在一起(内嵌文档),所以很多操作不像MySQL,需要做多表的关联操作。
MongoDB的内嵌模型可以给应用程序提供很好的数据查询性能,因为基于内嵌模型,可以通过一次数据库操作得到所有相关的数据。同时,内嵌模型可以使数据更新操作变成一个原子写操作。然而,内嵌模型也可能引入一些问题,比如说文档会越来越大,这样就可能会影响数据库写操作的性能,还可能会产生数据碎片(data fragmentation)。
MongoDB数据类型丰富,查询功能强大,还有文本搜索功能和地理空间计算,强大的数据分析和统计能力。
缺点:没有join ,连表操作能力弱,所以在复杂查询时,还是关系型数据库更胜一筹。
MongoDB在内存充足的情况下数据都放在内存且有完整的索引支持,查询效率较高。人们有时把mongodb跟redis对比就是因为此点。
mongodb为提高查询效率,也要建索引,避免全表扫描。
索引创建过程中锁的使用情况,4.2+版本不会在索引创建过程中锁表。参见文章
writeConcern是确定写入时需要写入几个节点的配置,格式为:
{ w: , j: , wtimeout: }
其中:
w=1就是主节点确认即可,这是默认值。
w=2代表需要两个节点确认
w=majority代表需要大多数节点确认,2/3
writeConcern在w>1的时候可以设置超时时间
j:指定写入请求是否确认已写入磁盘预写日志。true时会确保写入操作已经写入w指定数量节点的预写日志中。
wtimeout:指定写入操作超时时间(毫秒)。
readConcern是读取数据的选项,有如下几种:
local:当前实例读取。查询从当前实例返回的数据不保证数据已写入大多数副本集成员(可能因故障而回滚)。当读偏好为primary或者读偏好为secondary但因果一致性为true时,默认值为“local”。
available:任一可得的实例返回。查询从实例返回的数据不保证数据已写入大多数副本集成员(可能因故障而回滚)。当读偏好为secondary且因果一致性为false时,默认值为“available”。也就是说,available仅针对readPreference=secondary有意义。
majority:查询返回已被大多数副本集成员确认的数据。读取操作返回的数据是持久的,即使发生故障也是如此。为了满足读取关注“majority”,副本集成员在majority提交点从其内存数据视图快照返回数据。因此,读取关注 "majority"在性能成本上与其他读取关注相当,并不会有过高代价。
需注意:仅当事务写关注也为“majority”时,读关注“majority”才提供数据保证。
linearizable: 查询返回在读操作开始之前完成的所有成功的majority确认写入数据。如遇并发执行的写入操作,查询动作会挂住等待写入传播到多数副本集后再返回结果。所谓线性,指的是写-读是顺序发生的。
读取操作后如果多数副本集崩溃重启,此时如果writeConcernMajorityJournalDefault默认为true,则数据保证持久。如果writeConcernMajorityJournalDefault设为false,则写关注“majority”可能回滚。
对于因果一致性的会话和事务,linearizable不可用;读偏好为primary时,可设置读关注linearizable;
snapshot:如果事务要求因果一致性,在事务提交时写关注"majority",则保证事务操作从多数提交的数据的快照中读取,且结果保证与事务开始之前数据的因果一致性相同。 如果事务不要求因果一致性,在事务提交时写关注"majority",则保证事务操作从多数提交的数据的快照中读取。
在事务下,readConcern仅支持local、majority、snapshot三种级别。
需要特别指出的是:
readConcern=majority选项主要解决脏读问题,比如用户从 MongoDB 的 primary 上读取了某一条数据,但这条数据并没有同步到大多数节点,然后 primary 就故障了,重新恢复后 这个primary 节点会将未同步到大多数节点的数据回滚掉,导致用户读到了『脏数据』
另外readConcern
能保证读到的数据『不会发生回滚』,但并不能保证读到的数据是最新的,这个官网上也有说明:
Regardless of the read concern level, the most recent data on a node may not reflect the most recent version of the data in the system.
有用户误以为,readConcern
指定为 majority 时,客户端会从大多数的节点读取数据,然后返回最新的数据。
实际上并不是这样,无论何种级别的 readConcern
,客户端都只会从『某一个确定的节点』(具体是哪个节点由 readPreference 决定)读取数据,该节点根据自己看到的同步状态视图,只会返回已经同步到大多数节点的数据。
注意
:无论readConcern还是writeConcern,影响的是mongodb所有的读写行为,而不仅仅是事务!
MongoDB driver支持以下几种read-reference:
primary:默认模式,一切读操作都路由到replica set的primary节点
primaryPreferred:正常情况下都是路由到primary节点,只有当primary节点不可用(failover)的时候,才路由到secondary节点。
secondary:一切读操作都路由到replica set的secondary节点
secondaryPreferred:正常情况下都是路由到secondary节点,只有当secondary节点不可用的时候,才路由到primary节点。
nearest:从延时最小的节点读取数据,不管是primary还是secondary。对于分布式应用且MongoDB是多数据中心部署,nearest能保证最好的data locality。
如果使用secondary或者secondaryPreferred,那么需要意识到:
(1) 因为延时,读取到的数据可能不是最新的,而且不同的secondary返回的数据还可能不一样;
(2) 对于默认开启了自动数据均衡的sharded collection,由于还未结束或者异常终止的chunk迁移,secondary返回的可能是有缺失或者多余的数据
(3) 在有多个secondary节点的情况下,选择哪一个secondary节点呢,简单来说是“closest”即平均延时最小的节点,具体参见Server Selection Algorithm