MongoDB 中的锁

前言

MongoDB 是一种常见的文档型数据库,因为其高性能、高可用、高扩展性等特点,被广泛应用于各种场景。

在多线程的访问下,可能会出现多线程同时操作一个集合的情况,进而出现数据冲突的情况,为了保证数据的一致性,MongoDB 采用了锁机制来保证数据的一致性。

下面来看看 MongoDB 中的锁机制。

MongoDB 中锁的类型

MongoDB 中使用多粒度锁定,它允许操作锁定在全局,数据库或集合级别,同时允许各个存储引擎在集合级别一下实现自己的并发控制(例如,WiredTiger 中的文档级别)。

MongoDB 中使用一个 readers-writer 锁,它允许并发多个读操作访问数据库,但是只提供唯一写操作访问。

当一个读锁存在时,其它的读操作可以继续,不会被阻塞,如果一个写锁占有这个资源的时候,其它所有的读操作和写操作都会被阻塞。也就是读读不阻塞,读写阻塞,写写阻塞。

MongoDB 中的锁首先提供了读写锁,即共享锁(Shared, S)(读锁)以及排他锁(Exclusive, X)(写锁),同时,为了解决多层级资源之间的互斥关系,提高多层级资源请求的效率,还在此基础上提供了意向锁(Intent Lock)。即锁可以划分为4种类型:

1、共享锁,读锁(S),允许多个线程同时读取一个集合,读读不互斥;

2、排他锁,写锁(X),允许一个线程写入数据,写写互斥,读写互斥;

3、意向共享锁,IS,表示意向读取;

4、意向排他锁,IX,表示意向写入;

什么是意向锁呢?

如果另一个任务企图在某表级别上应用共享或排他锁,则受由第一个任务控制的表级别意向锁的阻塞,第二个任务在锁定该表前不需要检查各个页或行锁,而只需检查表上的意向锁。

简单的讲就是意向锁是为了快速判断,表里面是否有记录被加锁。如果没有意向锁,大更新操作要判断是否有更小的操作在进行,

意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

例如,当以写入模式(X模式)锁定集合时,相应的数据库锁和全局锁都必须以意向独占(IX)模式锁定。一个数据库可以同时以IS和IX模式进行锁定,但独占(X)锁无法与其他模式并存,共享(S)锁只能与意向共享(IS)锁并存。

MongoDB 中的锁是公平的,所有的请求都会排队获取相应的锁。但是 MongoDB 为了优化吞吐量,在执行某个请求的时候,会同时执行和它兼容的其它请求。比如一个队列一个请求队列需要的锁如下,执行IS请求的同时,会同时执行和它相容的其他S和IS请求。等这一批请求的S锁释放后,再执行X锁的请求。

IS → IS → X → X → S → IS

这种处理机制保证了在相对公平的前提下,提高了吞吐量,不会让某一类的请求长时间的等待。

对于长时间的读或者写操作,某些条件下,mongodb 会临时的 让渡 锁,以防止长时间的阻塞。

锁的让渡释放

对于大多数读取和写入操作,WiredTiger 使用乐观并发控制。WiredTiger 仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间的冲突时,其中一个将导致写冲突,从而使 MongoDB 可以在不可见的情况下重新尝试该操作。

在某些情况下,读写操作可以释放它们持有的锁。以防止长时间的阻塞。

长时间运行的读取和写入操作,比如查询、更新和删除,在许多情况下都会释放锁。MongoDB操作也可以在影响多个文档的写入操作中,在单个文档修改之间释放锁。

对于支持文档级并发控制的存储引擎,比如 WiredTiger,在访问存储时通常不需要释放锁,因为在全局、数据库和集合级别保持的意向锁不会阻塞其他读取者和写入者。然而,操作会定期释放锁,以便:

1、避免长时间存储事务,因为这些事务可能需要在内存中保存大量数据;

2、充当中断点,以便可以终止长时间运行的操作;

3、允许需要对集合进行独占访问的操作,比如索引/集合的删除和创建。

常见操作使用的锁类型

下面列列举一下 MongoDB 中的常见操作对应的锁类型

select:库级别的意向读锁(r),表级别的意向读锁(r),文档级别的读锁(R);

update/insert:库级别的意向写锁(w),表级别的意向写锁(w),文档级别的写锁(W);

foreground 方式创建索引:库级别的写锁(W);当为一个集合创建索引时,因为是库级别的写锁,这个操作将阻塞其他的所有操作,任意基于所有数据库申请读或写锁都将等待直到前台完成索引创建操作;

