MongoDB/NoSQL:保留文档变更历史。
MongoDB/NoSQL:保留文档变更历史。
在数据库应用中一个相当常见的需求是跟踪数据库中一个或多个特定实体的变化。听说这被称为行版本控制、日志表或历史表(我确定还有其他名称)。在关系型数据库管理系统中,有很多方法可以处理它——你既可以将所有源表的所有更改写入单个表中(更像是一个日志),也可以为每个源表有一个单独的历史表。此外,你还可以选择通过应用程序代码或数据库触发器来管理日志记录。
我试图思考在 NoSQL/document 数据库(特别是 MongoDB)中,处理同样问题的解决方案是什么,以及如何以统一的方式解决问题。它是否像创建文档的版本号,并永不覆盖它们那样简单?创建“真实”文档与“日志记录”文档的不同集合?这会如何影响查询和性能?
总之,在 NoSQL 数据库中是否有常见的这种情况,如果有,是否存在常见的解决方案?
为什么不对在文档内进行更改存储进行变化?
与每个键值对存储版本不同,文档中的当前键值对始终代表最新状态,并在历史数组中存储一次“日志”更改。只有那些自创建以来发生变化的键才会在日志中有条目。
{ _id: "4c6b9456f61f000000007ba6" title: "Bar", body: "Is this thing on?", tags: [ "test", "trivial" ], comments: [ { key: 1, author: "joe", body: "Something cool" }, { key: 2, author: "xxx", body: "Spam", deleted: true }, { key: 3, author: "jim", body: "Not bad at all" } ], history: [ { who: "joe", when: 20160101, what: { title: "Foo", body: "What should I write?" } }, { who: "jim", when: 20160105, what: { tags: ["test", "test2"], comments: { key: 3, body: "Not baaad at all" } } ] }
很好的问题,我也在研究这个。
每次更改创建一个新版本
我发现 Ruby 的 Mongoid 驱动程序的版本模块。我自己没有使用过它,但从我所找到的资料中可以发现,它会给每个文档添加一个版本号,旧版本嵌入在文档本身中。最大的缺点是,每次更改都会复制整个文档,当你处理大型文档时,会导致大量重复的内容存储。不过当你处理小型文档和/或不经常更新文档时,这种方法是可以的。
仅存储新版本中的更改
另一种方法是仅存储更改的字段。然后你可以“展平”你的历史记录来重构文档的任何版本。不过这相当复杂,因为你需要跟踪模型中的更改,并以应用程序可以重构最新状态的方式存储更新和删除。这可能很棘手,因为你处理的是结构化文档而不是扁平的 SQL 表格。
在文档内存储更改
每个字段也可以有独立的历史记录。使用这种方式重构文档到给定版本会更容易。在你的应用程序中,你不需要明确地跟踪更改,只需在更改其值时创建该属性的新版本即可。文档可能像这样:
{ _id: "4c6b9456f61f000000007ba6" title: [ { version: 1, value: "Hello world" }, { version: 6, value: "Foo" } ], body: [ { version: 1, value: "Is this thing on?" }, { version: 2, value: "What should I write?" }, { version: 6, value: "This is the new body" } ], tags: [ { version: 1, value: [ "test", "trivial" ] }, { version: 6, value: [ "foo", "test" ] } ], comments: [ { author: "joe", // Unversioned field body: [ { version: 3, value: "Something cool" } ] }, { author: "xxx", body: [ { version: 4, value: "Spam" }, { version: 5, deleted: true } ] }, { author: "jim", body: [ { version: 7, value: "Not bad" }, { version: 8, value: "Not bad at all" } ] } ] }
在版本中将文档的一部分标记为删除仍然有些别扭。你可以为可以从应用程序中删除/恢复的部分引入一个 state
字段:
{ author: "xxx", body: [ { version: 4, value: "Spam" } ], state: [ { version: 4, deleted: false }, { version: 5, deleted: true } ] }
使用这些方法,你可以将最新版本和历史数据存储在一个集合中的“平坦”版本中。如果你只对最新版本的文档感兴趣,这应该可以改善查询时间。但是当你需要最新版本和历史数据时,你将需要执行两次查询而不是一次。因此,使用单个集合与使用两个分离的集合的选择应取决于你的应用程序需要历史版本的频率。
这个答案大部分只是我思考的结果,我还没有尝试任何一个方法。回头看,第一个选项可能是最简单和最好的解决方案,除非重复数据的开销对你的应用程序非常重要,否则不推荐使用第二个选项,它相当复杂。第三个选项基本上是选项二的优化,应该更容易实现,但除非你真的无法使用选项一,否则不值得实现。
期望能够收到对此的反馈,以及其他人对该问题的解决方案 🙂