第19章:Mongo读写关注与一致性模型——下单后为什么查不到订单
1. 项目背景业务场景本地生活电商的订单系统切换到了复制集看起来一切正常——直到客服接到大量投诉“我刚下单成功了但打开’我的订单’页面根本看不到这笔订单”我明明付了款订单状态还是’待支付’过了一分钟才变。技术团队排查发现下单接口连接的是 Primary 写入订单但我的订单页面为了分摊读压力读的是 Secondary——而 Secondary 的复制延迟 5-8 秒用户刚写入就查从库自然看不到。还有一个更严重的问题——退款流程中先标记订单为已退款再调用支付宝退款如果支付宝退款成功但 MongoDB 的订单状态更新因为从库落后没被后续流程读到后果是财务部门统计到已退款但支付宝实际未退款的对账异常。痛点writeConcern、readConcern、readPreference 这三个参数是 MongoDB 一致性的三驾马车90% 的开发从来没配过也不知道默认值是什么意思。典型问题“readConcern: local和majority的区别是什么”“我的写入指定了w: majority为什么读还是可能读到旧数据”“读 Primary 和读 Secondary 到底差了什么”“因果一致性怎么配”2. 项目设计小胖抱头大师我刚下的订单在列表页看不到过 5 秒刷新又有了。这特么是在变魔术吗大师不是魔术是你在 Primary 上写下单在 Secondary 上读列表页查询。Secondary 复制需要时间——这个时间差就是你观察到的幽灵期。这引出 MongoDB 三个核心概念writeConcern写关注即你的写入要多可靠。w: 1只等 Primary 确认快但不稳w: majority等多数节点确认稳但稍慢。readConcern读关注即你的读取要看到哪个时间点之后的数据。local是最新的可能还未被确认majority是已被多数节点确认过的已持久化。readPreference读偏好即你的请求发给谁。primary读主节点secondary读从节点。小胖那我的场景——在下单和列表页之间保证一致性应该怎么配大师这叫读己之写Read Your Own Writes。解决方案之一是因果一致性Causal Consistency。你只需要在 Spring Boot 中做两件事// 写入时下单接口ClientSessionsessionclient.startSession();session.startTransaction();// ... 写入订单 ...session.commitTransaction();// 读取时我的订单接口// 通过 session 传递 afterClusterTime确保读到本次写入之后的数据如果写入和读取不在同一个 session 的上下文中比如跨服务最简单的做法是——读你的写入不要从 Secondary 读——把我的订单这个接口的readPreference强制设为primary。技术映射MongoDB 的因果一致性通过afterClusterTime和operationTime实现——写入返回一个时间戳后续读取带上这个时间戳保证读到的数据不早于该时间戳。小白疑惑那readConcern: linearizable呢是不是最严格的MongoDB 支持吗大师支持但不推荐在生产中频繁使用。linearizable要求读操作必须被当前 Primary 处理并且确认 Primary 仍然是真正的 Primary——它消耗额外的一次多数确认通信。大多数业务用majority就够了。对账/金融的精确总账可以用linearizable确保不会读到已经回滚的写入。技术映射readConcern的严格程度排序local(最快) availablemajoritylinearizable(最严格) snapshot事务专用。小白那不同业务场景该用什么配置能用一张表说清楚吗大师看这张业务场景配置表业务场景writeConcernreadPreferencereadConcern理由用户下单majorityprimarylocal写是要紧操作读直接从主读商品浏览w: 1secondarylocal允许短暂不一致高吞吐订单列表我的订单majorityprimarylocal读己之写必须读主运营报表w: 1secondarymajority允许延迟但不要瞬态数据库存扣减majorityprimarysnapshot事务内确切减库存不能丢对账/财务majorityj: trueprimarylinearizable精确一致零容忍小胖等等j: true又是什么大师j代表 Journal——WiredTiger 的预写日志。j: true要求写入被刷新到磁盘的 Journal 文件后才返回成功。这避免掉电丢失已缓冲但未落盘的数据。代价是额外 IO 延迟。技术映射j: true≈ MySQL 的innodb_flush_log_at_trx_commit1。是阻止断电丢数据的最后一层保障。大师总结一致性不是越严格越好——每个场景按自己的要求选配。今天的核心记三个数——读什么readPreference读到什么版本readConcern写多保险writeConcern。三者各自独立组合起来才是一致性策略。3. 项目实战3.1 环境准备沿用第 17 章的 3 节点复制集。3.2 分步实现步骤一演示读己之写问题目标在 Primary 上写入后在 Secondary 上查询复现查不到的问题。// 连接 Primary// mongosh mongodb://localhost:27017/?replicaSetmyRSuse local_lifeconsttestDoc{testId:RW_TEST_Date.now(),value:刚写入的数据,createdAt:newDate()}db.rw_test.insertOne(testDoc,{writeConcern:{w:1}})print(写入完成:,testDoc.testId)// 立即在 Secondary 上查询// mongosh mongodb://localhost:27018/?replicaSetmyRSreadPreferencesecondarydb.getSiblingDB(local_life).rw_test.findOne({testId:testDoc.testId})// 如果刚写入立即查可能会返回 null——Secondary 还没同步到步骤二五种 writeConcern 实测对比目标通过计时对比不同 writeConcern 的性能影响。use local_lifefunctiontestWriteConcern(w,jfalse,label){conststartDate.now()try{db.wc_test.insertOne({label:label,ts:newDate(),w:String(w)},{writeConcern:{w:w,j:j,wtimeout:5000}})constelapsedDate.now()-startprint(${label}(w:${w}, j:${j}):${elapsed}ms)returnelapsed}catch(e){print(${label}: FAILED -${e.message})return-1}}testWriteConcern(0,false,不确认)testWriteConcern(1,false,主节点确认)testWriteConcern(majority,false,多数确认)testWriteConcern(1,true,主节点Journal)testWriteConcern(majority,true,多数Journal)// 期望耗时越来越长步骤三四种 readConcern 对比目标理解不同 readConcern 返回数据的时间点差异。// 在不同 readConcern 下读同一个文档functiontestReadConcern(level,label){conststartDate.now()constdocdb.rw_test.findOne({testId:{$regex:/^RW_TEST/}},{},{readConcern:{level:level}})constelapsedDate.now()-startprint(${label}:${elapsed}ms -${doc?有数据:无数据(可能还未同步)})returndoc}// 注意readConcern 需要在 mongosh 中通过命令或者通过 session 指定// 使用 runCommand 方式测试constresultdb.runCommand({find:rw_test,filter:{testId:{$regex:/^RW_TEST/}},readConcern:{level:local}})print(local 结果数:,result.cursor.firstBatch.length)constresult2db.runCommand({find:rw_test,filter:{testId:{$regex:/^RW_TEST/}},readConcern:{level:majority}})print(majority 结果数:,result2.cursor.firstBatch.length)// 在从库上majority 可能会少返回刚写入但未多数确认的文档步骤四因果一致性实战目标通过 session 传递operationTime实现因果一致性。// 场景同一个 session 内写入后立刻读取保证读到刚写的constsessiondb.getMongo().startSession()constcollsession.getDatabase(local_life).rw_test// 写入constwriteResultcoll.insertOne({testId:CAUSAL_TEST,value:因果一致性测试,createdAt:newDate()},{writeConcern:{w:majority}})print(写入 operationTime:,writeResult.operationTime)// 使用同一个 session 读取自动携带 causalConsistencyconstreadResultcoll.findOne({testId:CAUSAL_TEST},{readConcern:{level:majority}})print(读取结果:,readResult?读到刚写入的数据:未读到不应发生)session.endSession()// 跨 session 的因果一致性// 如果写入和读取不在同一 session手动传递 operationTime// const readResult2 coll.findOne(// { testId: CAUSAL_TEST },// {// readConcern: {// level: majority,// afterClusterTime: writeResult.operationTime// }// }// )步骤五readPreference 组合测试目标验证不同 readPreference 的实际行为。// 在 Primary 上写入db.rp_test.insertOne({value:RP_TEST,createdAt:newDate()},{writeConcern:{w:majority}})// 测试 1primary 读默认constpdb.runCommand({find:rp_test,filter:{value:RP_TEST},$readPreference:{mode:primary}})print(primary 读主库:,p.cursor.firstBatch.length0?PASS:FAIL)// 测试 2secondary 读// 在 mongosh 连接时指定: mongosh .../?readPreferencesecondary// 或在查询中:constsdb.runCommand({find:rp_test,filter:{value:RP_TEST},$readPreference:{mode:secondary}})print(secondary 读从库:,s.cursor.firstBatch.length0?PASS:FAIL)// 测试 3nearest 读最低延迟节点constndb.runCommand({find:rp_test,filter:{value:RP_TEST},$readPreference:{mode:nearest}})print(nearest 读:,n.cursor.firstBatch.length0?PASS:FAIL)步骤六一致性配置对照表目标综合理解 writeConcern readConcern readPreference 的配合。// 场景 A电商下单强一致 // write: { w: majority, j: true }// read: { readConcern: { level: local }, readPreference: primary }// 结果写入需要多数确认读从主库读最新数据// 场景 B商品浏览高性能 // write: { w: 1 }// read: { readConcern: { level: local }, readPreference: secondary }// 结果写入只等 Primary读从从库分担读压力// 场景 C报表允许延迟不要瞬态 // write: { w: 1 }// read: { readConcern: { level: majority }, readPreference: secondary }// 结果读到的是多数确认过的稳定数据但可能有延迟// 场景 D金融对账最高一致 // write: { w: majority, j: true }// read: { readConcern: { level: linearizable }, readPreference: primary }// 结果写入需 journal 持久化 多数确认读需要验证 Primary 身份// 验证脚本 constscenarios[{name:电商下单,w:majority,rc:local,rp:primary},{name:商品浏览,w:1,rc:local,rp:secondary},{name:报表,w:1,rc:majority,rp:secondary},{name:金融对账,w:majority,rc:linearizable,rp:primary}]console.table(scenarios)3.3 完整代码清单文件用途mongodb-lab/replicaset/read-write-concern.jswriteConcern/readConcern 对比实验mongodb-lab/replicaset/causal-consistency.js因果一致性演示mongodb-lab/replicaset/read-preference.jsreadPreference 行为测试mongodb-lab/replicaset/consistency-matrix.js业务场景一致性配置矩阵3.4 测试验证// 1. 验证 writeConcern: majority 的可靠性db.test_wc.insertOne({test:wc_majority},{writeConcern:{w:majority}})// 在 2 个 Secondary 上验证数据存在// → 全部通过// 2. 验证因果一致性constsessdb.getMongo().startSession()sess.getDatabase(local_life).test_causal.insertOne({_id:CT1},{writeConcern:{w:majority}})constressess.getDatabase(local_life).test_causal.findOne({_id:CT1})print(因果一致性:,res!null?PASS:FAIL)sess.endSession()// 3. 验证 readConcern: linearizabletry{db.runCommand({find:test_wc,readConcern:{level:linearizable}})print(linearizable: PASS)}catch(e){print(linearizable: 仅 Primary 支持 - e.message)}print(\n 一致性验证完成 )4. 项目总结4.1 一致性参数速查参数可选值默认值性能代价安全级别writeConcern.w0/1/n/“majority”1w↑ 延迟↑w↑ 安全↑writeConcern.jtrue/falsefalse64位平台jtrue 延迟↑磁盘 IOjtrue 防断电丢数据readConcern.levellocal/available/majority/linearizable/snapshotlocallinearizable 性能↓majority 防脏读readPreferenceprimary/primaryPreferred/secondary/secondaryPreferred/nearestprimary无显著差异路由开销primary 一致性最强4.2 适用场景各配置组合的典型场景已在步骤六中给出。4.3 注意事项注意事项说明readConcern: majority在从库可能阻塞从库需要检查 Primary 的 commit point如果主从延迟大读会等不要混用w:1和linearizable写入只等 Primary读却检查 Primary 是否仍为 Primary——逻辑矛盾因果一致性的 session 不能跨服务如果下单和订单列表是不同的微服务需要显式传递operationTimenearest可能读到自己的写入失败nearest 按网络延迟路由可能将刚写入的请求的后续读请求发送至延迟大的从库仲裁节点无数据readPreference: secondary时不会路由到 Arbiter4.4 常见踩坑经验故障案例一读从库看到幽灵订单某系统用writeConcern: 1写订单但用readConcern: local从从库读——巧合读到一条刚写入但 Primary 立刻宕机后被回滚的订单。根因从库在回滚前已经拉取了这条 Oplog 并应用了但没来得及知道这条记录已被回滚。解决将读升级到readConcern: majority多数确认的数据不会回滚。故障案例二w: majority在 2 节点复制集中卡住已在上一章中讲过此案例本章角度不同——开发在应用代码中写死了w: majority运维因资源紧张只给了 2 个数据节点——系统间歇性报waiting for replication timed out。解决要么加数据节点要么业务分级——部分低优先级写入降级为w:1。故障案例三nearestsecondary导致请求漂移某微服务配置readPreference: nearest在 K8s 的多可用区部署中客户端到不同分区的从库延迟有波动导致同一用户的请求一会儿读北京从库、一会儿读上海从库——订单列表数据来回变动。解决用户相关接口强制使用readPreference: primary或primaryPreferred。4.5 思考题如果使用writeConcern: majorityreadConcern: majority的组合能保证写后立刻读总能读到刚写的数据吗为什么在 Spring Boot 中如何为某个特定查询覆写全局的 readPreference如何验证这个覆写真的生效了答案将在第 20 章末尾揭晓上一章思考题答案三节点全部宕机后应该先启动最后一个 Secondary即拥有最新 Oplog 的节点让它作为恢复的基准。如果启动了最旧的节点且它被选举为 Primary后续节点需要通过 Rollback 把多余的操作回滚掉 可能引发数据丢失。正确顺序① 找到所有节点中最新的 Oplog看rs.status()保存在每个节点 local 数据库的历史信息② 先启动拥有最新 Oplog 的节点让它成为 Primary③ 再启动其他节点让其追赶。Hidden 节点实时拉取 Oplog只是不对外暴露读接口hidden: true阻止客户端驱动的读路由。Delayed 节点故意延迟Oplog 的应用secondaryDelaySecs秒后再应用Oplog仍然被拉取但被缓存在本地延迟执行。区别在于Hidden 节点的数据是准实时的和普通 Secondary 一样Delayed 节点的数据是故意滞后的。延伸阅读与资源python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析