background 方式创建索引:库级别的意向写锁(w),表级别的意向写锁(w);从 MongoDB 4.2 开始,在构建过程的开始和结束时,索引构建仅获取正在进行索引的集合的独占锁,以保护元数据更改。在创建索引的其它部分,使用锁的让渡行为,最大限度保证集合的读写访问。

如果定位 MongoDB 中锁操作

当查询有慢查询出现的时候,有时候会出现锁的阻塞等待,紧急情况,需要我们快速定位并且结束当前操作。

使用 db.currentOp() 就能查看当前数据库正在执行的操作。

db.currentOp()

{
"inprog" : [
{
"opid" : 6222, #进程号
"active" : true, #是否活动状态
"secs_running" : 3,#操作运行了多少秒
"microsecs_running" : NumberLong(3662328),#操作持续时间(以微秒为单位)。MongoDB通过从操作开始时减去当前时间来计算这个值。
"op" : "getmore",#操作类型,包括(insert/query/update/remove/getmore/command)
"ns" : "local.oplog.rs",#命名空间
"query" : {#如果op是查询操作,这里将显示查询内容;也有说这里显示具体的操作语句的 },
"client" : "192.168.91.132:45745",#连接的客户端信息
"desc" : "conn5",#数据库的连接信息
"threadId" : "0x7f1370cb4700",#线程ID
"connectionId" : 5,#数据库的连接ID
"waitingForLock" : false,#是否等待获取锁
"numYields" : 0,
"lockStats" : {
"timeLockedMicros" : {#持有的锁时间微秒
"r" : NumberLong(141),#整个MongoDB实例的全局读锁
"w" : NumberLong(0)#整个MongoDB实例的全局写锁
},
"timeAcquiringMicros" : {#为了获得锁,等待的微秒时间
"r" : NumberLong(16),#整个MongoDB实例的全局读锁
"w" : NumberLong(0)#整个MongoDB实例的全局写锁
}
}
}
]
}

来看下几个主要的字段含义

  • client:发起请求的客户端;

  • opid: 操作的唯一标识;

  • secs_running:该操作已经执行的时间,单位:微妙。如果该字段的返回值很大,就需要查询请求是否合理;

  • op:操作类型。通常是query、insert、update、delete、command中的一种;

  • query/ns:这个字段能看出是对哪个集合正在执行什么操作。

当发现一个语句执行时间很久,影响到了整个数据库的运行,这时候我们 可以考虑中断这条语句的执行。

使用 db.killOp(opid) 命令终止该请求。

来个试验的栗子

对表里面一个大表创建索引,不添加 backend。

db.notifications.createIndex({userId: -1});

1、查询运行超过20S 的请求

$ db.currentOp({"active" : true, "secs_running":{ "$gt" : 20 }})

{
"inprog" : [
{
"host" : "host-192-168-61-214:27017",
"desc" : "conn50774156",
"connectionId" : 50774156,
"client" : "172.18.91.66:52088",
"appName" : "Navicat",
"clientMetadata" : {
"application" : {
"name" : "Navicat"
},
"driver" : {
"name" : "mongoc",
"version" : "1.16.2"
},
"os" : {
"type" : "Darwin",
"name" : "macOS",
"version" : "20.6.0",
"architecture" : "x86_64"
},
"platform" : "cfg=0x0000d6a0e9 posix=200112 stdc=201112 CC=clang 8.0.0 (clang-800.0.42.1) CFLAGS=\"\" LDFLAGS=\"\""
},
"active" : true,
"currentOpTime" : "2023-10-24T01:32:00.615+0000",
"opid" : -1782291565,
"lsid" : {
"id" : UUID("fff3c45d-b6ac-4a30-b83f-5a565ba166ef"),
"uid" : BinData(0,"EJF4gS8MLpU7cuurTHswrdjF5hInXITH3796necT7PU=")
},
"secs_running" : NumberLong(103),
"microsecs_running" : NumberLong(103025729),
"op" : "command",
"ns" : "gleeman.$cmd",
"command" : {
"createIndexes" : "notifications",
"indexes" : [
{
"key" : {
"userId" : -1
},
"name" : "userId_-1"
}
],
"$db" : "gleeman",
"lsid" : {
"id" : UUID("fff3c45d-b6ac-4a30-b83f-5a565ba166ef")
},
"$clusterTime" : {
"clusterTime" : Timestamp(1698111011, 1),
"signature" : {
"hash" : BinData(0,"iKilM1hvvIJC4hrTgu3FebYNhEw="),
"keyId" : NumberLong("7233287468395528194")
}
}
},
"msg" : "Index Build (background) Index Build (background): 27288147/34043394 80%",
"progress" : {
"done" : 27288148,
"total" : 34043394
},
"numYields" : 213205,
"locks" : {
"Global" : "w",
"Database" : "w",
"Collection" : "w"
},
"waitingForLock" : false,
"lockStats" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(213208),
"w" : NumberLong(213208)
}
},
"Database" : {
"acquireCount" : {
"w" : NumberLong(213209),
"W" : NumberLong(1)
}
},
"Collection" : {
"acquireCount" : {
"w" : NumberLong(213207)
}
},
"oplog" : {
"acquireCount" : {
"w" : NumberLong(1)
}
}
}
}
],
"ok" : 1,
"operationTime" : Timestamp(1698111118, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1698111118, 1),
"signature" : {
"hash" : BinData(0,"oLzIpVSGpZ213BW4x/jY6ESKvdA="),
"keyId" : NumberLong("7233287468395528194")
}
}
}

