No Referential Integrity — Orphaned References
db.users.deleteOne({ _id: ObjectId("user1") })
function deleteUserAndOrders(userId) {
db.orders.deleteMany({ userId: ObjectId(userId) })
db.users.deleteOne({ _id: ObjectId(userId) })
}
null Filter Matches More Than Expected
db.orders.deleteMany({ status: null })
db.orders.deleteMany({ status: { $eq: null, $exists: true } })
db.orders.deleteMany({ status: { $exists: false } })
Type Sensitivity — Won't Match Wrong Type
db.products.deleteMany({ price: { $lt: 0 } })
db.products.deleteMany({ price: { $type: "string" } })
deleteMany() is Not Atomic — No Rollback
const session = db.getMongo().startSession()
session.startTransaction()
try {
db.getSiblingDB("mydb").orders.deleteMany(
{ status: "cancelled" },
{ session }
)
db.getSiblingDB("mydb").audit.insertOne(
{ action: "purge_cancelled", timestamp: new Date() },
{ session }
)
session.commitTransaction()
} catch (e) {
session.abortTransaction()
} finally {
session.endSession()
}
Soft Delete Pattern — Don't Delete, Mark Instead
db.users.deleteOne({ _id: userId })
db.users.updateOne(
{ _id: userId },
{
$set: { deletedAt: new Date(), isDeleted: true },
$unset: { sessionToken: "" }
}
)
WARNHard deletes are irreversible — once committed, there is no "undo" in MongoDB without a backup. Use soft deletes (mark isDeleted: true) for user-facing data, and reserve hard deletes for truly temporary or expired data.