mongo 连接二三事

如果你也被慢查询、CPU 打满、雪崩等问题困扰,盘它!

查看实例上的慢查询

查询语句说明

假设我们想查询实例上所有执行时间超过 1s 的慢语句。

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

如果想要查看指定 db 上的慢查询:

db.currentOp(
  {
    "active": true,
    "secs_running": {
        "$gt": 1
    },
    "ns": /^siber\./
  }
)

查询结果分析

截取其中一条语句进行分析:

{
			"host" : "a**2.cloud.nu29:3**9",
			"desc" : "conn6603723",
			"connectionId" : 6603723,
			"client" : "172.16.10.108:58243",
			"appName" : "MongoDB Shell",
			"clientMetadata" : {
				"application" : {
					"name" : "MongoDB Shell"
				},
				"driver" : {
					"name" : "MongoDB Internal Client",
					"version" : "4.2.0"
				},
				"os" : {
					"type" : "Darwin",
					"name" : "Mac OS X",
					"architecture" : "x86_64",
					"version" : "20.3.0"
				}
			},
			"active" : true,
			"currentOpTime" : "2021-04-12T14:36:51.243+0800",
			"opid" : 1210807065,
			"lsid" : {
				"id" : UUID("4494c815-cc94-42bf-88cf-45924faef935"),
				"uid" : BinData(0,"3t2w+sv/3h3pGF2C4Y8XgsDXkmAm1lgwz8idwd27XZ0=")
			},
			"secs_running" : NumberLong(2),
			"microsecs_running" : NumberLong(2946118),
			"op" : "command",
			"ns" : "siber.collection_log_case",
			"command" : {
				"count" : "collection_log_case",
				"query" : {
					"methodname" : "manage_user.ManageUserService.CreateUser"
				},
				"lsid" : {
					"id" : UUID("4494c815-cc94-42bf-88cf-45924faef935")
				},
				"$clusterTime" : {
					"clusterTime" : Timestamp(1618209403, 2),
					"signature" : {
						"hash" : BinData(0,"4LE8FiDFhAU84meQOSn/vVRaoTg="),
						"keyId" : NumberLong("6909073628604661763")
					}
				},
				"$db" : "siber"
			},
			"planSummary" : "COLLSCAN",
			"numYields" : 3449,
			"locks" : {
				"Global" : "r",
				"Database" : "r",
				"Collection" : "r"
			},
			"waitingForLock" : false,
			"lockStats" : {
				"Global" : {
					"acquireCount" : {
						"r" : NumberLong(3450)
					}
				},
				"Database" : {
					"acquireCount" : {
						"r" : NumberLong(3450)
					}
				},
				"Collection" : {
					"acquireCount" : {
						"r" : NumberLong(3450)
					}
				}
			}
		},

所有输出的这些字段,都可以用于查询条件,常用的有:

字段名

说明

client

该语句来自于哪个客户端

可用于查询指定客户端的请求

opid

该操作的唯一标识

db.killOp()时使用此字段的值

active

操作是否已启动

空闲连接或者内部线程该字段为false

某些已经让步的操作,状态仍可能为 true

secs_running

操作已开始多长时间(秒)

当 active 为 true 时,该字段有值

microsecs_running

操作已开始多长时间(微秒)

当 active 为 true 时,该字段有值

op

操作类型,query、insert、command……

ns

namespace, db.collection 的形式

command

语句具体的执行信息

planSummary

执行计划,可用于慢语句分析

locks

操作对哪些模块加什么类型的锁

模块:

Global

MMAPV1Journal

Database

Collection

Metadata

oplog

锁类型:

R-读锁 W-写锁

r-读意向锁 w-写意向锁

waitingForLock

true:正在等锁

false:已经获得锁

连接处理

kill 指定操作

当慢查询严重,异常连接数过多(或者正常连接数过多),为了保证大局稳定,我们需要“牺牲”部分连接。

MongoDB​ 提供如下方法,可以杀掉任意执行中的操作:

db.killOp(opid)

其中 opid 为上文所述 db.currentOp() 中展示的 opid

killOp 原理

每个连接对应的服务线程存储了一个killPending的字段,当发送killOp时,会将该字段置1;请求在执行过程中,可以通过不断的调用OperationContext::checkForInterrupt()来检查killPending是否被设置,如果被设置,则线程退出。

一个请求要支持 killOp,必须在请求的处理逻辑里加上 checkForInterrupt() 检查点才行,否则即使发送了 killOp,也只能等待请求完全处理完毕线程才会退出。

比如 createIndex 的处理逻辑里包含了类似如下的代码,在 createIndex 的循环过程中,一旦 killPending 被置1了,createIndex 的执行可以在当前循环结束时退出。

while (!createIndexFinished) {
    createIndexForOneElement();
    checkForInterupt();
}

所以发送 killOp 后,请求要执行到下一个『检查点』线程才会退出,MongoDB 在很多可能耗时长的请求中,都加入了checkForInterrupt()检查点,如创建索引,repair database,mapreduce、aggregation等。

ctrl + c 是否可立即终止操作?

Ctrl + C 会关闭 clientserver 的连接,但后端的线程不会立即结束(MongoDB每个连接的请求由一个对应的线程来处理),而是会一直执行下去。

直到执行结束后,给 client 端发送通知,会发现连接已经断开,这时线程才会退出。

这个时候就需要使用 killOp 去实现真正的“终止”,操作点会在下个检查点结束执行,整个线程退出。

最后更新于

这有帮助吗?