2、批量删除请求大于 20s 的请求

var ops = db.currentOp(
{
"active": true,
"secs_running": {
"$gt": 20
}
}
).inprog for (i = 0; i < ops.length; i++) {
ns = ops[i].ns;
op = ops[i].op;
if (ns.startsWith("system.") || ns.startsWith("local.oplog.") || ns.length === 0 || op == "none" || ns.command == "" || ns in["admin", "local", "config"]) {
continue;
}
var opid = ops[i].opid;
db.killOp(opid);
print("Stopping op #" + opid)
}

3、kill 掉特定 client 端 ip 的请求

var clientIp="172.18.91.66";
var currOp = db.currentOp(); for (op in currOp.inprog) {
if (clientIp == currOp.inprog[op].client.split(":")[0]) {
db.killOp(currOp.inprog[op].opid)
}
}

4、查询所有 wait 锁定的写操作

db.currentOp(
{
"waitingForLock" : true,
$or: [
{ "op" : { "$in" : [ "insert", "update", "remove" ] } },
{ "command.findandmodify": { $exists: true } }
]
}
)

5.返回索引的创建信息

db.adminCommand(
{
currentOp: true,
$or: [
{ op: "command", "command.createIndexes": { $exists: true } },
{ op: "none", "msg" : /^Index Build/ }
]
}
)

总结

1、MongoDB 中使用一个 readers-writer 锁,它允许并发多个读操作访问数据库,但是只提供唯一写操作访问;

2、MongoDB 中的锁首先提供了读写锁,即共享锁(Shared, S)(读锁)以及排他锁(Exclusive, X)(写锁),同时,为了解决多层级资源之间的互斥关系,提高多层级资源请求的效率,还在此基础上提供了意向锁(Intent Lock)。即锁可以划分为4中类型:

  • 1、共享锁,读锁(S),允许多个线程同时读取一个集合,读读不互斥;

  • 2、排他锁,写锁(X),允许一个线程写入数据,写写互斥,读写互斥;

  • 3、意向共享锁,IS,表示意向读取;

  • 4、意向排他锁,IX,表示意向写入;

3、MongoDB 中支持高并发的一个重要的点就是 MongoDB 支持锁的让渡,在某些情况下,读写操作可以让渡它们持有的锁。以防止长时间的阻塞后面的操作;

参考

【mongodb锁表命令-相关文档】https://www.volcengine.com/theme/900385-M-7-1

【mongo 中的锁】https://www.jinmuinfo.com/community/MongoDB/docs/15-faq/03-concurrency.html

【FAQ: Concurrency】https://www.mongodb.com/docs/manual/faq/concurrency/

【mongodb中的锁】https://boilingfrog.github.io/2023/10/27/mongo中的锁/

