MongoDB - 事务
写操作事务
writeConcern - w
writeConcern决定写操作落到多少节点上才算成功,其取值包括:
- 0:发起写操作,不关心结果
- n 1 <= n <= 集群最大数据节点数:写操作复制到n个节点才算成功。
- majority: 写操作被复制到大多数节点才算成功
发起写操作的线程将阻塞到写操作到达指定节点数才算成功;三节点复制集,默认是1,即主节点写好了就成了; 对于重要数据设置w:majority
,对于普通数据设置w:1
语法:
db.test.insert({count:1}, {writeConcern:{w:3,wtimeout:3000}})
journal - j
journal决定写操作到什么程度才算成功
- true: 写操作落到journal文件中
- false: 写到内存就算成功
读操作事务
读数据,主要有两个问题:
- 从哪里读?- readPreference
- 什么样的数据可以读?(隔离性) - readConcern
readPreference
- primary: 只从主节点读
- primaryPreferred:优先主节点
🌰 用户下单后转到订单详情页,可能从节点还没有复制到新订单 - secondary: 只从从节点
🌰 生成报表 - secondaryPreferred: 从节点优先
🌰 用户查询自己的历史订单 - nearest: 选择最近的节点
🌰 用户上传的图片分发到全世界
语法:
db.test.find({a:123}).readPref("secondary")
Tag
例如五节点复制集,三个硬件好一点的,打上tag"online",另外两个打上tag"analyse",生成报表只选择"analyse"节点,如图所示:
readConcern
这个节点上哪些数据是可以读的:
- available: 所有可用数据
- local:所有可用且属于当前分片
local是默认值,在复制集上local和available无区别;只是在分片集上,当做chunk迁移时,只读当前分片,否则把还没迁移成功的另外个分片上迁移中的数据也读出来 - majority:大多数节点提交完成的数据
实现方式:和MySQL的MVCC类似,节点上保存多个版本的数据,根据需求返回不同版本数据。使用majority可以有效避免“脏读”。
首先,MongoDB对事务的支持更多指的是一个写操作能否被持久化下来,所以“提交”的概念就是,写到了多数节点(那么数据就不会丢失,永远持久化了),在这个层面上,读majority就不会“脏读”,读到的数据都是落到大多数节点的,而不会因为节点crash某个写操作在读之后丢失了(相当于回滚了)
- linearizable:可线性化读取文档,保证该“读”能读到上一个“写”,保证操作的线性顺序
- snapshot:读取最近快照中的数据 (隔离级别最高,相当于serializable)
安全的读写分离
readConcern和writeConcern配合实现安全的读写分离
🌰用户下单,并从从节点读订单数据:
不安全:
db.orders.insert({oid:100, sku:"kite", q:1})
db.orders.find({oid:100}).readPref("secondary")
安全:
db.orders.insert({oid:100, sku:"kite", q:1},{writeConcern:{w:"majority"}})
db.orders.find({oid:100}).readPref("secondary").readConcern("majority")
多文档事务
mongoDB 4.2之后全面支持了多文档事务,但是推荐还是能不用尽量不用,尽量通过合理的数据模型设计来规避事务的必要性。
语法:
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docOne);
collection.insertOne(clientSession, docTwo);
clientSession.commitTransaction();
}
注意:
- 多文档事务的读必须从主节点读
- 使用MongoDB4.2兼容的驱动
- readConcern只应该在事务级别设置,不能在每次读写操作上