mongoDB的事务
<p>官网传送门:</p><p>https://docs.mongodb.com/manual/core/replica-set-write-concern/</p>
<p>https://docs.mongodb.com/manual/core/transactions/#read-concern-write-concern-read-preference</p>
<p><span class="fontstyle0"><strong><span style="font-size: 16px">MongoDB ACID 多文档事务支持</span></strong><br></span></p>
<table class="NormalTable">
<tbody>
<tr>
<td width="393"><span class="fontstyle0">事务属性</span></td>
<td width="390"><span class="fontstyle0">支持程度</span></td>
</tr>
<tr>
<td width="393"><span class="fontstyle2">Atomocity 原子性</span></td>
<td width="390"><span class="fontstyle0">单表单文档 : 1.x 就支持<br><span class="fontstyle0">复制集多表多行:4.0 复制集<br>分片集群多表多行4.2</span></span></td>
</tr>
<tr>
<td width="393"><span class="fontstyle2">Consistency 一致性</span></td>
<td width="390"><span class="fontstyle0">writeConcern, readConcern (3.2)</span></td>
</tr>
<tr>
<td width="393"><span class="fontstyle2">Isolation 隔离性</span></td>
<td width="390"><span class="fontstyle0">readConcern (3.2)</span></td>
</tr>
<tr>
<td width="393"><span class="fontstyle2">Durability 持久性</span></td>
<td width="390"><span class="fontstyle0">Journal and Replication</span></td>
</tr>
</tbody>
</table>
<p> </p>
<p><strong>Atomocity 原子性 </strong></p>
<p><span class="fontstyle0">一个事务作为一个提交单位,要么一起成功要么一起失败,分布式关注的重点就是多节点之间数据的原子性控制。</span></p>
<p><strong>Consistency 一致性 </strong></p>
<p><span class="fontstyle0">读和写的一致性,会不会读到脏数据。</span></p>
<p><strong>Isolation 隔离性 </strong></p>
<p><span class="fontstyle0">每个事务相互之间是独立的,每个事务内与主表之间也是互不影响、相互独立的。<br><strong>Durability 持久性 </strong></span></p>
<p><span class="fontstyle0">数据落盘持久化,通过记录Journal日志文件和备份副本。</span></p>
<p>先从mongoDB的一致性相关的writeConcern, readConcern说。</p>
<h3><span class="fontstyle0">什么是 writeConcern ?</span></h3>
<p><span class="fontstyle0"><span class="fontstyle0">writeConcern 决定一个写操作落到多少个节点上才算成功。writeConcern 的取值包括:<br><span class="fontstyle2">• <span class="fontstyle0">0:发起写操作,不关心是否成功;<br><span class="fontstyle2">• <span class="fontstyle0">1~集群最大数据节点数:写操作需要被复制到指定节点数才算成功;默认是1。<br><span class="fontstyle2">• <span class="fontstyle0">majority:写操作需要被复制到大多数节点上才算成功。</span></span></span></span></span></span></span></span></p>
<p><span class="fontstyle0"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0">• all:写入所有节点才算成功。<br>发起写操作的程序将阻塞到写操作到达指定的节点数为止 </span></span></span></span></span></span></span></span></p>
<h3><span class="fontstyle0"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><br>默认情况 w:“1”</span></span></span></span></span></span></span></span></h3>
<p> <img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621224558611-1749107692.png" alt="" loading="lazy"></p>
<p> </p>
<h3><span class="fontstyle0">大多数节点确认(即一半以上节点) w: “majority” </span></h3>
<p><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621224722902-1317813042.png" alt="" loading="lazy"></p>
<h3><span class="fontstyle0">全部节点确认 w: “all” </span></h3>
<p><span class="fontstyle0"><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621224925583-1117135220.png" alt="" loading="lazy"></span></p>
<p> </p>
<p> </p>
<p> </p>
<h3><span class="fontstyle0">j:true <br></span></h3>
<p> <span class="fontstyle0">writeConcern 可以决定写操作到达多少个节点才算成功,journal 则定义如何才算成<br>功。取值包括:<br><span class="fontstyle2">• <span class="fontstyle0">true: 写操作落到 journal 文件中才算成功;<br><span class="fontstyle2">• <span class="fontstyle0">false: 写操作到达内存即算作成功。 </span></span></span></span></span></p>
<p><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0">数据库数据写入的顺序 数据在内存中先写日志文件(journal 中落地持久化日志文件),再写数据文件<br><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621225231639-94576831.png" alt="" loading="lazy"></span></span></span></span></span></p>
<p> </p>
<p> </p>
<p> mongoDB shell中使用</p>
<div class="cnblogs_code">
<pre>db.test.insert( {count: 1}, {writeConcern: {w: "majority"<span style="color: rgba(0, 0, 0, 1)">}})
db.test.insert( {count: </span>1}, {writeConcern: {w: 3<span style="color: rgba(0, 0, 0, 1)"> }})
db.test.insert( {count: </span>1}, {writeConcern: {w: 4 }})</pre>
</div>
<p> </p>
<h3>readPreference与<span class="fontstyle0">readConcern <br></span></h3>
<p><span class="fontstyle0">readPreference 设置 分布式数据库从哪里读<br></span></p>
<p><span class="fontstyle0"><span class="fontstyle0">readConcern 什么样的数据可以读<br></span></span></p>
<h4><span class="fontstyle0">readPreference </span></h4>
<p><span class="fontstyle0">readPreference 决定使用哪一个节点来满足</span><span class="fontstyle0">正在发起的读请求。可选值包括:</span></p>
<p><span class="fontstyle0"><span class="fontstyle2">• <span class="fontstyle0">primary: 只选择主节点;</span></span></span></p>
<p><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2">• <span class="fontstyle0">primaryPreferred:优先选择主节点,如</span></span></span></span></span></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0">果不可用则选择从节点;</span></span></span></span></span></em></em></em></em></em></p>
<p><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2">• <span class="fontstyle0">secondary:只选择从节点;</span></span></span></span></span></span></span></em></em></em></em></em></em></p>
<p><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"></em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2">• <span class="fontstyle0">secondaryPreferred:优先选择从节点,</span></span></span></span></span></span></span></span></span></em></em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0">如果从节点不可用则选择主节点;</span></span></span></span></span></span></span></span></span></em></em></em></em></em></em></em></em></p>
<p><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2">• <span class="fontstyle0">nearest:选择最近的节点;
<br></span></span></span></span></span></span></span></span></span></span></span></em></em></em></em></em></em></em></em></p>
<p><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621230856899-1936590480.png" alt="" loading="lazy"></p>
<p> </p>
<p> 场景举例:大量高并发读取的数据场景可以选择从节点,写入数据的时候,用主节点。</p>
<h4><span class="fontstyle0">readPreference的 配置方式</span></h4>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">通过 MongoDB 的连接串参数:
• mongodb:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPre</span>ference=<span style="color: rgba(0, 0, 0, 1)">secondary
通过 MongoDB 驱动程序 API:
• MongoCollection.withReadPreference(ReadPreference readPref)
Mongo Shell:
• db.collection.find({}).readPref( “secondary” ) </span></pre>
</div>
<h4><span class="fontstyle0"><span class="fontstyle0"><span class="fontstyle0">readConcern</span></span></span></h4>
<p><span class="fontstyle0"><span class="fontstyle0"><span class="fontstyle0"><span class="fontstyle0">在 readPreference 选择了指定的节点后,readConcern 决定这个节点上的数据哪些是可读的,类似于关系数据库的隔离级别。可选值包括:<br><span class="fontstyle2">• <span class="fontstyle0">available:读取所有可用的数据;<br><span class="fontstyle2">• <span class="fontstyle0">local:读取所有可用且属于当前分片的数据;<br><span class="fontstyle2">• <span class="fontstyle0">majority:读取在大多数节点上提交完成的数据;<br><span class="fontstyle2">• <span class="fontstyle0">linearizable:可线性化读取文档;<br><span class="fontstyle2">• <span class="fontstyle0">snapshot:读取最近快照中的数据;
<br><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></p>
<p> 在复制集中 local 和 available 是没有区别的。两者的区别主要体现在分片集上。考虑以下场景:<br>• 一个 chunk x 正在从 shard1 向 shard2 迁移;<br>• 整个迁移过程中 chunk x 中的部分数据会在 shard1 和 shard2 中同时存在,但源分片 shard1仍然是chunk x 的负责方:<br> 所有对 chunk x 的读写操作仍然进入 shard1;<br> config 中记录的信息 chunk x 仍然属于 shard1;<br>• 此时如果读 shard2,则会体现出 local 和 available 的区别:<br> local:只取应该由 shard2 负责的数据(不包括 x);<br> available:shard2 上有什么就读什么(包括 x);</p>
<p><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200621233137883-346017484.png" alt="" loading="lazy"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">注意事项:
• 虽然看上去总是应该选择 local,但毕竟对结果集进行过滤会造成额外消耗。在一些无关紧要的场景(例如统计)下,也可以考虑 available;
• MongoDB </span><=3.6 不支持对从节点使用 {readConcern: "local"<span style="color: rgba(0, 0, 0, 1)">};
• 从主节点读取数据时默认 readConcern 是 local,从从节点读取数据时默认readConcern 是 available(向前兼容原因)。</span></pre>
</div>
<h4><span class="fontstyle0"><strong>readConcern : ”majority” vs “local”</strong> </span></h4>
<p><span class="fontstyle0">majority 如果有如下场景:</span></p>
<p><span class="fontstyle0">应业务需要做读写分离,主节点A主要做写入操作,从节点做读取操作;向主节点写入一条数据;立即从从节点读取这条数据。<br></span></p>
<p><span class="fontstyle0">如果用默认配置local,有的复制集从节点B到主A可能是有延时的,而导致在主节点A上修改数据后,立刻在从节点读取,发现有脏数据,主从数据不同步。所以需要majority配置,和其他节点互交确认大多数据节点上都是一样的数据才返回。<br></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">#注意配置文件内server参数开启
enableMajorityReadConcern:ture<br><br>#这种方式读取不到刚写入的数据<br><span class="fontstyle0">db.orders.insert({ oid: 101, sku: ”kite", q: 1})//主节点写入<br>db.orders.find({oid:101}).readPref("secondary") //从节点读取
<br></span>#<span class="fontstyle0">使用 writeConcern + readConcern majority来解决 </span><br><span class="fontstyle0">db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {<span class="fontstyle2">writeConcern:{w: "majority”}<span class="fontstyle0">}) //主节点写入<br>db.orders.find({oid:101}).readPref(“secondary”).<span class="fontstyle2">readConcern("majority") //从节点读取</span></span></span></span><br></span></pre>
</div>
<p><strong>readConcern: majority 与脏读</strong><br>MongoDB 中的回滚:</p>
<p>• 写操作到达大多数节点之前都是不安全的,一旦主节点崩溃,而从节还没复制到该次操作,刚才的写操作就丢失了;<br>• 把一次写操作视为一个事务,从事务的角度,可以认为事务被回滚了。所以从分布式系统的角度来看,事务的提交被提升到了分布式集群的多个节点级别的“提交”,而不再是单个节点上的“提交”。在可能发生回滚的前提下考虑脏读问题:<br>• 如果在一次写操作到达大多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读问题;<br>使用 {readConcern: “majority”} 可以有效避免脏读</p>
<h4><span class="fontstyle0">readConcern: linearizable</span></h4>
<p>只读取大多数节点确认过的数据。和 majority 最大差别是保证绝对的操作线性顺序:</p>
<p>在写操作自然时间后面的发生的读,一定可以读到之前的写(会在读取的节点,向其他所有节点发起确认请求)<br>- 只对读取单个文档时有效;<br>- 可能导致非常慢的读,因此总是建议配合使用 maxTimeMS;</p>
<p>下图情况是在主节点网络异常时,从节点发起选举期间发生的脏读</p>
<p><em><span class="fontstyle0"><span class="fontstyle0"><img src="https://img2020.cnblogs.com/blog/1093860/202006/1093860-20200627205920671-1813883508.png" alt="" loading="lazy"></span></span></em></p>
<p> </p>
<h4><span style="font-size: 1em">readConcern: snapshot(最高级别)</span></h4>
<p>{readConcern: “snapshot”} 只在多文档事务中生效。将一个事务的 readConcern<br>设置为 snapshot,将保证在事务中的读:<br>• 不出现脏读;<br>• 不出现不可重复读;<br>• 不出现幻读。<br>因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放。</p>
<h4><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0"><span class="fontstyle2"><span class="fontstyle0">官网mongoDB Java Client 事务使用示例:</span></span></span></span></span></h4>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">final</span> MongoClient client =<span style="color: rgba(0, 0, 0, 1)"> MongoClients.create(uri);
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
Prereq: Create collections. CRUD operations in transactions must be on existing collections.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
client.getDatabase(</span>"mydb1").getCollection("foo"<span style="color: rgba(0, 0, 0, 1)">)
.withWriteConcern(WriteConcern.MAJORITY).insertOne(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Document("abc", 0<span style="color: rgba(0, 0, 0, 1)">));
client.getDatabase(</span>"mydb2").getCollection("bar"<span style="color: rgba(0, 0, 0, 1)">)
.withWriteConcern(WriteConcern.MAJORITY).insertOne(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Document("xyz", 0<span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> Step 1: Start a client session. </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">final</span> ClientSession clientSession =<span style="color: rgba(0, 0, 0, 1)"> client.startSession();
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> Step 2: Optional. Define options to use for the transaction. </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
TransactionOptions txnOptions </span>=<span style="color: rgba(0, 0, 0, 1)"> TransactionOptions.builder()
.readPreference(ReadPreference.primary())
.readConcern(ReadConcern.LOCAL)
.writeConcern(WriteConcern.MAJORITY)
.build();
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> Step 3: Define the sequence of operations to perform inside the transactions. </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
TransactionBody txnBody </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> TransactionBody<String><span style="color: rgba(0, 0, 0, 1)">() {
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String execute() {
MongoCollection</span><Document> coll1 = client.getDatabase("mydb1").getCollection("foo"<span style="color: rgba(0, 0, 0, 1)">);
MongoCollection</span><Document> coll2 = client.getDatabase("mydb2").getCollection("bar"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
Important:: You must pass the session to the operations.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
coll1.insertOne(clientSession, </span><span style="color: rgba(0, 0, 255, 1)">new</span> Document("abc", 1<span style="color: rgba(0, 0, 0, 1)">));
coll2.insertOne(clientSession, </span><span style="color: rgba(0, 0, 255, 1)">new</span> Document("xyz", 999<span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 0, 255, 1)">return</span> "Inserted into collections in different databases"<span style="color: rgba(0, 0, 0, 1)">;
}
};
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
Step 4: Use .withTransaction() to start a transaction,
execute the callback, and commit (or abort on error).
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
clientSession.withTransaction(txnBody, txnOptions);
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (RuntimeException e) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> some error handling</span>
} <span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
clientSession.close();
}</span></pre>
</div>
<p>java 示例2:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">MongoDB 多文档事务的使用方式与关系数据库非常相似:</span>
<span style="color: rgba(0, 0, 255, 1)">try</span> (ClientSession clientSession =<span style="color: rgba(0, 0, 0, 1)"> client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docOne);
collection.insertOne(clientSession, docTwo);
clientSession.commitTransaction();
}</span></pre>
</div>
<p> </p>
<p> db.fsyncLock() 与<span class="fontstyle0">db.fsyncUnlock() 可以锁定/解锁节点的写入,可以用来做事务代码的测试。</span></p><br><br>
来源:https://www.cnblogs.com/yanghaolie/p/13174435.html
頁:
[1]