第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/7/20 14:53:18阅读更多 →
高校严查AI写作!如何让论文“天然抗检测”,这3招是关键

高校严查AI写作!如何让论文“天然抗检测”,这3招是关键

博主介绍:✌️码农一枚 ,专注于大学生🚢文降重,降AIGC服务 ✌️技术范围:全学科🚢文降重,降AIGC服务 主要内容:人工降重和降AIGC,基于语义理解重构句式,替换同…

2026/7/20 14:51:18阅读更多 →
如何快速上手mr-mantou-android:从安装到图片浏览的完整教程

如何快速上手mr-mantou-android:从安装到图片浏览的完整教程

如何快速上手mr-mantou-android:从安装到图片浏览的完整教程 【免费下载链接】mr-mantou-android On the importance of taste 项目地址: https://gitcode.com/gh_mirrors/mr/mr-mantou-android mr-mantou-android(馒头先生)是一款专注…

2026/7/20 14:51:18阅读更多 →
【YOLO26多模态涨点改进】TMM 2026顶刊 |独家创新首发、特征融合改进篇| 引入FDFAM频域特征聚合模块,通过在频域中建模关系,实现更高效融合,助力小目标检测,多模态目标检测有效涨点

【YOLO26多模态涨点改进】TMM 2026顶刊 |独家创新首发、特征融合改进篇| 引入FDFAM频域特征聚合模块,通过在频域中建模关系,实现更高效融合,助力小目标检测,多模态目标检测有效涨点

一、本文介绍 🔥本文给大家介绍使用 FDFAM频域特征聚合模块 改进YOLO26多模态网络模型。 为了提升YOLO26多模态特征的频域融合能力,本文引入FDFAM频域特征聚合模块,将可见光与红外特征映射至频域,并通过多尺度频率交互自适应聚合低频全局语义与高频边缘、纹理信息。相比…

2026/7/21 8:13:09阅读更多 →
IE浏览器退役与现代浏览器技术演进

IE浏览器退役与现代浏览器技术演进

1. IE浏览器退役背后的技术演进史 2022年6月15日,微软正式终止对IE浏览器的支持,这个服役27年的"上古神器"终于退出历史舞台。作为前端开发者,我电脑里还保留着IE6的虚拟机镜像——当年为了兼容这个特立独行的浏览器,我…

2026/7/21 8:13:09阅读更多 →
获取用户到期时间(四)

获取用户到期时间(四)

执行脚本(根据仓库数量,用户数量,大概耗时5分钟)./getGitlabExpireUserMsg.py 或python3 getGitlabExpireUserMsg.py#!/usr/bin/python3 import requests import json from datetime import datetime, timedelta# GitLab API URL 和访问令牌 …

2026/7/21 8:13:09阅读更多 →
MCP Client实战翻车记:两个Server同时跑,AI调错了Tool

MCP Client实战翻车记:两个Server同时跑,AI调错了Tool

前面写完 MCP Server 那篇文章之后,我又接了个需求:让一个 Agent 同时调两个 MCP Server。一个查 SQLite 数据库,一个查本地文件系统。Agent 跑了一轮,返回了一个答案。我看着答案觉得不太对,对了一下原始数据——AI 给…

2026/7/21 8:13:09阅读更多 →
AstrBot:一个以「IM 平台粘合剂」为核心竞争力的开源 AI Agent 框架

AstrBot:一个以「IM 平台粘合剂」为核心竞争力的开源 AI Agent 框架

信源已充分收集,现在输出完整笔记。 AstrBot:一个以「IM 平台粘合剂」为核心竞争力的开源 AI Agent 框架 项目地址:github.com/AstrBotDevs/AstrBot | Stars: ~28.4k | 许可证: AGPL-3.0 | 主语言: Python 3.12 核心观点 AstrBot 的本质不是…

2026/7/21 8:13:09阅读更多 →
Windows 11必备效率工具全解析

Windows 11必备效率工具全解析

1. Windows 11必备工具全景解析作为从Windows 95时代就开始折腾系统的老用户,我见证了操作系统从简陋到精致的演变过程。Windows 11无疑是微软近年来最具革新性的版本,但就像精装修的房子需要软装搭配一样,系统原生的功能总有些让人不顺手的地…

2026/7/21 8:11:09阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 0:51:49阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 0:51:49阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 0:51:49阅读更多 →
Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

📌教程适配:OpenClaw v2.7.9 | 兼容 Windows10/11、macOS 双系统 📖前言 当下各类本地 AI 工具层出不穷,多数产品仅能完成文字问答交互,很难直接操控电脑执行实际操作。OpenClaw,业内常称小龙虾 AI&#…

2026/7/21 0:01:46阅读更多 →
Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

聊《一次Codex项目复盘,问题最后出在流程而不是模型》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做&…

2026/7/21 0:01:46阅读更多 →
手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

大家好,还是我。前几期带大家做了心情日记本和可视化大屏,后台有朋友留言:“能不能教点好玩的?我想做游戏,但一行代码都不会。”行,这期就安排。今天的目标:从零做一个五子棋游戏。 带AI对战、三…

2026/7/21 0:03:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/20 22:51:39阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/20 18:51:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/20 18:51:18阅读更多 →