deleteMany 不会触发全局写锁。MongoDB 4.0+ 使用 WiredTiger 引擎,采用文档级锁,但无索引时全表扫描会导致逻辑长时锁;加索引仍慢因 WAL、B-tree 更新等开销;应分批删除、错峰执行、避免 collation 和类型不匹配。

deleteMany 会触发全局写锁吗?
不会。MongoDB 4.0+ 默认存储引擎 WiredTiger 使用文档级并发控制,deleteMany 不会锁整张表或整个数据库,但会按匹配文档逐个加排他锁(X lock)——锁粒度是文档,不是集合。
不过,如果删除条件没走索引,WiredTiger 仍需扫描全集合,此时大量文档被逐个锁定、检查、删除,实际效果接近“逻辑上的长时持有锁”,其他写操作(如 updateOne、insertOne)在争抢相同文档或临近数据页时就会排队等待。
常见错误现象:db.currentOp() 显示大量 delete 操作状态为 "active": true,同时 db.serverStatus().metrics.locks 中 ParallelBatchWriterMode 或 Global 的 acquireCount.w 持续飙升,说明锁竞争已发生。
为什么加了索引,deleteMany 还很慢?
索引能加速定位,但不能跳过 WiredTiger 的底层开销:每删一条文档,都要做日志写入(WAL)、更新 B-tree 索引节点、释放空间并标记为可重用——这些动作本身有 CPU 和 I/O 成本,尤其当单次 deleteMany 删除几十万文档时,日志刷盘和内存压力会明显拖慢整体吞吐。
使用场景中容易忽略的点:
- 即使条件字段有索引,若删除文档体积大(比如含大数组或嵌套对象),WiredTiger 清理对应磁盘页的代价更高
- 副本集环境下,
w: "majority"的 write concern 会让主节点等多数从节点落盘后才返回,进一步拉长感知延迟 - 如果集合启用了
collation,每次比较都要做字符串归一化,CPU 开销翻倍
性能影响示例:删除 50 万条平均 8KB 的文档,无 collation + 索引命中,耗时约 12 秒;开启 collation: { locale: "zh" } 后升至 47 秒。
如何避免 deleteMany 导致的写阻塞?
核心思路是“拆小、控速、错峰”。WiredTiger 并不支持事务回滚式批量删除,所以不能靠单一大事务来保证原子性,反而要主动放弃原子性换稳定性。
实操建议:
- 用
find先查出待删文档的_id列表,按每批 1000–5000 个分段,再调用deleteMany({ _id: { $in: [...] } })——确保_id走默认索引,避免全表扫描 - 每批之间加
sleep(100)(毫秒级),让 WiredTiger 有时间完成 checkpoint 和 page 回收,缓解内存压力 - 避开业务高峰执行,例如凌晨 2–4 点;若必须在线清理,用
maxTimeMS控制单次最长执行时间:db.collection.deleteMany({ ... }, { maxTimeMS: 3000 }) - 删除后观察
db.collection.stats().size和storageSize差值,若后者远大于前者,说明碎片多,需后续执行compact(注意:该命令会短暂阻塞该集合所有读写)
deletedCount: 0 却不报错,是 bug 吗?
不是 bug,是 MongoDB 的明确设计行为。deleteMany 返回 { acknowledged: true, deletedCount: 0 } 表示“查询合法、权限正常、执行成功、但没匹配到任何文档”——这和 SQL 的 DELETE WHERE false 返回影响行数 0 是同一语义。
容易踩的坑:
- 误以为是网络中断或权限失败,其实连接和认证都 OK
- 在监控告警里把
deletedCount === 0当异常处理,结果误报;正确做法是结合matchedCount(4.2+ 支持)判断是否条件写错 - 用
deleteMany清理过期数据时,如果时间字段类型不一致(比如存的是字符串"2024-01-01",而查询用new Date()),也会静默返回 0,且无提示
真正危险的不是 deletedCount: 0,而是没加索引 + 大范围条件导致的隐式全表扫描——它不报错,但会悄悄拖垮整个实例的写吞吐。











