暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MongoDB-从0到1-锁模式

原创 赵师的工作日 2024-12-30
382

Snipaste_20240125_134647.png

作者:赵师的工作日(赵明中)
现役Oracle ACE、MySQL 8.0 ocp、TiDB PCTA\PCTP、Elasticsearch Certified Engineer
微信公众号:赵师的工作日
CSND:赵师的工作日
二维码.png

MongoDB 作为一个广泛使用的 NoSQL 数据库,虽然与传统的关系型数据库(如 MySQL 和 PostgreSQL)在存储和操作上有显著的差异,但它也面临着类似的并发控制问题。为了保证在多线程和多进程环境下的数据一致性和隔离性,MongoDB 实现了一套独特的锁机制。

一、MongoDB 锁的背景

MongoDB 从一开始便设计为支持高并发读写的数据库,它通过 锁机制 来管理不同操作的并发执行。与传统关系型数据库相比,MongoDB 的锁策略相对宽松,因为它的设计目标包括高可扩展性和低延迟。因此,MongoDB 的锁机制较为细粒度,并尽量减少了锁的竞争,避免了全局锁导致的性能瓶颈。
随着 MongoDB 的不断发展,它的锁机制也经历了多次优化和改进。

二、MongoDB 锁的类型

MongoDB 的锁可以分为 数据库级锁、集合级锁、文档级锁 等几种类型。根据不同的版本,MongoDB 在锁的粒度和锁的数量上做了不同程度的优化。

1、早期版本的全局锁(MongoDB 2.x)

在 MongoDB 的早期版本(MongoDB 2.x 及以前),MongoDB 使用的是 全局锁。在这种模式下,当一个客户端连接请求对数据库执行写操作时,整个数据库会被锁住。这意味着在进行写操作时,其他所有对该数据库的读取和写入请求都会被阻塞,直到当前操作完成。
全局锁的最大问题是它会造成 锁竞争,特别是在高并发场景下,多个操作争用同一个全局锁时,性能急剧下降。为了避免这一问题,MongoDB 在后来的版本中逐步引入了更细粒度的锁机制。

2、数据库级锁(MongoDB 3.x)

从 MongoDB 3.0 开始,MongoDB 逐步放宽了全局锁,转而采用 数据库级锁。这种锁机制允许多个数据库同时进行读写操作,但在同一时间内,某个数据库只能被一个客户端写入。数据库级锁依旧是一个比较粗粒度的锁,但相比全局锁而言,性能有所提升。

  • 数据库级锁:在数据库级别进行锁定,其他操作仍然可以对其他数据库进行读写,减少了锁竞争的范围。
  • 共享锁与排他锁:数据库级锁支持共享锁(读取操作)和排他锁(写入操作),写操作需要排他锁,读操作则只需要共享锁。

尽管数据库级锁提高了并发性能,但它仍然不够细粒度,特别是在高并发应用场景下,写操作可能会阻塞多个读取操作。

3、集合级锁(MongoDB 4.x)

MongoDB 在 4.0 版本中进一步改进了锁机制,开始支持 集合级锁。这种锁机制允许数据库内部的不同集合(Collection)拥有独立的锁。也就是说,如果一个集合正在进行写操作,其他集合的读写操作仍然可以继续进行,从而显著提高了数据库的并发能力。

  • 集合级锁:每个集合在进行写操作时会独立加锁,其他集合的操作不会受到影响。这样,多个集合之间的并发性得到了进一步提升。
  • 锁竞争减少:随着锁粒度的减小,锁的竞争也得到了有效减少,尤其是在多集合同时进行读写操作的场景下,性能表现更好。

4、文档级锁(MongoDB 4.2+)

