MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MongoDB删除文档的方法与参数详解

2022-06-303.9k 阅读

MongoDB 删除文档的基础方法

在 MongoDB 中,删除文档是一项常见的操作,它允许我们从集合中移除不再需要的数据。删除文档的主要方法是 deleteOne()deleteMany()

deleteOne() 方法

deleteOne() 方法用于删除符合指定条件的单个文档。如果集合中有多个文档满足条件,它只会删除第一个匹配的文档。

语法

db.collection.deleteOne(
   <filter>,
   {
     writeConcern: <document>
   }
)
  • <filter>:一个文档,用于指定删除条件。例如 { "name": "John" },它会匹配 name 字段值为 John 的文档。
  • writeConcern(可选):一个文档,用于指定写入操作的确认级别。默认情况下,MongoDB 使用 { w: 1 },这意味着 MongoDB 会等待确认写入操作已传播到副本集的主节点。

示例: 假设我们有一个名为 users 的集合,其中包含以下文档:

[
  { "name": "John", "age": 30 },
  { "name": "Jane", "age": 25 },
  { "name": "Bob", "age": 35 }
]

要删除 nameJohn 的文档,可以使用以下代码:

db.users.deleteOne({ "name": "John" });

执行此操作后,users 集合将变为:

[
  { "name": "Jane", "age": 25 },
  { "name": "Bob", "age": 35 }
]

deleteMany() 方法

deleteMany() 方法用于删除符合指定条件的所有文档。

语法

db.collection.deleteMany(
   <filter>,
   {
     writeConcern: <document>
   }
)
  • <filter>writeConcern 的含义与 deleteOne() 方法中的相同。

示例: 继续使用 users 集合,如果我们要删除所有年龄大于 30 的文档,可以这样做:

db.users.deleteMany({ "age": { $gt: 30 } });

执行后,users 集合将变为:

[
  { "name": "Jane", "age": 25 }
]

复杂删除条件

在实际应用中,删除条件可能会更加复杂。MongoDB 提供了丰富的查询操作符来构建复杂的删除条件。

逻辑操作符

  1. $and:用于指定多个条件都必须满足。 语法:{ $and: [ { <condition1> }, { <condition2> },... ] } 示例:假设我们有一个 products 集合,要删除价格大于 100 且库存小于 50 的产品。
db.products.deleteMany({
  $and: [
    { "price": { $gt: 100 } },
    { "stock": { $lt: 50 } }
  ]
});
  1. $or:用于指定多个条件中只要有一个满足即可。 语法:{ $or: [ { <condition1> }, { <condition2> },... ] } 示例:要删除类别为 electronics 或者评分小于 3 的产品。
db.products.deleteMany({
  $or: [
    { "category": "electronics" },
    { "rating": { $lt: 3 } }
  ]
});
  1. $not:用于对条件进行取反。 语法:{ <field>: { $not: { <operator-expression> } } } 示例:要删除名称不是 iPhone 的产品。
db.products.deleteMany({ "name": { $not: { $eq: "iPhone" } } });

比较操作符

除了前面提到的 $gt(大于)、$lt(小于)、$eq(等于)之外,还有:

  1. $gte:大于等于。 示例:删除价格大于等于 50 的产品。
db.products.deleteMany({ "price": { $gte: 50 } });
  1. $lte:小于等于。 示例:删除库存小于等于 10 的产品。
db.products.deleteMany({ "stock": { $lte: 10 } });
  1. $ne:不等于。 示例:删除颜色不是 red 的产品。
db.products.deleteMany({ "color": { $ne: "red" } });

数组操作符

  1. $in:用于检查字段值是否在指定的数组内。 语法:{ <field>: { $in: [ <value1>, <value2>,... ] } } 示例:假设 products 集合中有一个 tags 字段是数组类型,要删除标签包含 featurednew 的产品。
db.products.deleteMany({ "tags": { $in: ["featured", "new"] } });
  1. $nin:与 $in 相反,检查字段值是否不在指定的数组内。 语法:{ <field>: { $nin: [ <value1>, <value2>,... ] } } 示例:删除标签不包含 sale 的产品。
db.products.deleteMany({ "tags": { $nin: ["sale"] } });

删除操作与索引

在 MongoDB 中,索引对于删除操作的性能有着重要的影响。当执行删除操作时,如果删除条件使用了索引字段,MongoDB 可以利用索引快速定位要删除的文档,从而提高删除操作的效率。

例如,我们在 users 集合的 email 字段上创建了索引:

db.users.createIndex({ "email": 1 });

现在,如果要删除 emailexample@example.com 的用户:

db.users.deleteOne({ "email": "example@example.com" });

由于 email 字段上有索引,MongoDB 可以快速定位到该文档并删除,而不需要全表扫描。

然而,如果删除条件不使用索引字段,MongoDB 就需要扫描整个集合来查找符合条件的文档,这在大数据量的情况下会非常耗时。

例如,要删除 bio 字段包含 engineerbio 字段没有索引:

db.users.deleteMany({ "bio": { $regex: "engineer" } });

这种情况下,MongoDB 只能逐行扫描集合中的每个文档,检查 bio 字段是否符合条件,性能会相对较差。

因此,在设计数据库架构和执行删除操作时,要充分考虑索引的使用,尽量让删除条件基于已有的索引字段,以提高删除操作的效率。

写入关注点(Write Concern)

写入关注点决定了 MongoDB 在确认写入操作成功之前需要等待的条件。在删除操作中,writeConcern 参数可以控制删除操作的确认级别。

  1. w: 1(默认):MongoDB 等待确认写入操作已传播到副本集的主节点。这意味着只要主节点确认写入成功,操作就被认为成功。这种方式提供了较好的性能,但在主节点故障且未及时同步到从节点的情况下,可能会丢失数据。 示例:
