MongoDB 在 4.0 版本支持了复制集的多文档事务

4.2 版本支持了分片集的多表、多行事务操作

事务四大特性

  • 原子性(Atomicity):事务必须是原子工作单元,对于其数据修改,要么全执行,要么全不执行。类似于 Redis 中我通常使用 Lua 脚本来实现多条命令操作的原子性。
  • 一致性(Consistency):事务在完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):由并发事务所做的修改必须与任何其他并发事务所作的修改隔离(简而言之:一个事务执行过程中不应受其它事务影响)。
  • 持久性(Durability):事务完成之后,对于系统的影响是永久性的。

控制 Session 的行为:Read Concern/Write Concern/Read Preference

一、MongoDB的应答机制

1、定义:

MongoDB应答机制就是说对于当前数据库的写入成功与否告知客户端(db.getLastError())。

如下:

mongoDB client发出写入(或更新)请求 ----> mongoDB Server端写入 ----> 通知客户端已经写入 OK

2、应答机制类型

  • 应答式写入(缺省情形,安全写入,适用于数据强一致性场景)
  • 非应答式写入(非安全写入,适用于数据弱一致性场景)

3、实现方式

通过 Write Concern 来实现,客户端驱动调用 db.getLastError() 方法,错误返回给客户端。//如果捕获到错误,则可以通过客户端定义的逻辑尝试再次写入或记录到特定日志等

二、什么是 writeConcern

1、定义:

Write Concern 描述了 MongoDB 写入到 mongod 单实例,副本集,以及分片集群时何时应答给客户端。writeConcern 决定一个写操作落到多少个节点上才算成功。类似于 MySQL 中的半同步复制。主要保证数据最终一致性。

2、如何配置:

  • 支持客户端灵活配置写入策略(writeConcern) //以满足不同场景的需求。
  • replica set or sharded cluster 全局设置 //从 MongoDB 4.4 开始,副本集和分片集群支持设置全局默认的 Write Concern。没有显式指定 Write Concern 的操作将会继承全局默认设置。使用 setDefaultRWConcern

说明:写关注需使用事务级的,不能在事务内为某个写操作单独指定写关注级别,会导致报错。

3、参数详解

MongoDB 支持的 WriteConncern 选项如下:

{ w: <value>, j: <boolean>, wtimeout: <number> }

(1) w: <number>,数据写入到 number 个节点才向用客户端确认

  • {w: 0}: 发起写操作,不关心是否成功;
  • {w: 1}: 默认的值,数据写入到 Primary 就向客户端发送确认
  • {w: n}: 表示写操作需要被复制到指定节点数才算成功
  • {w: "majority"} 适用于集群,要求写入操作已经传递到绝大多数投票节点以及主节点后向客户端发送确认 //适用于对数据安全性要求比较高的场景,该选项会降低写入性能

(2) j: <boolean>,写入操作的 journal 日志持久化后才向客户端确认

默认为 {j: false},如果要求 Primary 写入持久化了才向客户端确认,则指定该选项为 true

(3) wtimeout: <millseconds>,写入超时时间,仅 w 的值大于 1 时有效。

当指定 {w: } 时,数据需要成功写入 number 个节点才算成功,如果写入过程中有节点故障,可能导致这个条件一直不能满足,从而一直不能向客户端发送确认结果,针对这种情况,客户端可设置 wtimeout 选项来指定超时时间,当写入过程持续超过该时间仍未结束,则认为写入失败。

Standalone

//w 选择和 j 选项确定 mongod 实例何时确认写操作。

一个独立的 mongod 要么在内存中应用写操作,要么在写入磁盘日志后确认写操作。下表列出了独立程序的确认行为和相关的写注意事项:

写关注 j is unspecified j : true j : false
w: 1 In memory On-disk journal In memory
w: "majority" On-disk journal if running with journaling On-disk journal In memory

Replica Sets

1、{w: "majority"}

  • 副本集的任何带有数据的投票成员都可以对 "majority" 写操作的写确认做出贡献。
  • members[n].votes 大于 0 的 Hidden,delayed 和 priority 0 成员可以确认 "majority" 写入操作;
  • 延迟节点可以不早于配置的 secondaryDelaySecs 返回写确认

2、写入关注值 "majority" 的大多数是通过以下值中的较小者计算得出的

  • the majority of all voting members (including arbiters) vs. //所有投票节点的大多数
  • the number of all data-bearing voting members. //所有有数据的投票节点的数量

默认行为

3 节点复制集不作任何特别设定(默认值)。

w: "majority"

大多数节点确认模式,其中包括主节点

w: "all"

全部节点确认模式

三、setDefaultRWConcern

//从 MongoDB 4.4 开始,副本集和分片集群支持设置全局默认的 Write Concern。没有显式指定 Write Concern 的操作将会继承全局默认设置。使用 setDefaultRWConcern

1、作用

