deleteMany() 最常用但易卡顿锁表,drop() 清空分表最快;需校验时间类型、建索引、验证删除结果并注意分片、Change Stream 等影响。

deleteMany() 是最常用也最可控的方式,但直接删百万级数据容易卡住、锁表、影响线上读写;真要快,得看是不是能 drop() 整个集合。
用 deleteMany() 删除指定日期前的数据
适用于不能丢结构、字段不规律、或只删部分月份的场景。核心是写对时间条件,别漏掉时区和类型校验:
- 时间字段必须是
ISODate类型,不是字符串——用db.collection.find().limit(1)看一眼值长什么样,如果是"2025-01-01"这种字符串,$lt比较会按字典序错乱 - Python 示例里务必用
datetime.now(timezone.utc),别用datetime.now()(本地时区)或datetime.utcnow()(无时区对象),否则跨时区部署时可能漏删 - 加索引能提速:如果常按
created_at删,建单字段索引db.collection.createIndex({created_at: 1});复合条件如{status: "archived", created_at: {$lt: ...}}就得建复合索引 - 大集合慎用:800 万文档 + 5 个索引时,
deleteMany({created_at: {$lt: cutoff}})可能跑 40+ 秒,期间写锁阻塞其他操作
用 drop() 直接清空整个月份集合(最快)
如果你已按月分表(比如 logs_202501、logs_202502),且目标就是清掉整月数据,drop() 是唯一毫秒级方案:
-
db.logs_202501.drop()不扫描、不更新索引、不写 oplog,直接卸载元数据和数据文件 - 执行后
db.stats().dataSize立降,但磁盘du -sh不变——WiredTiger 后台异步回收,几小时到一天内才真正释放空间 - 必须确认三件事:
TTL 索引是否存在(drop后不会自动重建)、集合是否分片(chunk >10k 时可能短暂影响路由)、是否有Change Stream在监听(会触发invalidate事件,下游没处理就卡死) - 脚本里别写
remove({})或deleteMany({_id: {$exists: true}})——前者在 MongoDB 5.0+ 已移除,后者在某些旧驱动里可能被错误优化导致漏删
为什么 TTL 索引不适合主动清理?
TTL 索引适合“写入即过期”的场景(比如 session、临时 token),不适合按业务节奏批量清理历史日志:
- 延迟不可控:后台线程每 60 秒扫一次,高峰期可能积压数分钟;你
createIndex后立刻删不掉任何数据 - 不支持复合条件:只能基于单个日期字段,没法加
{type: "error"}这类过滤 - 无法审计:删了谁、删了多少、啥时候删的,全无记录;出问题没法回溯
- 一旦建错(比如字段不是
Date类型),文档永远不删,还占着索引空间
删完怎么验证没漏?
别只信 result.deletedCount,它只反映命令返回时的计数,事务隔离或未提交写可能导致偏差:
- 删前先查匹配数:
db.collection.countDocuments({created_at: {$lt: cutoff}}) - 删后立刻再查:
db.collection.find({created_at: {$lt: cutoff}}).limit(1),抽样看还有没有 - 如果删的是分表,检查集合是否存在:
db.getCollectionNames().includes("logs_202501")——drop()后该名应消失 - 注意:副本集环境下,主节点
drop()后从节点同步有延迟,rs.status()看optime是否追平
最常被忽略的一点:分表命名必须严格统一格式(如 yyyyMM),否则自动化脚本按正则匹配时会漏掉 logs_20251 这种少补零的集合。











