首页 > 数据库 >MongoDB 中的锁分析

MongoDB 中的锁分析

时间:2023-10-27 09:58:40浏览次数:35  
标签:分析 请求 写锁 MongoDB NumberLong 操作 op

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,NumberLong,操作,op
From: https://www.cnblogs.com/ricklz/p/17791076.html

相关文章

  • 常见编译优化及LLVM Pass源码分析(填坑ing)
    一、常量传播二、常量折叠三、复写传播四、公共子表达式消除五、死代码消除(ADCE,Aggressivedeadcodeelimination)llvm/lib/Transforms/Scalar/ADCE.cpp六、函数内联......
  • 软考系列(系统架构师)- 2014年系统架构师软考案例分析考点
    试题一软件架构(MYC架构、扩展接口模式)MVC架构风格最初是Smalltalk-80中用来构建用户界面时采用的架构设计风格。其中M代表模型(Model),V代表视图(View),C代表控制器(Controller)。在该风格中,模型表示待展示的对象,视图表示模型的展示,控制器负责把用户的动作转成针对模型的操作。模......
  • PSV-2020-0211-Netgear-R8300-UPnP栈溢出漏洞分析
    PSV-2020-0211-Netgear-R8300-UPnP栈溢出漏洞分析系统级模拟启动这个路由器的使用的upnpd来开放服务,执行/usr/sbin/upnpd后没有反应,使用ida看一下发现没有/var/run这个目录,那就创建一个mkdir-p/tmp/var/run(为什么是tmp下的是因为/var/run是软连接到/tmp/var/run)在......
  • 基于Scrapy的考研院校报名数据分析系统-计算机毕业设计源码+LW文档
    一、选题意义随着我国高等教育不断发展,现在我国学生本科就读率不断增高,社会上本科生人才数量也在不断增多。由于我国人口众多,从恢复高考到现在,全国本科学历占比不超过5%,但总数也达到了六千万人左右。本科生不断增多,也造成了就业竞争加大,于是选择继续深造考研的大学生不断增多,2022......
  • 海尔集团营运能力分析及建议-LW
    AbstractThispaperfirstintroducesthebackgroundandsignificanceofenterpriseoperationanalysis,andsummarizestheresearchstatusathomeandabroad.Secondly,itbrieflyintroducesthekeyindicatorsandtheircharacteristicsintheanalysisofe......
  • Android使用Profiler查看应用内存分析
    内存分析是Profiler中的一个组件,可以帮助我们识别可能会导致应用卡顿、冻结甚至崩溃的内存泄露和内存抖动。可以显示应用内存使用情况实时图表,帮助我们捕获堆转储、强制执行垃圾回收以及跟踪内存的分配情况。打开内存分析步骤:1、依次点击View→ToolWindow→Profiler2、从Profile......
  • 顺丰控股的盈利能力分析及提升路径研究—论文文档
    目录摘要 IAbstract II一、绪论 1(一)研究背景与研究意义 11.研究背景 12.研究意义 1(二)文献综述 21.国外文献研究现状 22.国内文献研究现状 33.文献述评 4(三)研究内容和方法 41.研究内容 42.研究方法 4二、相关理论概述 5(一)企业盈利能力相关理论概述 51.盈利能力的含......
  • 数据分析报告的阅读
    数据分析报告的阅读如何生成数据分析报告通常我们拿到一份数据时,我们希望尽快了解她的全貌,那就不可避免的要做探索性数据分析的工作(EDA)。而这项工作对于我而言一次两次到还好,有些数据集的特征值极多,且分布规律极其抽象,所以我还是希望有一种自动生成报告的方法。为此,特学习了利用......
  • 学生成绩数据分析软件,提升数据分析效率?
     学生成绩数据分析软件在教育领域中起着重要的作用,可以帮助教育机构和教师更好地理解学生的学习情况、评估教学效果,并提供决策支持。这些软件利用统计分析、数据挖掘和机器学习等技术,可以处理大量的学生成绩数据,并从中提取有价值的信息。下面将详细介绍一些常见的学生成绩数据......
  • 高校教研大数据分析系统
    学校常用的成绩分析软件在教育领域扮演着至关重要的角色,它们帮助学校管理者和教师对学生的学习进展进行跟踪和分析。以下是一些常用的成绩分析软件及其功能说明。学生成绩管理系统:学生成绩管理系统是一种全面的学生学习数据管理和分析解决方案。它可以帮助学校管理者和教师快速......