1. 数据"凭空消失"前,我经历过的那几次心跳骤停
先交代一下背景。我手上维护着好几套 MongoDB 集群,版本从 4.4 到 7.0 都有,承载的业务包括用户行为日志、消息队列的落库、还有一部分偏核心的交易流水。按理说 MongoDB 的稳定性在 NoSQL 里算是能打的,但"数据莫名其妙没了"这种事,我前前后后遇到过六次,每一次都够喝一壶的。
最典型的一次是凌晨三点被电话吵醒。业务方说某个集合里昨天的数据还在,今天一查全没了,不是少了几条,是整段整段地没。我当时第一反应是有人误删了,于是赶紧查 oplog、查慢日志、查审计日志,折腾了一个多小时,最后发现根本不是人为问题,而是写入根本就没有成功,只是应用层没报错。
这就是 MongoDB 数据丢失最坑的地方:大多数数据"消失"不是被谁删了,而是它压根就没写进去,或者写进去之后被某些机制静默清理掉了。如果没有一套系统化的排查思路,你会在错误的方向上浪费大量时间。
这篇文章我就把这几年踩过的坑、排查过的 case、以及最终沉淀下来的一套排查流程完整梳理一遍。不管你是刚接触 MongoDB 的开发,还是已经上了生产环境的运维,按着这套方法来,能帮你少走很多弯路。需要说明的是,文中的排查步骤都是基于通用实践总结的,具体命令在不同版本下可能略有差异,但排查思路是通用的。
2. 第一现场:先搞清楚"没数据"到底是哪一层的问题
数据没了,第一件事不是翻数据,而是先界定问题范围。我把排查过程分成三个层次,每一层都有对应的检查点。
2.1 先确认:是数据真没了,还是你查的方式不对
这个听起来像废话,但我在实际排查中至少遇到三次"假消失"。最经典的一种情况:你用了错误的数据库或集合名。MongoDB 的连接串里如果没有显式指定数据库,默认会连到test库上。而某些驱动在写入时,如果你没有调用useDatabase(),数据会静默写入test库,等你下次用 Compass 去看,发现业务库空空如也,以为数据丢了,其实数据都在test里躺着。
还有一种常见情况是读取时使用了错误的过滤条件。比如你存的时候用的是ISODate("2024-06-01T00:00:00Z"),查的时候却传了一个字符串"2024-06-01",MongoDB 在比较时会做类型匹配,字符串和日期类型不匹配,查出来就是空结果。
所以排查的第一步永远是:
- 确认连接串里的数据库名、集合名是否正确
- 确认查询条件的数据类型是否匹配
- 用 Compass 或者命令行直接看集合的
count()和最近的文档
我用一个简单的命令序列来快速判断:
# 查看当前连接下的所有数据库 show dbs # 切换到你认为有数据的库 use your_db_name # 查看所有集合 show collections # 统计集合文档数 db.your_collection.countDocuments() # 看最近一条数据的写入时间 db.your_collection.find().sort({_id: -1}).limit(1)如果countDocuments()返回 0,但show dbs里能看到该库的磁盘占用不是 0,说明数据可能真的在,只是集合名不对,或者你在错误的库里查。
2.2 锁定时间窗口:是全部没了,还是某段时间内的没了
这一步决定了你排查的方向。
- 全部没了:大概率是
drop()、deleteMany({})、或者整个数据库被删了。 - 某段时间内的没了:大概率是 TTL 索引过期、写入失败、或者数据被定期清理任务做掉了。
- 部分文档的某些字段没了:大概率是 update 操作覆盖了字段,或者文档结构被修改。
我遇到的大多数生产事故属于第二种——某个时间段范围内的数据缺失。这种问题最隐蔽,因为它不像是"被删除",更像是"从未写入"。
2.3 检查应用日志:写入端有没有报错,有没有被吞掉的异常
很多数据丢失的根源在应用层。最常见的场景是:代码里没有确认写入结果就继续执行了。MongoDB 驱动在默认情况下,如果写入失败会抛异常吗?分情况:
- 使用
insertOne时,如果发生网络错误或主节点不可用,驱动会抛异常。 - 但如果是
insertMany使用了非有序(ordered: false)插入,部分失败时不会整体报错,只是返回一个writeErrors数组。如果你没有检查这个返回值,失败的那几条就悄无声息地丢了。
更隐蔽的是,有些语言驱动默认开启了重试机制。比如 MongoDB 官方驱动在写入时如果遇到网络抖动,会自动重试一次。但重试可能导致写入了两次,也可能导致数据实际落盘了但客户端收到的是超时异常。如果你在捕获到异常后做了补偿逻辑——比如更新一条记录的状态为"写入失败"——后续排查时你看到的是"失败"状态,但数据其实已经写进去了,这会严重干扰判断。
所以排查时一定要做一件事:查应用日志里有没有任何写入相关的 warning 或 exception,特别要注意那些被 try-catch 吞掉的异常。很多时候"数据没了"的真相就藏在这些被你忽略了很久的日志里。
3. 找到真凶:六大常见"隐形杀手"逐个拆解
在确认不是查询问题、也不是应用日志表面问题之后,接下来就要对 MongoDB 自身的各种"隐形删除"机制做逐一排查。我把这几年遇到的全部整理成了一张清单。
3.1 TTL 索引:你以为在优化,实际在删数据
TTL(Time To Live)索引是 MongoDB 提供的一种自动过期删除机制。创建了 TTL 索引的字段,MongoDB 后台线程会周期性扫描,删除那些字段值加上指定秒数后仍然小于当前时间的文档。
TTL 索引的威力我在生产环境体验过。有一回,运营团队为了清理日志表,要求加一个"数据保留 30 天"的清理策略。开发同事直接在日期字段上建了 TTL 索引,测试环境跑了一周没问题,上线后一个月也没人注意。直到有一天,某条业务线的数据分析师说"我要看 45 天前的数据",结果发现 30 天前的数据全没了。
当时排查过程也很痛苦,因为mongostat看不到 TTL 的删除动作,explain也看不出来。最后是通过db.currentOp()发现了一直在跑的delete操作,顺着操作类型才定位到 TTL 索引。
如何排查:
// 查看集合上的所有索引 db.your_collection.getIndexes() // TTL 索引的特征是 options 里有 expireAfterSeconds 字段如果发现某个索引带有expireAfterSeconds,立刻检查它是不是建在了不该建的字段上。特别是:如果这个字段是时间字段,且你更新文档时该字段会变,那么 TTL 的删除时间会以最后一次更新后的值为准,这可能导致数据比预期更早被清掉。
我碰到过一个真实案例:一个会话集合,字段lastActiveAt每次用户操作都会更新,TTL 设置 7 天。正常情况下,7 天没活跃的会话才会被清。结果因为某个版本代码 bug,lastActiveAt被错误地赋值为new Date(0)(1970 年),TTL 线程一看,这个文档早就过期了,立刻删光。用户下次打开应用发现会话全部失效,被迫重新登录。排查到最后,数据层面没错,是业务代码的锅。
建议:能用定时任务清理的数据,尽量不要交给 TTL。如果非用不可,一定在索引命名上区分清楚,并且每天巡检索引列表。
3.2 误执行了 drop / deleteMany / remove
这类问题最直接,也最容易被发现,但导致它的场景往往很离奇。
- 手滑在错误的数据库上执行了
db.dropDatabase() - 脚本里写死了集合名,连接串换了环境但脚本没换
- 清理数据的定时任务没有加过滤条件,本来应该
deleteMany({status: "expired"})结果写成了deleteMany({}) - MongoDB Compass 里打开集合后按了
Shift + Delete快捷键,直接删了整个集合而不只是选中的文档
我的建议是:如果你还没有启用访问控制(Authorization),现在就去启用。哪怕只是一个简单的用户名密码,也能挡住一大半误操作。更进一步,创建专用的运维账号,只授readWrite权限的账号不要给dropCollection和dropDatabase的权限。
排查误删除的办法是查 oplog:
// 切换到 local 库 use local // 查询最近的删除操作 db.oplog.rs.find({ op: "d", ns: "your_db.your_collection" }).sort({$natural: -1}).limit(10)oplog 里每一条删除记录都有ts时间戳和o._id,能精准定位到删除发生的时间和被删文档的 ID。如果你开启了审计日志,那排查起来更快,直接按用户名和时间过滤。
3.3 主从切换后,还没同步到从节点的数据全部丢失
这是 MongoDB 数据丢失中最经典、也最无奈的一种——非正常的主从切换导致的数据回滚。
MongoDB 复制集的原理是:写入先到主节点,写入主节点的 oplog,然后从节点异步地从主节点拉取 oplog 并重放。如果主节点在某个写入还没有被绝大多数从节点复制时就宕机了,那么新选举出来的主节点可能没有这条数据。旧的节点恢复后,发现自己有"超前"的数据(比新主节点的 oplog 位置更靠后),它会把这些数据回滚掉,这些数据就永久消失了。
这个回滚量有多大?如果用默认配置,MongoDB 会把回滚的数据写入一个名为rollback/的目录下的 BSON 文件里。这些文件在回滚发生后不会立即删除(除非设置了rollbackViaRefetch),这是救命稻草。
排查步骤:
- 查看所有从节点的复制延迟:
rs.status()里的secs字段。 - 查看主节点的 oplog 窗口大小。
- 如果怀疑发生了回滚,去每个节点的
dbPath目录下找rollback文件夹。
我经历的一次事故是:某个从节点因为磁盘 IO 紧张,复制延迟达到了惊人的 3 个小时,而 oplog 窗口只有 1 小时,结果主节点宕机后,新主节点直接断开了这个落后太多的从节点的心跳,这个节点上积压的 3 小时数据全部无效。
建议:每天用rs.status()检查复制延迟,异常时第一时间处理。oplog 窗口尽量调大,尤其是业务有周期性批量写入的情况。
3.4 分片集群的 chunk 迁移失败
如果你用的是分片集群(sharded cluster),数据丢失的风险又多了一层——chunk 迁移过程中可能出现的孤儿文档或迁移失败。
MongoDB 的分片机制是按 shard key 把数据拆成多个 chunk,后台 balancer 会在各分片之间迁移 chunk 以平衡负载。迁移过程中,如果源分片在迁移尚未完成时收到了新的写入,这些写入会被标记为"孤儿文档"(orphaned documents),最终在分片中重复或不一致。
更麻烦的是,如果你在迁移过程中手动做了聚合查询,某些查询在旧版本(4.4 之前的某些版本)里可能会扫描到正在迁移的 chunk 的不完整快照,导致部分数据"看不见"。
排查方法是查看 balancer 的状态:
// 查看 balancer 是否在运行 sh.status() // 查看是否有正在进行的 chunk 迁移 db.adminCommand({ currentOp: 1, "shard": true })我从 MongoDB 5.0 的官方文档里还了解到,如果你是启用了会话(session)和因果一致性的事务,迁移期间的读写能在一定程度上避免孤儿文档问题,但并不能完全消除。
建议:shard key 的选择要慎重,避免出现 jumbo chunk(超过 chunk size 且无法分裂的块)。一旦出现 jumbo chunk,balancer 无法迁移它,数据会长期卡在某个分片上,遇到这种问题要主动手工拆分或调整 chunk size。
3.5 磁盘损坏与 WiredTiger 的"未持久化"窗口
MongoDB 默认的存储引擎是 WiredTiger,它在数据安全性和性能之间做了平衡。WiredTiger 使用 checkpoint 机制定期把内存中的数据落盘,默认 60 秒或 2GB 日志写入后触发一次 checkpoint。在两次 checkpoint 之间,如果进程被强制 kill(比如kill -9)或者机器宕机,WiredTiger 会通过 journal(预写日志)来恢复数据。
但如果 journal 也坏了呢?那就真的无力回天了。
磁盘损坏的情况我遇到过一次,是发生在云服务器突发 IO 错误之后。现象是:集合能查出来,但某些文档的某些字段变成了null或空值。后来用mongodump导出时直接报错,提示"corrupt document"。WiredTiger 在遇到损坏的文档时,有时候不会主动报错,而是返回异常结果,这在数据层面最坑。
如何提前发现:
- 定期执行
db.collection.validate(),它会扫描集合中所有文档的结构完整性。 - 检查 MongoDB 日志里有没有 WiredTiger 的 warning。
- 注意
mongod进程的退出码,如果是异常的退出码(比如非 0),需要重点关注。
// 校验集合完整性 db.your_collection.validate({ full: true })如果validate()报错了,说明文档确实损坏,只能从备份恢复,或者用mongodump的--forceTableScan参数尝试导出尽可能多的数据。
3.6 数据被业务逻辑"覆盖"而不是删除
有一种数据消失,从数据库层面看一切正常,文档还在,只是里面的字段被覆盖成了意想不到的值。这类问题最难排查,因为纯粹是业务逻辑的 bug。
举个例子:某业务用一个集合存储用户配置信息,更新时用了updateOne({userId: xxx}, {$set: 整个新的配置对象})。如果上游传来的配置对象是null或者空对象{},就会把所有已有配置全部清空。数据没有消失,但看起来就像是"没了"。
还有一种是使用upsert: true且搭配错误的过滤条件,导致每次都创建新文档,而旧文档再也查不到。MongoDB 默认_id唯一,但如果你自定义的过滤条件没有走唯一索引,就可能导致"看起来像覆盖了"的更新实际创建了重复文档。
排查思路:
- 查
_id是否出现重复冲突 - 查文档更新时间(如果文档没有
updatedAt字段,你需要靠 oplog 查看 update 的操作记录) - 打开 MongoDB 的 profiler(慢查询日志),记录所有 update 操作
// 开启 profiling,记录所有慢操作(超过 100ms 的) db.setProfilingLevel(1, { slowms: 100 }) // 查看最近更新过该集合的操作 db.system.profile.find({ ns: "your_db.your_collection", op: "update" }).sort({ts: -1}).limit(20).pretty()profiler 默认在生产环境是关闭的,这一点需要注意。如果数据"消失"的同时你没开 profiler,前面那段排查日志就只能靠 oplog 来看了。
4. 从 0 到 1 的完整排查链路:一条命令一条命令敲出来的经验
在这一节,我把前面提到的零散步骤串成一条完整链路。下次你遇到"MongoDB 数据莫名其妙没了",直接按这个顺序执行,能省下至少两小时无头苍蝇式的排查时间。
4.1 阶段一:确认可见性(5 分钟内)
先做最基础的确认,排除"查错了地方"的可能:
# 1. 列出所有数据库及其大小 show dbs # 2. 确认你连的是不是对的那台机器 db.getMongo()特别注意:如果你用了 MongoDB Atlas 或者云数据库,连接串里可能带了?authSource=admin参数,如果你指定了错误的库名,只能看到部分数据库,看起来就像是"某些库的数据消失了"。
确认库之后,用 Compass 或者命令行看目标集合的文档数。此时如果文档数是 0,走阶段二;如果文档数明显少于预期,跳转到阶段三。
4.2 阶段二:定位删除操作(30 分钟内)
如果文档数是 0,快速锁定删除时间窗口:
// 进入 local 库查看 oplog use local // 统计当前 oplog 的时间范围 db.oplog.rs.find().sort({$natural: 1}).limit(1).toArray()[0].ts db.oplog.rs.find().sort({$natural: -1}).limit(1).toArray()[0].ts // 查询目标集合的所有写操作(插入、更新、删除) db.oplog.rs.find({ ns: "your_db.your_collection" }).sort({$natural: -1}).limit(50).pretty()如果你在 oplog 里看到了大量op: "d"(删除)记录,马上用o._id去备份里捞数据。如果你在主节点上开启了审计日志,直接查审计日志定位是谁在什么时间、从哪个 IP 执行的操作。
如果 oplog 里连插入记录都没有,说明写入可能从未到达 MongoDB,问题大概率出在应用层和网络层,检查驱动配置和连接池状态。
4.3 阶段三:体检式排查(1 小时内)
如果文档数只是减少,或者查询"时有时无",执行下面全套体检:
| 检查项 | 命令/工具 | 关注的异常 |
|---|---|---|
| 复制延迟 | rs.status()中的secs | 任一节点延迟大于 oplog 窗口 |
| TTL 索引 | db.collection.getIndexes() | 字段上是否意外存在expireAfterSeconds |
| 慢查询 | db.setProfilingLevel(1)后查system.profile | 意外的deleteMany或全表update |
| 校验完整性 | db.collection.validate({full: true}) | 出现 corruption 报错 |
| 后台任务 | db.currentOp() | 是否有大范围的 delete / migrate 任务在运行 |
| 连接来源 | db.serverStatus().connections | 是否有陌生 IP 的活跃连接 |
| 磁盘状态 | df -h、`dmesg | grep -i error` |
| 备份状态 | 确认最近的备份是否成功 | 备份与当前数据的差异时间 |
4.4 阶段四:从备份恢复(视情况而定)
如果确认数据被删且无法通过 oplog 回放找回,就只能走恢复方案了。这里我给你两个思路:
- 最近备份 + oplog 增量恢复:先恢复到最近一个全量备份的时间点,然后用 oplog 把从备份点到灾难点之间所有操作都回放一遍。这个方案要求你当时有有效的全量备份,并且备份之后的 oplog 还没被覆盖。
- 只读节点恢复:如果你有隐藏的从节点(hidden secondary),可以从它上面
mongodump导出数据。但需要注意,隐藏节点也会参与复制,数据延迟和普通从节点一样。
我记得有一次事故,备份策略是每天凌晨全量 + 每小时增量,但恢复时发现增量备份用的工具不兼容 MongoDB 7.0 的 oplog 格式,导致无法回放,最后只能用全量恢复到前一天晚上,白白丢了十几个小时的数据。从那以后我就特别强调:备份工具的兼容性验证,比备份本身更重要。
5. 血泪教训:哪些"安全配置"其实并不安全
这块内容我要倒出点真东西了。下面几条都是我或身边同行在生产环境里实际踩出来的坑,不是官方文档会明确警告你的。
5.1 不带 --w=majority 的写入,在切换时可能直接丢
MongoDB 的写入级别(write concern)决定了一个写操作在返回成功前,需要被多少个节点确认。
默认的 write concern 是w: 1,意思是在主节点写入完成后就返回成功。如果这时候主节点挂了,其他从节点还没同步这条数据,那么新主节点上就没有这条数据。
生产环境强烈建议使用w: "majority",这样写入会等待复制集中大多数节点确认后才返回成功。这样即使主节点宕机,数据也已经同步到了多数节点,不会丢。
当然,w: "majority"会带来更大的写入延迟,因为需要跨节点同步。对延迟极其敏感的业务,可以做折中:关键数据用majority,非关键数据用w: 1。
我曾经做过一个压测对比,在同一台机器上,w: 1的写入延迟大约是 1ms,w: "majority"在三节点集群里大约是 8-12ms。这个差距对大部分业务可以接受,但如果你有类似实时计数器那种超高频写入的场景,all-or-nothing 的取舍要仔细权衡。
5.2 开了 auth 不等于安全
很多人觉得只要启用了账号密码,MongoDB 就安全了。实际上,如果用的是弱口令,等于没开。2017 年那次大规模勒索事件至今让很多人记忆犹新——被攻击的 MongoDB 实例基本都是没开认证或弱口令的。
即便开了认证,权限也要尽量精细。我见过一个生产库,运维统一用一个拥有root角色或者dbAdminAnyDatabase角色的账号做日常连接,业务代码也用它连接数据库。结果有一次这个账号被钓鱼泄露,攻击者直接把整库拖走了。
正确的做法是分权分账号:
- 业务应用:只用
readWrite+ 指定数据库的权限 - 定时任务:额外给
dbAdmin权限,但不给dropDatabase - 运维人员:用独立账号,且开启 MFA(云数据库支持的情况下)
- 审计:开启 MongoDB Enterprise 的审计功能,或者云数据库的 SQL 审计
5.3 云数据库的"快照"≠"绝对恢复点"
如果你用的是托管云数据库(阿里云、腾讯云、AWS 等),快照备份确实省心,但你要搞清楚快照的恢复粒度。有些云厂商的快照是每天一次,有些是每 6 小时一次,如果你在两次快照之间发生了数据删改,恢复后你丢失的正好是那段时间的增量数据。
云厂商一般还会提供一个"按时间点恢复"的功能,这个功能依赖的是底层基于 binlog/oplog 的持续备份能力。但要注意:这个能力并非所有版本都有,也不是默认开启的。你需要主动确认自己的实例是否开启了"秒级恢复"或"按备份集恢复"的选项。
一种比较稳的生产策略:在业务侧自己做一份独立的逻辑备份(mongodump),定期上传到异地的对象存储里。虽然听起来原始,但它不依赖云厂商的底层机制,恢复时完全可控。
5.4 hidden secondary:你可以多一个"后悔药"
MongoDB 支持隐藏节点(hidden secondary)。它能同步数据,但不参与客户端读取,也不参与投票选举。它的最大价值是:当主库出现逻辑错误(比如误删)时,你可以立即从隐藏节点拿到一份相对最新的数据。
很多人觉得复制集只要有 3 个节点就万事大吉,但普通从节点是可以被应用读取的,如果你把脏数据同步到了从节点,那从节点也不可靠。隐藏节点由于业务不可见,数据被污染的概率低得多。
要注意的是,隐藏节点也会应用 TTL 删除和普通的删除操作,所以它只能帮你抵抗"物理故障"和"主从切换回滚"这类问题,不能抵抗"逻辑删除"。在 TTL 场景下,你需要的是定期快照,而不是隐藏节点。
6. 最终防线:一套靠谱的备份策略,关键时候真的能救命
说了这么多排查手段,最后来聊聊预防。其实"数据莫名其妙没了"这个问题,不可控因素太多,但可控的只有一件事:备份的可靠性与可恢复性。备份不是策略对了就行,还要保证备份文件本身是有效的,恢复流程是可跑的。
6.1 备份方案怎么选
我梳理了三种主流的备份方案的优缺点:
| 方案 | 恢复粒度 | 优点 | 缺点 |
|---|---|---|---|
mongodump | 逻辑数据 | 跨版本兼容性好,可选择性恢复单个库/集合 | 恢复速度慢,对大数据量不友好 |
| 文件系统快照(如 LVM) | 物理快照 | 恢复快,几乎无损 | 只适用于单机,复制集场景需要所有节点同时快照 |
| 云厂商自带备份 | 物理/逻辑 | 运维成本低,支持按时间点恢复 | 粒度受产品限制,成本会随存储量上升 |
我的推荐是:大库用云厂商的物理备份,小库用mongodump,两者都跑,且每天至少验证一次备份文件是否能正常启动。
6.2 一个我踩过的备份恢复坑
有一次,我用mongodump做全量备份,命令是:
mongodump --host localhost:27017 --db test --out /backup/test操作很顺利,日志没有任何报错。一个月后真的需要恢复时,我用mongorestore来导数据,结果发现备份包里少了两个集合。排查原因是:备份期间刚好有均衡器在迁移 chunk(分片集群),mongodump默认不等待迁移完成,导致某些集合被 dump 成空集合。
后来我改用--forceTableScan参数,并在备份前手动停止 balancer:
sh.stopBalancer()备份完成后:
sh.startBalancer()这样才彻底解决了备份"成功"但数据不完整的问题。
6.3 备份验证的自动化脚本
我平时会写一个简单的 Java 工具类或者 shell 脚本,每天凌晨备份完以后,自动做三件事:
- 检查
mongorestore能否在临时目录里正确恢复 - 恢复后随机抽查几个集合的文档数是否与源库一致
- 把
mongodump的退出码和日志发到告警群
这个脚本的原理不复杂,但它能保证你在灾难发生时,手里那份备份一定是能用的。
#!/bin/bash # 备份后校验文档数是否匹配 SOURCE_COUNT=$(mongosh --quiet --eval "db.test.countDocuments()" mongodb://localhost:27017/test) RESTORE_COUNT=$(mongosh --quiet --eval "db.test.countDocuments()" mongodb://localhost:27018/test_restore) if [ "$SOURCE_COUNT" != "$RESTORE_COUNT" ]; then echo "Backup verification failed: $SOURCE_COUNT vs $RESTORE_COUNT" fi这一套流程跑起来之后,我对"数据到底丢没丢"这个问题就变得特别有底气:就算它真丢了,我也能在明确的时间内给出恢复方案,而不是徒手翻 oplog 碰运气。
7. 一点总结性质的个人体会
写了这么多,最后说点掏心窝子的话。排查 MongoDB 数据丢失,最怕的不是技术细节复杂,而是你心态崩了之后开始瞎试。正确姿势是先按链条把"问题定界"做清楚——数据到底在哪里丢的、哪一层丢的、什么时候丢的,再对症下药。
另外,平时一定要养成一个习惯:每次接线上问题,先在群里丢一条消息记录"当前时间 + 问题现象 + 影响范围",然后才去查。这既是对业务的交代,也是对自己排查过程的时间锚点。数据丢失类问题最忌讳的就是查到最后自己也忘了是从哪个时间点开始查的。
如果你手头也正被类似的问题困扰,我的建议是:先把本文 4.1 到 4.3 那几条命令完整跑一遍。大部分"莫名其妙"的问题,跑完这几步就"莫名其妙"地定位到了。如果最后真的查无实据,那就坚定地走备份恢复流程,不要在一个方向上死磕超过一个小时。时间拖得越久,业务受到的影响就越大。