我不是数据库专家,也没有正式的计算机科学背景,所以请多多包涵。我想知道如果您使用不符合 ACID 的旧 MongoDB version prior to v4,可能会发生哪些现实世界负面事情。这适用于任何不符合 ACID 的数据库。
我了解 MongoDB 可以执行 Atomic Operations,但它们不“支持传统锁定和复杂事务”,主要是出于性能原因。我也了解数据库事务的重要性,以及当您的数据库用于银行时,并且您正在更新几条都需要同步的记录时,您希望事务恢复到初始状态(如果有)停电所以信用等于购买等。
但是当我开始谈论 MongoDB 时,我们这些不了解数据库实际实现方式的技术细节的人开始抛出如下语句:
MongoDB 比 MySQL 和 Postgres 快得多,但它“无法正确保存”的可能性很小,比如百万分之一。
“不会正确保存”部分指的是这种理解:如果在您向 MongoDB 写入的那一刻发生停电,则有可能获得特定记录(例如,您正在跟踪具有 10 个属性的文档中的页面浏览量每个),其中一个文档只保存了 5 个属性……这意味着随着时间的推移,您的浏览量计数器将“略微”关闭。你永远不会知道多少,你知道他们会 99.999% 正确,但不是 100%。这是因为,除非您专门将其设为 mongodb atomic operation,否则不能保证该操作是原子的。
所以我的问题是,什么时候以及为什么 MongoDB 不能“正确保存”的正确解释是什么?它不满足 ACID 的哪些部分,在什么情况下,以及您如何知道 0.001% 的数据何时关闭?这不能以某种方式解决吗?如果不是,这似乎意味着您不应该将诸如 users
表之类的内容存储在 MongoDB 中,因为可能无法保存记录。但是话又说回来,那 1/1,000,000 的用户可能只需要“尝试再次注册”,不是吗?
我只是在寻找一个关于何时/为什么会在像 MongoDB 这样的不符合 ACID 的数据库上发生负面事情的列表,理想情况下,如果有一个标准的解决方法(比如运行后台作业来清理数据,或者只使用 SQL 等) .
MongoDB 不符合 ACID 实际上是不正确的。相反,MongoDB 在文档级别是 ACID 兼容的。
对单个文档的任何更新都是
原子的:它要么完全完成,要么没有
一致:没有读者会看到“部分应用”的更新
孤立:再一次,没有读者会看到“脏”读
耐用:(带有适当的写入问题)
MongoDB 没有事务——即可以回滚且符合 ACID 的多文档更新。
请注意,您可以通过 using two-phase commit 在单个文档的符合 ACID 的更新之上构建事务。
使用 MongoDB 会丢失一件事是多集合(表)事务。 MongoDB 中的原子修饰符只能对单个文档起作用。
如果您需要从库存中删除一个项目并同时将其添加到某人的订单中 - 您不能。除非这两个东西 - 库存和订单 - 存在于同一个文档中(它们可能不存在)。
我在正在处理的应用程序中遇到了同样的问题,并且有两种可能的解决方案可供选择:
1) 尽可能构建文档并尽可能使用原子修饰符,对于剩余部分,使用后台进程清理可能不同步的记录。例如,我从库存中删除项目并使用原子修饰符将它们添加到同一文档的 reservedInventory 数组中。
这让我始终知道库存中没有物品(因为它们是由客户保留的)。当客户结账时,我会从 reservedInventory 中删除这些项目。这不是标准交易,由于客户可以放弃购物车,我需要一些后台进程来查找废弃的购物车并将保留的库存移回可用库存池。
这显然不太理想,但它是大型应用程序中 mongodb 不能完美满足需求的唯一部分。另外,到目前为止它完美无缺。这在很多情况下可能是不可能的,但是由于我使用的文档结构,它很适合。
2) 将事务数据库与 MongoDB 结合使用。通常使用 MySQL 为绝对需要它们的事物提供事务,同时让 MongoDB(或任何其他 NoSQL)做它最擅长的事情。
如果我的 #1 解决方案从长远来看不起作用,我将进一步研究将 MongoDB 与 MySQL 结合,但现在 #1 很适合我的需求。
"Starbucks Does Not Use Two Phase Commit" 中有一个很好的解释。
这与 NoSQL 数据库无关,但它确实说明了有时您可以承受丢失事务或使数据库暂时处于不一致状态的情况。
我不认为它是需要“修复”的东西。解决方法是使用符合 ACID 的关系数据库。当 NoSQL 的行为满足您的应用程序要求时,您可以选择它。
我认为其他人已经给出了很好的答案。但是我想补充一点,有 ACID NOSQL 数据库(如 http://ravendb.net/ )。所以这不仅仅是决定 NOSQL - 没有 ACID 与 ACID 的关系......
从 MongoDB v4.0 开始,将支持多文档 ACID 事务。通过快照隔离,事务将提供全局一致的数据视图,并强制执行全有或全无以保持数据完整性。
他们感觉像是来自关系世界的交易,例如:
with client.start_session() as s:
s.start_transaction()
try:
collection.insert_one(doc1, session=s)
collection.insert_one(doc2, session=s)
s.commit_transaction()
except Exception:
s.abort_transaction()
请参阅https://www.mongodb.com/blog/post/multi-document-transactions-in-mongodb
“无法正确保存”可能意味着:
默认情况下,MongoDB 不会立即将您的更改保存到驱动器。所以有可能你告诉用户“更新成功”,发生断电,更新丢失。 MongoDB 提供了控制更新“持久性”级别的选项。它可以等待其他副本接收此更新(在内存中),等待写入本地日志文件等。对多个集合甚至多个文档中的多个文档没有简单的“原子”更新同一个集合。在大多数情况下这不是问题,因为它可以通过两阶段提交来规避,或者重组您的架构以便对单个文档进行更新。请参阅此问题:文档数据库:冗余数据、引用等(特别是 MongoDB)
请阅读 ACID properties 以获得更好的理解。
您还可以在 MongoDB 文档中找到 question and answer。
MongoDB 不符合 ACID。请阅读下文,了解有关 ACID 合规性的讨论。
MongoDB 仅在文档级别上是原子的。它不符合我们从关系数据库系统中知道的原子的定义,尤其是上面的链接。从这个意义上说,MongoDB 不符合来自 ACID 的 A。 MongoDB 默认是一致的。但是,您可以从副本集中的辅助服务器读取。在这种情况下,您只能获得最终的一致性。如果您不介意阅读稍微过时的数据,这将很有用。 MongoDB 不保证隔离(再次根据上述定义):
对于具有多个并发读写器的系统,MongoDB 将允许客户端在写操作返回之前读取写操作的结果。如果 mongod 在日志提交之前终止,即使写入成功返回,查询也可能读取到 mongod 重新启动后将不存在的数据。但是,MongoDB 单独修改每个文档(用于插入和更新);仅在文档级别上,而不是在多文档事务上。
关于持久性 - 您可以使用写入关注选项配置此行为,但不确定。也许有人更清楚。
我相信正在进行一些研究以将 NoSQL 转向 ACID 约束或类似约束。这是一个挑战,因为 NoSQL 数据库通常更快(更),而 ACID 约束会显着降低性能。
atomic 修改针对单个集合的工作的唯一原因是因为 mongodb 开发人员最近用集合范围的写锁交换了数据库锁。决定在这里增加并发是值得的。 mongodb 的核心是一个内存映射文件:他们将缓冲池管理委托给机器的 vm 子系统。因为它总是在内存中,所以他们能够摆脱非常粗粒度的锁:你将在持有它的同时只执行内存中的操作,这将非常快。这与传统的数据库系统有很大不同,传统的数据库系统有时被迫在持有页锁或行锁的同时执行 I/O。
“在 MongoDB 中,对单个文档的操作是原子的” - 这是过去的事情
在新版本的 MongoDB 4.0 中,您可以:
但是,对于需要原子性来更新多个文档或读取多个文档之间的一致性的情况,MongoDB 提供了针对副本集执行多文档事务的能力。多文档事务可以跨多个操作、集合、数据库和文档使用。多文档交易提供了“全有或全无”的主张。当事务提交时,事务中所做的所有数据更改都会被保存。如果事务中的任何操作失败,事务中止并且事务中所做的所有数据更改都将被丢弃,而不会变得可见。在事务提交之前,事务中的写操作在事务之外是不可见的。
尽管可以执行的操作和内容的限制很少。
检查 Mongo 文档。 https://docs.mongodb.com/master/core/transactions/
如果您的存储支持每个键的线性化以及比较和设置(对于 MongoDB 来说是这样),您可以在客户端实现原子多键更新(可序列化事务)。这种方法在 Google's Percolator 和 CockroachDB 中使用,但没有什么能阻止您将它与 MongoDB 一起使用。
我创建了 step-by-step visualization 个此类交易。我希望它能帮助你理解它们。
如果您对已提交的读取隔离级别感到满意,那么看看 Peter Bailis 的 RAMP transactions 是有意义的。它们也可以在客户端为 MongoDB 实现。