MongoDB 索引与事务

索引与优化

1.1 索引类型

类型说明
单字段索引最基础,{field: 1} / {field: -1}
复合索引多字段组合,遵循最左前缀
多键索引(Multikey)字段是数组时自动创建,每个数组元素一条索引项
唯一索引{unique: true}_id 默认唯一且不可删
稀疏索引只索引「存在该字段」的文档
部分索引partialFilterExpression 只索引满足条件的文档,比稀疏更灵活
TTL 索引基于 Date 字段到期自动删除
文本索引{field: "text"},支持全文检索
地理空间索引2dsphere / 2d
哈希索引{field: "hashed"},用于哈希分片
每个集合默认在 _id 字段上有一个唯一索引。
//  个人最常用的是单字段索引、复合索引、后台建立索引、查询集合索引
db.user.createIndex({ name: 1 })
db.user.createIndex({ name: 1, age: -1 })                                   // 复合
db.user.createIndex({ name: 1 }, { unique: true })                           // 唯一
db.user.createIndex({ email: 1 }, { unique: true, sparse: true })            // 稀疏
db.user.createIndex({ age: 1 }, { partialFilterExpression: { age: { $gt: 18 } } }) // 部分
db.user.createIndex({ createTime: 1 }, { expireAfterSeconds: 3600 })         // TTL
db.user.createIndex({ desc: "text" })                                        // 文本
db.user.createIndex({ loc: "2dsphere" })                                     // 地理
db.user.createIndex({ shardKey: "hashed" })                                  // 哈希
db.users.createIndex({ age: 1 }, { background: true }) // 后台建立索引(避免生产环境读写被阻塞)
db.user.getIndexes()  
db.user.dropIndex("name_1")

1.2 索引核心规则

问题答题要点
最左前缀原则查询条件必须包含复合索引的前导字段,否则用不上
ESR 规则复合索引字段顺序按 Equality(等值查询) → Sort(排序查询) → Range(范围查询) 排列
覆盖索引查询条件 + 返回字段全在索引里,不用回表;explain 结果里没有 FETCH 阶段;注意排除 _id(投影加 _id: 0
索引失效场景$ne / $nin / $not$regex^ 前缀;类型不一致(用字符串查数字);跳过复合索引前导字段;$or 中有字段没索引
索引的代价占空间 + 写放大(每次写都要维护索引),不是越多越好

1.3 与 MySQL 索引对比

维度MySQL(InnoDB)MongoDB
索引结构B+树B树
索引类型主键、唯一、普通、联合、全文等单字段、复合、多键、文本、地理、哈希、TTL
最左前缀支持支持(复合索引)
索引覆盖支持(覆盖索引)支持(covered query)
索引代价占用空间,降低写入速度同上

1.4 慢查询定位

// 有阿里云服务器的,可查询MongoDB慢SQL日志
// 1. 开启慢查询采集(慢于 100ms 记录到 system.profile)
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find().sort({ ts: -1 }).limit(10)

// 2. 执行计划分析
db.user.find({ age: { $gt: 18 } }).explain("executionStats")

看三个关键值:

  • stageCOLLSCAN 全表扫描(要警惕) / IXSCAN 走索引(正常)
  • totalDocsExaminednReturned:比值应接近 1,差得越多说明索引选择性越差
  • executionTimeMillis:实际耗时

辅助工具:mongostat(整体吞吐)、mongotop(各集合读写耗时)、db.currentOp()(当前操作)。


1.5 TTL / 稀疏 / 部分索引的坑

  • TTL 索引基于 Date 字段 + expireAfterSeconds;后台线程每 60 秒扫一次,删除时间不精确;被 TTL 删除的文档同样会产生 oplog
  • 稀疏索引只跳过「完全没有该字段」的文档,字段值为 null 仍会被索引
  • 部分索引用 partialFilterExpression,表达力更强,优先选部分索引

1.6 性能优化

  1. 为高频查询字段建索引,尤其是 wheresortjoin 用到的字段。
  2. 复合索引字段顺序:等值查询在前,范围查询在后,排序字段最后。
  3. 避免过多索引:每个索引都会降低写入速度,增加存储。
  4. 使用覆盖查询:尽量让查询字段和返回字段都包含在索引中。
  5. 使用部分索引:只索引常用子集,减少索引大小。
  6. 使用 TTL 索引:自动清理过期数据。
  7. 定期分析慢查询:开启 profiling,用 explain 诊断。
  8. 避免在索引字段上使用 $ne$not$regex(非前缀),这些通常导致全表扫描。

事务与一致性

一、事务基础概念

1.1 什么是事务

事务是一组操作的集合,这些操作要么全部成功,要么全部失败,保证数据的一致性。

1.2 MongoDB 事务的发展

版本支持情况
4.0副本集支持多文档事务
4.2分片集群支持多文档事务
4.4支持在事务中创建集合和索引
5.0事务性能优化,支持更多场景

1.3 与关系型数据库的对比

维度MySQLMongoDB
事务粒度行级、表级文档级、多文档
隔离级别读未提交、读已提交、可重复读(默认)、串行化快照隔离(Snapshot Isolation)
默认隔离级别可重复读快照隔离
跨分片事务支持4.2+ 支持
性能开销较高较高,但通常低于跨分片
使用复杂度简单、SQL标准需要Session
适用场景强事务、复杂关联灵活文档、水平扩展、事务需求少
const session = db.getMongo().startSession();
session.startTransaction();
try {
  session.getDatabase("app").orders.updateOne({ _id: 1 }, { $set: { status: "PAID" } });
  session.getDatabase("app").stock.updateOne({ sku: "A" }, { $inc: { qty: -1 } });
  session.commitTransaction();
} catch (e) {
  session.abortTransaction();
}

二、单文档原子性

2.1 核心原则

单文档原子性:MongoDB 对单个文档的更新天然原子(含嵌套子文档、数组),很多「伪事务」场景其实一条 updateOne 就能解决,不必上事务。

2.3 与多文档事务的关系

  • 单文档原子性不需要显式事务,MongoDB 自动保证。
  • 多文档事务只在需要跨文档、跨集合、跨分片时使用。

三、多文档事务

3.1 使用场景

  • 跨集合操作(如订单和库存)
  • 跨文档操作(如转账)
  • 跨分片操作(分片集群)

3.2 基本语法

const session = db.getMongo().startSession();
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
});