MongoDB 在 4.2 版本及以后进一步细化了锁机制,开始支持 文档级锁。文档级锁是 MongoDB 最细粒度的锁,它允许在同一个集合内,不同的文档可以并行进行读写操作,极大地提升了并发性能。

  • 文档级锁:当对某个文档进行写操作时,只有该文档会被加锁,其他文档的操作不受影响。这是 MongoDB 为了提高性能,特别是在高并发写入的场景下所采取的一项重要优化。
  • 提升并发能力:文档级锁极大地提高了并发性能,尤其在数据表中有大量独立记录(如日志、事务记录等)时,文档级锁能够最大化地减少写锁的冲突。

文档级锁让 MongoDB 在高并发写入的应用场景中表现更加优秀,但同时也引入了更多的锁管理开销。需要注意的是,虽然文档级锁可以减少锁竞争,但它也带来了更复杂的锁管理机制。

三、MongoDB 锁的工作原理

MongoDB 的锁机制并非一成不变,而是基于多个并发控制的因素和机制。下面简要介绍一下 MongoDB 锁的工作原理:

1、锁的获取和释放

MongoDB 的锁机制是通过 操作系统级的互斥锁(mutex) 来实现的。当一个操作需要访问某个数据时,MongoDB 会先尝试获取相应的锁。如果锁已经被其他操作持有,当前操作将进入等待状态,直到锁被释放。

  • 共享锁:读取操作(如 find 查询)请求共享锁,这种锁可以允许多个操作同时进行,但不能与写操作并行。
  • 排他锁:写入操作(如 insert、update、delete)请求排他锁,这种锁会阻止其他操作对该资源的访问,直到写操作完成。

2、锁的升级与降级

在某些情况下,MongoDB 会根据需要对锁进行升级或降级。举例来说:

  • 如果一个操作最初是共享锁,但随着执行,它需要更强的排他锁,那么它可能会尝试将锁升级为排他锁。
  • 反之,如果一个操作从排他锁转换为共享锁,可能会执行降级。

这些机制帮助 MongoDB 在保持较低的锁开销的同时,尽可能提高并发性。

3、锁竞争和死锁

尽管 MongoDB 提供了不同级别的锁,但在高并发写入的情况下,锁竞争仍然是一个问题。如果多个操作试图同时获取对同一资源的锁,可能会导致性能下降。更严重的情况下,错误的锁设计可能导致 死锁,即操作相互等待对方释放锁,最终导致操作无法完成。
MongoDB 在内部实现了防止死锁的机制。它通过锁的排序、时间戳等策略来避免死锁的发生。然而,在高并发的环境下,合理设计锁的粒度和锁的顺序是至关重要的。

四、如何优化 MongoDB 的锁策略

尽管 MongoDB 的锁机制已经非常高效,但开发者仍然可以通过以下几个策略来进一步优化数据库的性能:

1、优化索引设计

通过合理的索引设计,可以减少对数据库的全表扫描,减少写操作时的锁竞争。例如,创建复合索引或覆盖索引可以显著提升查询性能,减少 MongoDB 锁的占用时间。

2、控制写操作的频率

对于频繁的写操作,可以考虑使用批量插入或更新(bulkWrite),从而减少单个操作持有锁的时间。这样,其他操作就能尽快获得锁,从而提高整体的并发性能。

3、使用副本集和分片

通过设置副本集和分片,可以将负载分散到多个节点上,从而减少单个节点的锁竞争压力。副本集提供了数据的高可用性和读写分离,而分片则可以将数据按分片键分配到多个节点上,进一步提升并发能力。

4、避免长时间持有锁

在应用层面,尽量避免长时间持有锁,特别是在读写混合的场景下。长时间持有锁会造成其他操作的等待和阻塞。对于长时间执行的操作,考虑使用分页查询或分批处理。

5、使用文档级锁优化写入操作

尽可能使用 MongoDB 4.2 及以后版本的文档级锁特性,特别是在对大量独立文档进行操作时,减少锁竞争。

其实可以看出来,由于底层Btree结构的缘故,MongoDB的锁机制与MySQL有很多类似之处。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论