事务机制
1.事务概念
在对数据的操作的过程中,涉及到一连串的操作,这些操作如果失败,会导致我们的数据部分变化了,部分没变化。这个过程就好比如你去吃早餐,你点完餐了,并且吃完早餐了,没付钱你就跑了,这不符合正常的流程,要保证你是否付钱了,现在有收款语音播报器,能清楚你是否付了钱了。为确保数据的可靠性,采用事务来进行处理。
事务涉及了四个基本特性(ACID):
- 原子性(atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(durability):已被提交的事务对数据库的修改应该是永久性的。
2. 使用事务
事务和其他支持事务的数据库一样,需要开启和提交事务,MongoDB事务涉及到wirteConcern和readConcern两个属性上
2.1wirteConcern
`wirteConcern决定了写操作需要落入到多少个节点才算成写入成功
writeConcern的属性有
1.w
w 为0时,对写入数据的正确性不关心,不需要确认
w 为1时, 数据写入到主节点就与客户端进行确认
w 为majority时,数据写入到大多数成员后,再与客户端进行确认
2. 具体的数字
j 为true时,数据写入到主节点持久化后,就与客户端进行确认
3.wtimeout 写入超时时间,防止一直等待的情况,超过超时时间未结束,写入失败
4. 具体的数字
落入到具体数字的节点才与客户端确认
// 包含延迟节点的3节点pss复制集
db.collection.insertOne({name:"李四"},writeConcern:{w:"majority"}})
// 等待延迟节点写入数据后才会响应
db.collection.insertOne({name:"王五"},{writeConcern:{w:3}})
// 超时写入失败
db.collection.insertOne({name:"小明"},{writeConcern:{w:3,wtimeout:3000}})
使用事务的话,大多数情况时使用{w:"majority"}
2.2 readConcern
读取处于事务中的数据时,需要知道从那读,以及能读取什么样的数据。
- readPreferce来解决从哪读的问题
其中选择节点读取的参数有如下:
(1)primary: 只选择主节点,默认模式;
(2)primaryPreferred:优先选择主节点,如果主节点不可用则选择从节点;
(3)secondary:只选择从节点;
(4)secondaryPreferred:优先选择从节点, 如果从节点不可用则选择主节点
(5)nearest:根据客户端对节点的 Ping 值判断节点的远近,选择从最近的节点读取。
具体使用:
# 通过 MongoDB 驱动程序 API
MongoCollection.withReadPreference(ReadPreference readPref)
# Mongo Shell(指定从哪读) 使用readPref简写
db.collection.find().readPref( "secondary" )
db.fsyncLock() 锁定写入
db.fsyncUnLock() 解锁写入
Tag: readPreference的拓展
readPreference只能控制一类节点,而Tag可用于控制一到多个节点,可实现定制服务,某些节点负责在线服务,某个服务负责报表服务。
具体使用
# 为复制集节点添加标签
conf = rs.conf()
conf.members[1].tags = { purpose: "online"}
conf.members[4].tags = { purpose: "analyse"}
rs.reconfig(conf)
#查询
db.collection.find({}).readPref( "secondary", [ {purpose: "analyse"} ] )
- readConcern解决能读取什么样的数据
决定从哪个节点读取数据后,还需要考虑读取什么样的数据,可选以下参数
(1) available:读取所有可用的数据;
(2) local:读取所有可用且属于当前分片的数据;
(3) majority:读取在大多数节点上提交完成的数据;
(4) inearizable:可线性化读取文档,仅支持从主节点读(线性);
(5) snapshot:读取最近快照中的数据,仅可用于多文档事务(不出现脏读、不可重复读、幻读);
脏读: 读取了事务回滚前的数据或未正确同步的数据
。
不可重复读: 事务A先读取了一条数据,此时事务B对该数据进行了更新,事务A再次读取了该数据,此时前后读取的数据不匹配
。
幻读: 事务A读取了N条数据,事务B对这N条数据进行了删除或新增操作,事务A重新读取数据时,数据的条数产生了变化,从而产生了幻读
。
- readConcern读取数据时引发脏读的原因以及解决办法
写入数据只到达主节点,没有到达其他节点,一旦主节点出现故障,从节点没来得及复制数据,从而导致脏数据的出现。可以设置写入落入大多数节点时再进行写入,虽然消耗了点性能,但保证了数据的不丢失。设置{readConcern: “majority”}。
- 安全的实现读写分离
安全的读写分离,有利于提高效率,并且确保数据安全,其中的措施是将写入和读取都使用majority这个值,确保数据在多个节点都是存在的,并且要选取备节点进行读取。
# 使用writeConcern+readConcern majority来解决
db.collection.insert({oid:101,sku:"kite",q:1},{writeConcern:{w:"majority"}})
db.collection.find({oid:101}).readPref("secondary").readConcern("majority")
5.事务流程
事务内的改变,事务外是无法获取的,直到提交事务后。
var session = db.getMongo().startSession()
# 开启事务
session.startTransaction()
var coll = session.getDatabase("test").getCollection("tx")
#事务内修改 {x:1, y:1}
coll.updateOne({x: 1}, {$set: {y: 1}})
#事务内查询 {x:1}
coll.findOne({x: 1}) //{x:1, y:1}
#事务外查询 {x:1}
db.tx.findOne({x: 1}) //{x:1}
#提交事务
session.commitTransaction()
# 或者回滚事务
session.abortTransaction()