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

MongoDB删除文档操作详解

2021-04-212.1k 阅读

MongoDB删除文档基础操作

在MongoDB中,删除文档是一项重要的操作。删除文档使用deleteOne()deleteMany()方法。

deleteOne()方法

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

语法

db.collection.deleteOne(
   <filter>,
   {
     writeConcern: <document>
   }
)
  • <filter>:用于指定删除条件的文档。
  • writeConcern(可选):用于指定写入操作的确认级别。

示例:假设有一个名为students的集合,存储学生信息,文档结构如下:

{
  "name": "Alice",
  "age": 20,
  "course": "Math"
}

要删除名为Bob的学生文档,可以这样操作:

db.students.deleteOne({ "name": "Bob" });

这里,{ "name": "Bob" }就是过滤条件,它告诉MongoDB只删除name字段为Bob的文档。

deleteMany()方法

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

语法

db.collection.deleteMany(
   <filter>,
   {
     writeConcern: <document>
   }
)
  • <filter>:指定删除条件的文档。
  • writeConcern(可选):指定写入操作的确认级别。

示例:在students集合中删除所有年龄小于18岁的学生文档:

db.students.deleteMany({ "age": { $lt: 18 } });

这里的{ "age": { $lt: 18 } }作为过滤条件,表示删除age字段值小于18的所有文档。

深入理解删除条件

使用比较运算符

在删除文档时,比较运算符是常用的条件设定方式。除了前面提到的$lt(小于),还有以下常见的比较运算符:

  • $gt:大于
  • $lte:小于等于
  • $gte:大于等于
  • $ne:不等于

示例:删除students集合中年龄大于等于25岁的学生文档:

db.students.deleteMany({ "age": { $gte: 25 } });

示例:删除students集合中课程不是Computer Science的学生文档:

db.students.deleteMany({ "course": { $ne: "Computer Science" } });

使用逻辑运算符

逻辑运算符可以组合多个条件,使删除条件更加灵活。常见的逻辑运算符有:

  • $and:用于连接多个条件,所有条件都必须满足。
  • $or:用于连接多个条件,只要有一个条件满足即可。
  • $not:对条件进行取反。

示例:使用$and删除students集合中年龄大于20岁且课程为Physics的学生文档:

db.students.deleteMany({
  $and: [
    { "age": { $gt: 20 } },
    { "course": "Physics" }
  ]
});

示例:使用$or删除students集合中年龄小于18岁或者年龄大于30岁的学生文档:

db.students.deleteMany({
  $or: [
    { "age": { $lt: 18 } },
    { "age": { $gt: 30 } }
  ]
});

示例:使用$not删除students集合中年龄不等于22岁的学生文档:

db.students.deleteMany({ "age": { $not: { $eq: 22 } } });

使用数组运算符

当文档中包含数组字段时,可以使用数组运算符来设定删除条件。例如:

  • $in:判断字段值是否在给定的数组中。
  • $nin:判断字段值是否不在给定的数组中。

假设students集合中的文档结构如下,增加了一个hobbies数组字段:

{
  "name": "Charlie",
  "age": 23,
  "course": "Biology",
  "hobbies": ["reading", "swimming", "coding"]
}

示例:删除students集合中爱好包含dancing的学生文档:

db.students.deleteMany({ "hobbies": { $in: ["dancing"] } });

示例:删除students集合中爱好不包含traveling的学生文档:

db.students.deleteMany({ "hobbies": { $nin: ["traveling"] } });

删除文档与索引的关系

在MongoDB中,索引对删除操作有着重要的影响。

索引加速删除操作

当删除文档时,如果删除条件字段上有索引,MongoDB可以利用索引快速定位到要删除的文档。例如,在students集合的name字段上创建了索引:

db.students.createIndex({ "name": 1 });

之后执行删除操作:

db.students.deleteOne({ "name": "David" });

此时,MongoDB可以通过name字段的索引快速找到名为David的文档并删除,大大提高了删除操作的效率。

删除文档对索引的影响

当删除文档时,MongoDB会自动更新相关的索引。如果删除的文档的索引键值是唯一的,并且该文档是该键值的唯一实例,MongoDB会从索引中删除对应的索引项。如果索引键值不是唯一的,MongoDB会更新索引以反映文档的删除。

例如,在students集合中,name字段是唯一索引:

db.students.createIndex({ "name": 1 }, { unique: true });

当删除名为Eve的学生文档后,MongoDB会从name字段的唯一索引中删除Eve对应的索引项。

删除文档时的注意事项

谨慎使用无过滤条件的删除

在使用deleteMany()方法时,如果不指定过滤条件,将会删除集合中的所有文档。例如:

db.students.deleteMany({});