相关新闻

浏览器请求头到底哪些字段不能乱写?

浏览器请求头到底哪些字段不能乱写?

在前端开发、接口调试甚至逆向爬虫的场景里,修改请求头是常规操作。但很多人不知道:浏览器对请求头有严格的安全管控,不是所有字段都能随意修改 —— 有些字段写了会直接报错,有些写了请求直接失效,甚至会触发安全风险…

2026/10/10 9:13:50 阅读更多 →
Azure Linux终极指南:微软云原生操作系统的完整解决方案

Azure Linux终极指南:微软云原生操作系统的完整解决方案

Azure Linux终极指南:微软云原生操作系统的完整解决方案 【免费下载链接】azurelinux General purpose Linux OS for Azure 项目地址: https://gitcode.com/GitHub_Trending/az/azurelinux 在云计算时代,操作系统不仅是技术基础设施的基石&#x…

2026/10/8 14:18:50 阅读更多 →
C语言性能封神的11个硬实力:从内存控制到编译器优化

C语言性能封神的11个硬实力:从内存控制到编译器优化

在编程语言层出不穷的今天,当开发者们热衷于讨论Python的简洁、Go的并发、Rust的安全时,一个“古老”的名字却依然在性能的圣殿中稳坐王座——C语言。你是否曾疑惑,为什么操作系统内核、数据库引擎、游戏引擎、嵌入式系统这些对性能有极致要求…

