如何防止 MongoDB 锁定

Sea*_*ark 0 javascript mongodb node.js

我有一个写入查询,它会在一分钟左右的时间内从源读取数据。但我还有一个需要从该数据库读取的应用程序。我那一刻不需要新数据。

写入查询锁定数据库,防止(通常很快)读取查询运行。

这是 currentOP() 的结果;

{
    "inprog" : [
        {
            "opid" : 547909,
            "active" : true,
            "secs_running" : 0,
            "microsecs_running" : NumberLong(104962),
            "op" : "insert",
            "ns" : "nb.article_raw",
            "insert" : {
                "$msg" : "query not recording (too large)"
            },
            "client" : "<<IP>>:52548",
            "desc" : "conn10993",
            "threadId" : "0x7ef7ebfb2700",
            "connectionId" : 10993,
            "locks" : {
                "^" : "w",
                "^nb" : "W"
            },
            "waitingForLock" : false,
            "numYields" : 0,
            "lockStats" : {
                "timeLockedMicros" : {
                    "r" : NumberLong(0),
                    "w" : NumberLong(581)
                },
                "timeAcquiringMicros" : {
                    "r" : NumberLong(0),
                    "w" : NumberLong(15467)
                }
            }
        },
        {
            "opid" : 546673,
            "active" : true,
            "secs_running" : 48,
            "microsecs_running" : NumberLong(48720118),
            "op" : "query",
            "ns" : "nb.article_raw",
            "query" : {
                "id" : {
                    "$nin" : [ ]
                },
                "hasCompleteImage" : {
                    "$exists" : true
                },
                "date" : {
                    "$gt" : ISODate("2015-07-10T00:54:35Z")
                }
            },
            "client" : "100.36.81.202:60791",
            "desc" : "conn10979",
            "threadId" : "0x7f55de5ae700",
            "connectionId" : 10979,
            "locks" : {
                "^nb" : "R"
            },
            "waitingForLock" : true,
            "numYields" : 6611,
            "lockStats" : {
                "timeLockedMicros" : {
                    "r" : NumberLong(19244958),
                    "w" : NumberLong(0)
                },
                "timeAcquiringMicros" : {
                    "r" : NumberLong(18701993),
                    "w" : NumberLong(0)
                }
            }
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

有什么想法可以编写第一个查询以使其不锁定吗?写入代码通过 NodeJS Mongo 驱动一次写入一个循环的迭代collection.insertOne

插入代码循环

promises.push(new Promise(function(resolve) {
        collection.insertOne(obj, {}, function(err) {
            if(err) {

            }
            resolve();
        });
        z++;
}));
Run Code Online (Sandbox Code Playgroud)

它位于对象的 foreach 内部。

Bla*_*ven 5

因此,您可以在这里做一些事情,但通常您会在应用程序和服务器发行版中遇到“体系结构”。这意味着有一些事情(或者也许是全部)需要考虑。

我将在这里介绍基础知识,而不涉及“购买更多 RAM”或“获取更大的实例”游戏,并在“扩展”之前真正专注于“扩展”方面。

更智能的刀片


一遍又一遍地调用.insertOne()项目列表并不是真正有效,并且根据列表的大小可能真的会“锁定”您的数据库。

无论您使用什么来实现实际分辨率,我认为您确实应该查看批量操作 APi。写入不是单独发送,而是“批量”发送。所以以最简单的形式:

var bulk = collection.inititalizeUnorderedBulkOp();

listOfThings.forEach(function(obj) {
    bulk.insert(obj);
});

// But actually sends to server here
bulk.execute(function(err,response) {

});
Run Code Online (Sandbox Code Playgroud)

对于非常大的列表,您可能不希望所有这些都在内存中进行列表处理或“批量”处理,因此应该进行一些限制。

关键是,一次向服务器发送 1000 个插入优于 1000 个请求和 1000 个响应,后者的流量很大。此外,这还允许在“批量”操作仅获取锁的情况下进行更多控制。与您在服务器上反复“敲击”一个又一个请求相比,这里有更多的“收益”空间用于其他操作。

可能使用二次读取


既然您说您可以接受不在“绝对最新”数据上的查询,那么拥有 ReplicaSet 架构(无论如何您“应该”在生产中拥有它)就可以让您将“读取”推迟到辅助数据库。

虽然辅助节点“遵循更新”,但它们并没有像接受所有初始写入的主节点那样“努力”。所以这允许有更多的空间。

分片分发


这里有“热点”,基本上同时读取和写入操作超载。分片允许您考虑通过跨分片分配写入来分散“写入负载”,或者通过利用可能不会发生在与“读取”来源相同的“分片”上的“写入”来分散“读取负载”从那个时间点开始,或者通常在搜索中进行“分散收集”并均匀地命中多个碎片。

更新存储引擎,或者至少考虑您的数据在哪里


因此,最终您会遇到“集合级锁定”或“数据库级锁定”,具体取决于可用版本。一个明显的变化是升级到 MongoDB 3.x 并使用Wired Tiger具有“文档级锁定”的

如果无法做到这一点,那么至少要考虑锁定级别以及:

  • 尝试写入与您正在阅读的集合不同的另一个集合,并且仅“定期”更新“阅读集合”

  • 或者,在数据库容器级别执行完全相同的操作。


这里的所有内容都是同一主题的变体,即“减少开销”和“尽可能分散负载”。因此,我至少会首先实现批量插入,因为总能从中获得一些好处。

然后查看此处的其他选项(不一定按提供的顺序,而是分片之前的副本集)并尝试获取写入之间的“冲突”级别。

还要看看“索引”,如果你有很多索引,它会减慢写入速度。人们索引超出实际需要的内容是很常见的,所以也要注意这一点。