这会将students集合中的所有文档删除,这是一个非常危险的操作,在生产环境中一定要谨慎使用,务必确保在执行前进行了充分的备份。

写入关注点对删除操作的影响

writeConcern参数可以控制删除操作的确认级别。不同的确认级别会影响删除操作的性能和可靠性。

  • { writeConcern: { w: 1 } }:默认值,MongoDB会等待确认写操作已写入主节点。
  • { writeConcern: { w: "majority" } }:MongoDB会等待确认写操作已被大多数副本节点确认。

例如,使用w: "majority"的确认级别删除文档:

db.students.deleteOne({ "name": "Frank" }, { writeConcern: { w: "majority" } });

使用w: "majority"会使删除操作更可靠,但由于需要等待大多数副本节点的确认,性能会有所下降。

删除操作的原子性

在单文档删除操作(deleteOne())中,MongoDB保证操作的原子性。也就是说,要么整个文档被成功删除,要么操作失败,不会出现部分删除的情况。

然而,在多文档删除操作(deleteMany())中,MongoDB不保证跨多个文档的原子性。如果在删除多个文档过程中出现错误,已经删除的文档不会回滚,未删除的文档可能会因为错误而未被处理。

例如,在删除一组文档时,如果在删除过程中网络中断,已经删除的文档不会恢复,而剩余的文档可能无法被删除。为了确保多文档删除操作的一致性,可以使用事务(从MongoDB 4.0开始支持)。

使用事务删除文档

从MongoDB 4.0开始,支持多文档事务,这使得在删除多个文档时可以保证原子性和一致性。

开启事务

在MongoDB中,使用startTransaction()方法开启事务:

session = db.getMongo().startSession();
session.startTransaction();

在事务中执行删除操作

在事务中,可以使用deleteOne()deleteMany()方法删除文档。例如,在一个事务中删除两个集合中的相关文档:

try {
  session.startTransaction();
  db.students.deleteMany({ "age": { $lt: 18 } }, { session });
  db.student_grades.deleteMany({ "student_age": { $lt: 18 } }, { session });
  session.commitTransaction();
} catch (e) {
  session.abortTransaction();
  print("Transaction aborted due to error: " + tojson(e));
}

在这个例子中,首先开启事务,然后在students集合中删除年龄小于18岁的学生文档,同时在student_grades集合中删除对应年龄小于18岁学生的成绩文档。如果所有操作都成功,提交事务;如果出现错误,回滚事务。

事务的局限性

虽然事务提供了多文档操作的原子性和一致性,但也有一些局限性:

  • 性能开销:事务需要额外的资源来管理,可能会影响系统的整体性能。
  • 嵌套事务:MongoDB目前不支持嵌套事务。
  • 跨分片事务:跨分片事务的实现相对复杂,并且在某些情况下可能会有性能问题。

监控和分析删除操作

使用日志监控删除操作

MongoDB的日志文件记录了数据库的各种操作,包括删除操作。通过查看日志文件,可以了解删除操作的执行时间、操作类型、影响的文档数量等信息。

在MongoDB配置文件中,可以设置日志级别来控制日志的详细程度。例如,将日志级别设置为verbose可以获取更详细的操作信息:

systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true
  verbosity: 3

之后,在日志文件中可以找到类似以下的删除操作记录:

2023-10-01T12:34:56.789+0000 I COMMAND  [conn123] command students.$cmd command: delete { delete: "students", deletes: [ { q: { name: "Grace" }, limit: 1 } ], writeConcern: { w: 1, wtimeout: 0 } } keyUpdates:0 writeConflicts:0 numYields:0 reslen:35 locks:{ Global: { acquireCount: { r: 2, w: 1 } }, Database: { acquireCount: { w: 1 } }, Collection: { acquireCount: { w: 1 } } } protocol:op_command 100ms

这条记录显示了在students集合中删除名为Grace的文档的操作,包括操作时间、操作命令、影响的文档数量、锁信息以及执行时间等。

使用explain()分析删除操作

explain()方法可以用于分析删除操作的执行计划。虽然explain()通常用于查询操作,但在删除操作中也能提供有价值的信息,例如是否使用了索引、扫描的文档数量等。

示例:对删除操作使用explain()

db.students.deleteMany({ "age": { $gt: 25 } }).explain("executionStats");

执行上述命令后,会返回一个包含详细执行统计信息的文档,例如:

{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.students",
    "indexFilterSet": false,
    "parsedQuery": {
      "age": {
        "$gt": 25
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "age": {
          "$gt": 25
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": []
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 0,
    "executionTimeMillis": 10,
    "totalKeysExamined": 0,
    "totalDocsExamined": 100,
    "executionStages": {
      "stage": "COLLSCAN",
      "filter": {
        "age": {
          "$gt": 25
        }
      },
      "nReturned": 0,
      "executionTimeMillisEstimate": 5,
      "works": 101,
      "advanced": 0,
      "needTime": 100,
      "needYield": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "direction": "forward",
      "docsExamined": 100
    }
  },
  "serverInfo": {
    "host": "localhost",
    "port": 27017,
    "version": "4.4.9",
    "gitVersion": "5555555555555555555555555555555555555555"
  }
}

从这个结果中可以看出,该删除操作使用了全表扫描(COLLSCAN),扫描了100个文档,执行时间为10毫秒等信息。通过这些信息,可以优化删除操作,例如为age字段创建索引以避免全表扫描。

删除操作在不同部署模式下的行为

单机模式下的删除操作

在单机模式下,删除操作相对简单直接。当执行deleteOne()deleteMany()方法时,MongoDB直接在本地数据库文件中删除相应的文档,并更新相关的索引。

由于没有副本节点和分片的复杂性,单机模式下的删除操作性能主要取决于服务器的硬件资源和数据库的大小。例如,在一个配置较低的单机服务器上删除大量文档时,可能会因为磁盘I/O或CPU资源紧张而导致操作变慢。

副本集模式下的删除操作

在副本集模式下,删除操作首先在主节点上执行。当主节点成功删除文档后,会将删除操作的日志(oplog)同步到副本节点。副本节点接收到oplog后,会在本地执行相同的删除操作,以保持数据的一致性。

如果在删除操作过程中主节点发生故障,副本集将会进行选举,选举出新的主节点。在这个过程中,未完成同步的删除操作可能会出现短暂的不一致,但当新主节点选举完成并同步完成后,数据会恢复一致。

例如,在一个三节点的副本集中,主节点执行删除操作后,其中一个副本节点可能因为网络延迟而未及时同步oplog。此时,如果查询该副本节点,可能会发现要删除的文档仍然存在,但当网络恢复正常,副本节点同步完oplog后,文档就会被删除。

分片集群模式下的删除操作

在分片集群模式下,删除操作会更加复杂。MongoDB首先会根据删除条件确定要删除的文档所在的分片。然后,协调器(mongos)会将删除操作分发到相应的分片上执行。

每个分片会在本地执行删除操作,并更新本地的索引。与副本集类似,分片内部也会通过oplog来同步删除操作,以保证分片内数据的一致性。

例如,在一个包含多个分片的集群中,要删除students集合中年龄大于30岁的学生文档。MongoDB会根据age字段的分片键(如果有)或其他路由规则,确定哪些分片包含符合条件的文档,然后将删除操作发送到这些分片上执行。

与其他数据库删除操作的对比

与关系型数据库删除操作对比

  • 语法差异:关系型数据库(如MySQL)使用DELETE FROM语句,语法相对固定。例如,在MySQL中删除students表中年龄大于20岁的记录:
DELETE FROM students WHERE age > 20;

而MongoDB使用deleteOne()deleteMany()方法,语法更加灵活,且基于文档结构。

  • 事务支持:大多数关系型数据库从设计之初就对事务有较好的支持,在删除多个相关记录时可以很方便地保证原子性和一致性。而MongoDB在4.0版本之前对多文档事务支持有限,4.0之后才引入了多文档事务功能。

  • 索引影响:关系型数据库和MongoDB在删除操作时都依赖索引来提高效率。但关系型数据库的索引管理相对复杂,例如删除操作可能会导致索引碎片,需要定期进行索引重建或优化。而MongoDB在删除文档时,会自动更新相关索引,相对来说在索引管理上较为简单。

与其他非关系型数据库删除操作对比

  • 与Redis删除操作对比:Redis主要用于缓存和简单数据存储,其删除操作主要针对键值对。使用DEL命令删除一个或多个键。例如:
DEL key1 key2

Redis没有文档结构,删除操作相对简单快速。而MongoDB基于文档,删除操作可以基于复杂的文档条件,功能更为丰富,但操作相对复杂。

  • 与Cassandra删除操作对比:Cassandra的删除操作使用DELETE语句,与关系型数据库类似。但Cassandra的数据模型基于列族,删除操作可能涉及到墓碑(tombstone)机制来标记已删除的数据,以便在后续的压缩和修复过程中彻底删除。MongoDB则没有墓碑机制,删除操作直接从数据库文件中移除文档。

通过以上对MongoDB删除文档操作的详细解析,包括基础操作、条件设定、与索引及事务的关系、注意事项、监控分析以及不同部署模式下的行为和与其他数据库的对比,希望能帮助读者全面深入地理解和掌握MongoDB的删除文档操作,在实际应用中能够更加高效、安全地管理数据库中的数据。