2026/10/11 2:09:29 阅读更多 →

最新新闻

Docker 一键启动服务合集:Redis、MySQL、Kafka、MinIO、Prometheus 等(网盘转存防失效)

Docker 一键启动服务合集:Redis、MySQL、Kafka、MinIO、Prometheus 等(网盘转存防失效)

0. 网盘链接速览(请先转存)⚠️ 重要提示:网盘链接可能随时失效,请先点击下方链接转存到自己网盘,再下载,防止链接失效后无法获取。服务网盘链接提取码redis-dockerhttps://pan.baidu.com/s/1nP2CNyaxdRRKB…

2026/10/12 3:54:21 阅读更多 →
Vim编辑器从入门到精通:模式、快捷键、配置、插件

Vim编辑器从入门到精通:模式、快捷键、配置、插件

SSH 到一台服务器上改配置,手头没有 VS Code,能用的编辑器基本就 Nano 和 Vim。Nano 上手快,但改大型项目源码时效率差 Vim 一大截。这一篇把 Vim 的模式、常用快捷键、基础配置和插件入门讲清楚,目标是看完就能独立改文件&#x…

2026/10/12 3:54:21 阅读更多 →
【C++ 基于微服务的即时通讯系统】基建三件套:gflags + gtest + spdlog 源码编译安装与实战入门

