数据库应用程序中一个相当常见的需求是跟踪数据库中一个或多个特定实体的更改.我听说这叫做行版本控制、日志(log)表或历史记录表(我相信还有其他名称).在RDBMS中有很多方法可以实现这一点——您可以将所有源表的所有更改写入一个表(更多的是日志(log)),或者 for each 源表创建一个单独的历史记录表.您还可以 Select 管理登录应用程序代码或通过数据库触发器.

我试图思考在NoSQL/文档数据库(特别是MongoDB)中解决同一问题的方法是什么,以及如何以统一的方式解决.它会像为文档创建版本号一样简单,而且永远不会覆盖它们吗?为"真实"和"记录"文档创建单独的集合?这将如何影响查询和性能?

不管怎么说,这是NoSQL数据库的常见情况吗?如果是的话,有共同的解决方案吗?

推荐答案

好问题,我自己也在调查这件事.

在每次更改时创建一个新版本

我为Ruby找到了Versioning module个Mongoid驱动程序.我自己没用过,但从what I could find开始,它会给每个文档添加一个版本号.旧版本嵌入到文档本身中.主要的缺点是entire document is duplicated on each change,这将导致在处理大型文档时存储大量重复内容.不过,当您处理小型文档和/或不经常更新文档时,这种方法是不错的.

仅在新版本中存储更改

另一种方法是store only the changed fields in a new version美元.然后你可以"展平"你的历史来重建文档的任何版本.但这相当复杂,因为您需要跟踪模型中的更改,并以应用程序可以重建最新文档的方式存储更新和删除.这可能很棘手,因为您处理的是 struct 化文档,而不是扁平的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 }
  ]
}

使用这些方法中的每一种,您都可以在一个集合中存储最新的扁平版本,并在单独的集合中存储历史数据.如果您只对文档的最新版本感兴趣,这将缩短查询时间.但是,当您同时需要最新版本和历史数据时,需要执行两个查询,而不是一个查询.因此,使用单个集合与使用两个独立集合的 Select 应取决于how often your application needs the historical versions.

这个答案大部分只是我脑子里的一堆 idea ,我还没有真正try 过.回过头来看,第一个选项可能是最简单、最好的解决方案,除非重复数据的开销对应用程序非常重要.第二种 Select 相当复杂,可能不值得这么做.第三个选项基本上是对选项二的优化,应该更容易实现,但可能不值得实现,除非你真的不能使用选项一.

期待对此的反馈,以及其他人对该问题的解决方案:)

Mongodb相关问答推荐

MongoDB 4.4如何使用未知密钥更新dict

多键索引,性能问题

MongoDB查询优化

如何聚合过滤器嵌套文档并从其他字段中获取值

mongoDB vs mySQL - 为什么在某些方面比另一个更好

哪种 NoSQL DB 最适合 OLTP 金融系统?

db.createCollection 不是函数

如何检索 MongoDb 集合验证器规则?

从命令行创建 MongoDB 用户

使用 Node.js 和 mongodb 处理超时

Spring Mongo 条件查询两次相同的字段

MongoDB $or 查询

java.lang.IncompatibleClassChangeError:Implementing class Mongo

Mongoose.pre('save') 不会触发

用于嵌入式集合的 MongoDB 首选模式.文档与数组

Flask:设置应用程序和请求特定的属性?

将新值推送到 mongodb 内部数组 - mongodb/php

缩短 MongoDB 属性名称值得吗?

验证 MongoCredential 的异常和未分类的 Mongo Db 异常

Mongodb Compass 无法在 Ubuntu 18.10 中打开