try {
  const orders = session.getDatabase("shop").orders;
  const inventory = session.getDatabase("shop").inventory;

  orders.updateOne({ _id: 1 }, { $set: { status: "confirmed" } });
  inventory.updateOne({ sku: "A001" }, { $inc: { stock: -1 } });

  session.commitTransaction();
} catch (error) {
  session.abortTransaction();
  throw error;
} finally {
  session.endSession();
}

3.3 读关注(readConcern)

决定读取操作能看到什么样的数据
控制读的时候,数据需要被多少个节点确认,或者是否需要一致性快照

级别说明
local默认,读取当前节点最新数据,可能回滚
majority读取已确认多数节点的数据,不会回滚
snapshot事务内使用,保证一致性快照
linearizable线性一致,保证读到最新已提交数据

3.4 写关注(writeConcern)

写操作需要被确认到什么程度,才返回成功
控制写操作 要写到多少个节点、是否要落盘,才算成功

级别说明适用场景
w: 1主节点确认即可一般写入,性能优先
w: majority多数节点确认,推荐用于事务重要数据,防止回滚
w: 0不等待确认,直接返回日志、监控等可容忍丢失场景
j: true写入 journal 后才确认需持久化保证
w: majority + j: true多数节点确认且落盘最高持久性,性能最差

四、ACID 特性

特性MongoDB 支持情况
原子性(Atomicity)单文档天然原子;多文档事务保证全部成功或全部失败
一致性(Consistency)事务提交后数据满足约束;应用层需自行保证业务规则
隔离性(Isolation)快照隔离,事务之间互不干扰;写冲突时事务中止并重试
持久性(Durability)依赖 writeConcern,w:majority + j:true 可保证

五、使用限制与注意事项

5.1 限制

  • 事务超时:默认 60 秒,可调整 transactionLifetimeLimitSeconds
  • 文档大小:事务中所有操作的总大小不能超过 16MB(oplog 限制)。
  • 不支持的操作
    • 创建或删除集合(4.4 之前)
    • 创建或删除索引(4.4 之前)
    • count 命令(可使用 countDocuments
    • distinct 命令
    • 某些管理命令
  • 分片集群:跨分片事务性能开销大,需谨慎使用。
  • 写冲突:事务内写冲突会导致事务中止,应用需重试。

5.2 性能开销

  • 多文档事务比单文档操作慢,因为需要协调多个节点、维护快照、写 oplog。
  • 事务越长,持有锁和资源越久,冲突概率越高。
  • 建议事务尽量短小,避免长时间运行。

5.3 重试机制

// 事务因写冲突中止时,应用层应重试
function runTransactionWithRetry(session, txnFunc) {
  while (true) {
    try {
      session.startTransaction();
      txnFunc(session);
      session.commitTransaction();
      break;
    } catch (error) {
      session.abortTransaction();
      if (error.hasErrorLabel("TransientTransactionError")) {
        continue; // 重试
      }
      throw error;
    }
  }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值