使用 setDefaultRWConcern 可以对副本集和分片集群支持设置全局默认的 Write Concern。

  • For replica sets, issue the setDefaultRWConcern command on the primary mongod
  • For sharded clusters, issue the setDefaultRWConcern on a mongos

2、使用方式

# Set Global Default Write Concern
db.adminCommand({
  "setDefaultRWConcern" : 1,
  "defaultWriteConcern" : {
    "w" : 2
  }
})

#Set Global Default Read Concern
db.adminCommand({
  "setDefaultRWConcern" : 1,
  "defaultReadConcern" : { "level" : "majority" }
})

#Set Global Default Read and Write Concern
db.adminCommand({
  "setDefaultRWConcern" : 1,
  "defaultWriteConcern" : {
    "w" : 2
  },
  "defaultReadConcern" : { "level" : "majority" }
})

四、journal日志

定义:

  • journal 是存储引擎存储数据时的一种辅助机制。
  • Journal 日志,是 MongoDB 的预写日志 WAL(类似 Mysql 的 Redo log)
  • mongodb 每 n(2~300) 毫秒往 journal 文件中 flush 一次数据

如何开启:

方式 1:

storage.journal.enabled 决定是否开启 journal
storage.journal.commitInternalMs 决定 journal 刷盘的间隔,默认为 100ms

方式 2:

通过写入时指定 writeConcern 为 {j: ture} 来每次写入时都确保 journal 刷盘

五、writeConcern 测试

5.1 非应答式写入图示

MongoDB 不对客户端进行应答,驱动会检查套接字,网络错误等。

mongo 192.168.1.154:27018/admin -uroot -proot123456

repl:PRIMARY>  db.version()
6.0.16

repl:PRIMARY> db
test

repl:PRIMARY> db.test.insert({name:"dbatest"},{writeConcern:{w:0}})
WriteResult({ })  //此处应答为空

repl:PRIMARY> db.test.find({},{_id:0})
{ "name" : "dbatest" }

5.2 应答式写入图示

应答式写入是默认值

MongoDB 会在收到写入操作并且确认该操作在内存中应用后进行应答,但不会确认数据是否已写入磁盘,同时允许客户端捕捉网络、重复 key 等等错误

示例:

repl:PRIMARY> db.test.insert( {count: 1},{writeConcern:{w:1}})
WriteResult({ "nInserted" : 1 })  //此处应答信息显示为 1个文档已插入

repl:PRIMARY> db.test.find({},{_id:0})
{ "name" : "dbatest" }
{ "count" : 1 }

5.3 带 journal应答式写入图示

确认写操作已经写入 journal 日志之后应答客户端,必须允许了日志功能,才能生效。写入 journal 操作必须等待直到下次提交日志时完成写入

提供通过 journal 来进行数据恢复

示例:

> db.test.insert( {count: 1},{writeConcern:{w:1,j:true}})

5.4 副本集应答写入图示

对于使用副本集的场景,缺省情况下仅仅从主(首选)节点进行应答。建议修改缺省的应答情形为特定数目或者 majority 来保证数据的可靠

示例:

(1) 在复制集测试 writeConcern 参数

db.test.insert( {count: 1}, {writeConcern: {w: "majority"}})
db.test.insert( {count: 1}, {writeConcern: {w: 2 }})
db.test.insert( {count: 1}, {writeConcern: {w: 3 }})

(2) 配置延迟节点,模拟网络延迟(复制延迟)

conf=rs.conf()
conf.members[0].secondaryDelaySecs = 5  //Memory从 0-100,0表示 rs.conf()显示的第一个,与 _id是几无关
conf.members[0].priority = 0
rs.reconfig(conf)

观察复制延迟下的写入,以及 timeout 参数

db.test.insert( {count: 1}, {writeConcern: {w: 3})
db.test.insert( {count: 1}, {writeConcern: {w: 3, wtimeout:3000 }})

(3) 修改 writeConcern 为全局值

//如果不希望每次在增删改时添加 writeConcern,可以通过设置 setDefaultRWConcern
db.adminCommand({
  "setDefaultRWConcern" : 1,
  "defaultWriteConcern" : {
    "w" : 2
  }
})

image-20260404155958845

六、注意事项

  • write concern 是一个性能和数据强一致性的权衡,应根据业务场景进行设定
  • 虽然多于半数的 writeConcern 都是安全的,但通常只会设置 majority,因为这是等待写入延迟时间最短的选择;
  • 不要设置 writeConcern 等于总节点数,因为一旦有一个节点故障,所有写操作都将失败;
  • 应对重要数据应用 {w: "majority"},普通数据可以应用 {w: 1} 以确保最佳性能。
  • 对于强一致性场景,建议 w>1 或者等于 majority,以及 journal 为 true
  • writeConcern 虽然会增加写操作延迟时间,但并不会显著增加集群压力,因此无论是否等待,写操作最终都会复制到所有节点上。设置 writeConcern 只是让写操作等待复制后再返回而已;