db.users.deleteOne({ "name": "Alice" }, { writeConcern: { w: 1 } });
  1. w: "majority":MongoDB 等待确认写入操作已传播到副本集的大多数节点(超过一半的节点)。这种方式提供了更高的数据安全性,因为即使主节点故障,数据也不太可能丢失,因为大多数节点都有该数据的副本。但由于需要等待多个节点的确认,性能会比 w: 1 略低。 示例:
db.users.deleteOne({ "name": "Bob" }, { writeConcern: { w: "majority" } });
  1. w: <number>:可以指定等待确认的节点数。例如 w: 3 表示等待至少 3 个节点确认写入操作成功。这种方式在性能和数据安全性之间提供了一种平衡,适用于特定的应用场景,比如需要确保在一定数量的节点上数据已被持久化。 示例:
db.users.deleteMany({ "age": { $lt: 20 } }, { writeConcern: { w: 3 } });

删除操作的原子性

在 MongoDB 中,单个文档的删除操作是原子性的。这意味着对于 deleteOne() 方法,要么整个文档被成功删除,要么文档保持不变,不会出现部分删除的情况。

对于 deleteMany() 方法,它会按顺序删除符合条件的文档,每个文档的删除操作本身是原子性的。然而,整个 deleteMany() 操作不是原子性的,因为在删除过程中,如果出现故障,可能会导致部分符合条件的文档被删除,部分未被删除。

例如,假设集合中有 10 个文档符合删除条件,在执行 deleteMany() 时,如果在删除第 5 个文档后出现故障,那么前 4 个文档会被删除,后 6 个文档仍会保留在集合中。

在一些需要更高一致性要求的场景下,我们可以结合 MongoDB 的事务功能来确保多个删除操作的原子性。例如,在一个事务中执行多个 deleteOne()deleteMany() 操作,要么所有操作都成功提交,要么所有操作都回滚,保证数据的一致性。

基于地理位置的删除

如果集合中的文档包含地理位置数据,MongoDB 提供了基于地理位置的查询和删除功能。假设我们有一个 restaurants 集合,其中每个文档包含餐厅的地理位置信息(如经纬度)。

  1. 基于距离的删除:要删除距离某个特定点一定距离内的餐厅。 假设我们有一个点的经纬度为 [longitude, latitude],要删除距离该点 1000 米内的餐厅。 首先,确保地理位置字段上有 2dsphere 索引:
db.restaurants.createIndex({ "location": "2dsphere" });

然后执行删除操作:

var point = [longitude, latitude];
db.restaurants.deleteMany({
  "location": {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: point
      },
      $maxDistance: 1000
    }
  }
});
  1. 基于几何形状的删除:要删除位于某个多边形区域内的餐厅。 假设我们定义了一个多边形的几何形状:
var polygon = {
  type: "Polygon",
  coordinates: [
    [ [lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1] ]
  ]
};

确保有 2dsphere 索引后,执行删除操作:

db.restaurants.deleteMany({
  "location": {
    $geoWithin: {
      $geometry: polygon
    }
  }
});

删除操作的性能优化

  1. 合理使用索引:如前文所述,确保删除条件基于已有的索引字段,这样可以大大提高删除操作的效率。定期分析查询和删除操作的模式,创建必要的索引。
  2. 批量删除:如果需要删除大量文档,使用 deleteMany() 方法比多次使用 deleteOne() 更高效。因为每次 deleteOne() 操作都需要与 MongoDB 服务器进行一次交互,而 deleteMany() 可以在一次交互中删除多个文档。
  3. 控制写入关注点:根据应用场景合理选择写入关注点。如果对数据安全性要求不高,使用 w: 1 可以提高性能;如果对数据安全性要求较高,使用 w: "majority" 或其他合适的 w 值。
  4. 避免全表扫描:尽量避免使用没有索引支持的复杂条件进行删除操作,因为这会导致全表扫描,在大数据量情况下性能会急剧下降。

在不同编程语言中的删除操作

  1. Node.js(使用 mongodb 驱动)
const { MongoClient } = require('mongodb');
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function deleteUser() {
  try {
    await client.connect();
    const database = client.db('test');
    const users = database.collection('users');
    const result = await users.deleteOne({ "name": "Charlie" });
    console.log(result.deletedCount);
  } finally {
    await client.close();
  }
}

deleteUser();
  1. Python(使用 pymongo 库)
from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['test']
users = db['users']

result = users.delete_one({"name": "David"})
print(result.deleted_count)
  1. Java(使用 mongodb - driver - sync 库)
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class DeleteDocument {
    public static void main(String[] args) {
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        MongoDatabase database = mongoClient.getDatabase("test");
        MongoCollection<Document> users = database.getCollection("users");

        Document filter = new Document("name", "Eve");
        users.deleteOne(filter);

        mongoClient.close();
    }
}

安全删除注意事项

  1. 备份数据:在执行大规模删除操作之前,尤其是在生产环境中,一定要备份相关数据。这样即使出现误操作,也可以恢复数据。
  2. 仔细检查条件:确保删除条件准确无误。错误的删除条件可能会导致误删大量重要数据。在执行删除操作之前,可以先使用 find() 方法验证条件,确保查询结果是预期要删除的数据。
  3. 权限管理:严格控制能够执行删除操作的用户权限。只有经过授权的用户才能执行删除操作,避免未授权的人员误操作或恶意删除数据。

通过以上对 MongoDB 删除文档方法与参数的详细介绍,希望能帮助你在实际开发中更加准确、高效地进行数据删除操作,同时保证数据的安全性和一致性。