MongoDB 中的锁分析的更多相关文章

  1. MongoDB实战指南(五):MongoDB中的聚集分析

    聚集操作是对数据进行分析的有效手段.MongoDB主要提供了三种对数据进行分析计算的方式:管道模式聚集分析,MapReduce聚集分析,简单函数和命令的聚集分析. 1. 管道模式进行聚集 这里所说的管 ...

  2. 聊聊MongoDB中连接池、索引、事务

    大家好,我是哪吒. 三分钟你将学会: MongoDB连接池的使用方式与常用参数 查询五步走,能活九十九? MongoDB索引与MySQL索引有何异同? MongoDB事务与ACID 什么是聚合框架? ...

  3. 分析AJAX抓取今日头条的街拍美图并把信息存入mongodb中

    今天学习分析ajax 请求,现把学得记录, 把我们在今日头条搜索街拍美图的时候,今日头条会发起ajax请求去请求图片,所以我们在网页源码中不能找到图片的url,但是今日头条网页中有一个json 文件, ...

  4. MongoDB中的一些坑( 2.4.10 版本)

    http://www.jb51.net/article/62654.htm 1.MongoDB 数据库级锁 MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大 ...

  5. MongoDB中的一些坑(最好不要用)

    MongoDB 是目前炙手可热的 NoSQL 文档型数据库,它提供的一些特性很棒:如自动 failover 机制,自动 sharding,无模式 schemaless,大部分情况下性能也很棒.但是薄荷 ...

  6. MongoDB中如何优雅地删除大量数据

    删除大量数据,无论是在哪种数据库中,都是一个普遍性的需求.除了正常的业务需求,我们需要通过这种方式来为数据库"瘦身". 为什么要"瘦身"呢? 表的数据量到达一定 ...

  7. 深入理解 iOS 开发中的锁

    来源:伯乐在线 - 夏天然后 链接:http://ios.jobbole.com/89474/ 点击 → 申请加入伯乐在线专栏作者 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大 ...

  8. MySQL系列(五)---总结MySQL中的锁

    MySQL中的锁 目录 MySQL系列(一):基础知识大总结 MySQL系列(二):MySQL事务 MySQL系列(三):索引 MySQL系列(四):引擎 概述 MyISAM支持表锁,InnoDB支持 ...

  9. MongoDB中的MapReduce介绍与使用

    一.简介 在用MongoDB查询返回的数据量很大的情况下,做一些比较复杂的统计和聚合操作做花费的时间很长的时候,可以用MongoDB中的MapReduce进行实现 MapReduce是个非常灵活和强大 ...

  10. Java中的锁——Lock和synchronized

    上一篇Java中的队列同步器AQS 一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同 ...

随机推荐

  1. 【ssh】SSH连接远程主机的两种方式

    一.基于用户名与密码连接 指令 ssh username@server_ip 随后要求输入密码 加密流程 1️⃣ 在SSH连接建立过程中,客户端和服务器使用Diffie-Hellman密钥交换协议协商 ...

  2. salesforce零基础学习(一百二十九)Lead Convertion 有趣的经历

    本篇参考:https://help.salesforce.com/s/articleView?id=000382564&type=1 Lead Convertion 是salesforce中s ...

  3. Linux 设置 VI 快捷键 -- 在多个打开的文件中切换

    场景 部署完一系列服务后,想要查看所有服务的 catelina.out 日志: vi $(find /data/http | grep catalina.out | grep -v bak) 这个命令 ...

  4. ansible 的特点

    ansible的特点 基于Python语言实现 模块化,调用特定的模块,完成特定任务 部署简单,基于python和SSH(默认已安装),yum install 即可,不需要客户端 安全,基于OpenS ...

  5. PlayWright(二十二)- allure插件(一)

    在上文中,我们介绍并使用了pytest-html插件,总之并不复杂,但是今天我们要讲一个比pytest-html插件强很多的插件allure报告,我们要掌握他并且灵活使用,之后的框架就不需要考虑其他的 ...

  6. [golang]使用mTLS双向加密认证http通信

    前言 假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信.为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议 ...

  7. 自治系统/自治域和自治系统编号(ASN)

    定义: 自治系统或自治域(英文:Autonomous system, AS)是指在互联网中,一个或多个实体管辖下的所有IP网络和路由器的组合,它们对互联网执行共同的路由策略.参看RFC 1930中更新 ...

  8. JNDI注入的本地搭建和分析

    JNDI注入的本地搭建和分析   JNDI概述 JNDI(The java Naming and Directory Interface,java命名和目录接口)是一组在Java应用中访问命名和目录服 ...

  9. 文心一言(ERNIE Bot)初体验

    引言 几个月前向百度提交了文心一言的体验申请,这两天收到了可以体验的通知,立马体验了一把.总体来说,文心一言基本上能做到有问必答,但是一些奇葩的问题还是会难住这位初出茅庐的 AI. 分享体验 我先后问 ...

  10. Java读取某个文件夹下的所有文件(支持多级文件夹)

    源码如下: package com.vocy.water.batch; import java.io.FileNotFoundException; import java.io.IOException ...