【C++ 基于微服务的即时通讯系统】基建三件套:gflags + gtest + spdlog 源码编译安装与实战入门

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C知识分享》 《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 前言: 做 C 高性能后端开发这么多年…

2026/10/12 3:54:21 阅读更多 →
C语言预处理机制详解:宏定义、条件编译与头文件包含避坑指南

C语言预处理机制详解:宏定义、条件编译与头文件包含避坑指南

1. 预处理在编译流程中的位置很多刚开始学C语言的人,会把注意力全放在语法、指针、结构体这些“看得见”的部分上,但真正到了项目里,第一个让你摔跟头的往往不是语法,而是预处理指令。它不参与具体的逻辑计算,却在编译…

2026/10/12 3:54:21 阅读更多 →
AI工程师不加班成长指南:把工作变训练场的高效学习法

AI工程师不加班成长指南:把工作变训练场的高效学习法

晚上十一点,我刚把一个数据管线的 bug 处理完,打开收藏夹里积压了两个月的论文列表,脑子已经完全转不动了。这种情景在过去两年里反复出现:白天被会议、需求评审、调试代码塞满,晚上想学点新东西却连打开文档的力气都没…

2026/10/12 3:54:21 阅读更多 →
Z-Gly-Gly-Arg-ΒNA;17278-97-6

Z-Gly-Gly-Arg-ΒNA;17278-97-6

基本性质中文名称:苄氧羰基 - 甘氨酰 - 甘氨酰 - 精氨酸 -β- 萘胺,蛋白酶体胰蛋白酶样活性荧光底物CAS 号:17278-97-6单字母序列:Z-G-G-R-βNA三字母序列:Z-Gly-Gly-Arg-βNA分子式:C₂₈H₃₃N₇O₅分子量…

2026/10/12 3:53:20 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →