软删除字段推荐用Date类型且默认值为null,支持索引与时间审计;所有find查询需通过middleware自动添加{deletedAt: {$eq: null}}过滤,禁用原生delete方法,聚合与populate须显式过滤。

软删除字段该用什么类型和默认值?
软删除的核心是加一个标记字段(比如 deletedAt),而不是真正删掉文档。这个字段必须能区分“未删除”和“已删除”两种状态,且支持索引和时间范围查询。推荐用 Date 类型,初始值设为 null(不是 undefined,MongoDB 不索引 undefined 值):
deletedAt: { type: Date, default: null, index: true }用 null 而不用 false 或字符串,是因为后续可以存删除时间戳,方便审计;同时 null 在 MongoDB 查询中可被 {$ne: null} 或 {$eq: null} 精确匹配,布尔类型反而容易和业务字段语义混淆。
如何让所有 find 查询自动排除已删除文档?
Mongoose 的 query middleware 是最稳妥的方式,在所有 find、findOne、countDocuments 等读操作前注入过滤条件。注意必须同时拦截 count 和 estimatedDocumentCount(后者不走 query middleware,需单独处理):
schema.pre('find', function(next) {<br> this.where({ deletedAt: { $eq: null } });<br> next();<br>});<br>schema.pre('findOne', function(next) {<br> this.where({ deletedAt: { $eq: null } });<br> next();<br>});别漏掉 findById —— 它本质是 findOne,会被上面的钩子覆盖。如果用了 lean() 或聚合管道(aggregate),middleware 不生效,得手动加 match 阶段。deleteOne / findByIdAndDelete 为什么会绕过软删除?
这些方法默认执行的是**硬删除**,完全跳过 schema 和 middleware。直接调用它们等于把软删除逻辑架空。正确做法是:禁用原生删除方法,统一走自定义实例方法或静态方法:
// 禁用危险方法<br>schema.methods.softDelete = async function() {<br> this.deletedAt = new Date();<br> return this.save();<br>};<br>schema.statics.softDeleteById = function(id) {<br> return this.findByIdAndUpdate(id, { deletedAt: new Date() }, { new: true });<br>};并在模型定义后立刻重写:MyModel.deleteOne = () => { throw new Error('Use softDelete instead'); };<br>MyModel.findByIdAndDelete = () => { throw new Error('Use softDeleteById instead'); };否则团队里有人顺手写 await User.findByIdAndDelete(id),数据就真没了。
聚合查询和 populate 怎么保持软删除过滤?
聚合管道(aggregate)不触发 query middleware,必须显式加 $match:
User.aggregate([<br> { $match: { deletedAt: { $eq: null } } },<br> { $lookup: { from: 'posts', localField: '_id', foreignField: 'userId', as: 'posts' } },<br> { $unwind: '$posts' },<br> { $match: { 'posts.deletedAt': { $eq: null } } } // 关联集合也要过滤<br>])populate 默认也不检查 deletedAt,需在选项里指定条件:User.findById(id).populate({<br> path: 'posts',<br> match: { deletedAt: { $eq: null } }<br>})漏掉关联表的过滤,会导致查出“用户没删但文章已删”的脏数据——这是线上最容易被忽略的坑。


















