MongoDB 中的锁分析
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 中的锁分析的更多相关文章
- MongoDB实战指南(五):MongoDB中的聚集分析
聚集操作是对数据进行分析的有效手段.MongoDB主要提供了三种对数据进行分析计算的方式:管道模式聚集分析,MapReduce聚集分析,简单函数和命令的聚集分析. 1. 管道模式进行聚集 这里所说的管 ...
- 聊聊MongoDB中连接池、索引、事务
大家好,我是哪吒. 三分钟你将学会: MongoDB连接池的使用方式与常用参数 查询五步走,能活九十九? MongoDB索引与MySQL索引有何异同? MongoDB事务与ACID 什么是聚合框架? ...
- 分析AJAX抓取今日头条的街拍美图并把信息存入mongodb中
今天学习分析ajax 请求,现把学得记录, 把我们在今日头条搜索街拍美图的时候,今日头条会发起ajax请求去请求图片,所以我们在网页源码中不能找到图片的url,但是今日头条网页中有一个json 文件, ...
- MongoDB中的一些坑( 2.4.10 版本)
http://www.jb51.net/article/62654.htm 1.MongoDB 数据库级锁 MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大 ...
- MongoDB中的一些坑(最好不要用)
MongoDB 是目前炙手可热的 NoSQL 文档型数据库,它提供的一些特性很棒:如自动 failover 机制,自动 sharding,无模式 schemaless,大部分情况下性能也很棒.但是薄荷 ...
- MongoDB中如何优雅地删除大量数据
删除大量数据,无论是在哪种数据库中,都是一个普遍性的需求.除了正常的业务需求,我们需要通过这种方式来为数据库"瘦身". 为什么要"瘦身"呢? 表的数据量到达一定 ...
- 深入理解 iOS 开发中的锁
来源:伯乐在线 - 夏天然后 链接:http://ios.jobbole.com/89474/ 点击 → 申请加入伯乐在线专栏作者 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大 ...
- MySQL系列(五)---总结MySQL中的锁
MySQL中的锁 目录 MySQL系列(一):基础知识大总结 MySQL系列(二):MySQL事务 MySQL系列(三):索引 MySQL系列(四):引擎 概述 MyISAM支持表锁,InnoDB支持 ...
- MongoDB中的MapReduce介绍与使用
一.简介 在用MongoDB查询返回的数据量很大的情况下,做一些比较复杂的统计和聚合操作做花费的时间很长的时候,可以用MongoDB中的MapReduce进行实现 MapReduce是个非常灵活和强大 ...
- Java中的锁——Lock和synchronized
上一篇Java中的队列同步器AQS 一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同 ...
随机推荐
- CF371D Vessels题解
思路: 定义一个权值并查集,权值保存这个集合还可以存下多少水. 如果这个集合可以存放的水已经小于要装入的水,就将这个集合与下一个集合合并. 否则,直接把这个集合可以存放的水减去要装入的水的体积. 代码 ...
- 树莓派命令——linux命令tips
sudo python3 test.py 和 python3 test.py 完全不是一个东西,有时候是链接的编译器不同,环境是完全不同,sudo会调用一些无关资源,反而容易造成程序运行失败或浪费cp ...
- 一分钟学一个 Linux 命令 - rm
前言 大家好,我是 god23bin,欢迎回到咱们的<一分钟学一个 Linux 命令>系列,今天我要讲的是一个比较危险的命令,rm 命令,没错,你可以没听过 rm 命令,但是删库跑路你不可 ...
- Power AutoMate: 运行脚本程序
运行脚本文件 操作步骤 配置脚本 点击脚本文件菜单,选中运行python脚本.在其中输入需要徐行的脚本点击保存 之后界面会如下所示: 运行程式 可以看到程式正常运行
- SwiftUI的认识与使用
SwiftUI简介 SwiftUI是苹果推出的一个新的UI框架,它使用了声明的方式,通过视图,基础控件和布局控件来进行页面的开发. SwiftUI具有跨平台性,一份SwiftUI代码可以同时跑在i ...
- 使用 python 快速搭建http服务
python -m SimpleHTTPServer 8888 使用上面的命令可以把当前目录发布到8888端口. 直接浏览器访问 但是这条命令是当前运行的,不是后台运行的,也就是说如果Ctrl + C ...
- Maven资源导出问题所需配置
<!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> < ...
- 基于redis6搭建集群
前言 系统版本:CentOS 7 redis版本:redis6.2.4,官方tar.gz包 两台服务器: 172.50.11.11 端口7002.7004.7006 172.50.12.11 端口70 ...
- [信息安全] 加密算法:md5摘要算法 / sha256算法
1 MD5 1.1 算法定义 MD5的全称为 Message-Digest Algorithm,是一种被广泛使用的单向散列函数.属于Hash算法中一种比较重要算法--具有单项加密.加密结果唯一.安全性 ...
- 【NestJS系列】连接数据库及优雅地处理响应
前言 Node作为一门后端语言,当然也可以连接数据库,为前端提供CURD接口 我们以mysql为例,自行安装mysql TypeORM TypeORM 是一个ORM框架,它可以运行在 NodeJS.B ...