22

我有使用 Codeigniter 编写的系统和使用 MySQL 的数据库。系统有用户,具有不同权限的用户组等。有很多mysql表,它们有多对多的关系。

我有的一些表:

  • 项目
  • 合同
  • 顾客
  • 产品
  • 产品特性
  • 订单
  • order_features
  • order_products
  • ETC...

目前,我正在记录用户对这些表的数据所做的每一次更改。用户可以根据他们的权限更改这些数据。仅以简单的形式存储日志的更改,例如

A user changed product features with id of A8767
B user added new customer with id 56
C user edited content of orderlist
A user added new product (id: A8767) to order (id: or67)
...

我想保留对每个细节所做的所有更改,例如问题 Stackoverflow 的编辑历史记录。我可以考虑log_table设计以保留来自各种表的所有数据更改。有没有办法,教程,引擎,插件来做到这一点?只有我能想到复制每个表并继续在它们上存储更改,但我认为这不是好方法。

4

2 回答 2

23

我已经考虑了一段时间,只能想到两种方法来做到这一点。当制作成抽象数据层/模型时,两者都可以完全透明地工作。

顺便说一句,ORM 映射器原则中有一个“版本化”表数据的实现。在他们的文档中查看这个例子。也许这符合你的需要,但它不适合我的。删除原始记录时似乎会删除所有历史数据,使其不是真正的修订安全。

选项 A:拥有每个表的副本以保存修订数据

假设您有一个简单的联系人表:

CREATE TABLE contact (
    id INT NOT NULL auto_increment,
    name VARCHAR(255),
    firstname VARCHAR(255),
    lastname VARCHAR(255),
    PRIMARY KEY (id)
)

您将创建该表的副本并添加修订数据:

CREATE TABLE contact_revisions (
    id INT NOT NULL,
    name VARCHAR(255),
    firstname VARCHAR(255),
    lastname VARCHAR(255),
    revision_id INT auto_increment,
    type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
    change_time DEFAULT current_timestamp,
    PRIMARY KEY(revision_id)
)

跟踪INSERTUPDATE使用AFTER触发器。在原始数据的每个新数据修订中,在修订表中插入新数据的副本并type正确设置修改。

要记录DELETE修订安全,您还必须在历史表中插入新行!为此,您应该使用BEFORE DELETE触发器并在删除最新值之前存储它们。否则,您还必须删除NOT NULL历史表中的每个约束。

关于此实现的一些重要说明

  • 对于历史表,您必须从修订表中删除每个UNIQUE KEY(此处:PRIMARY KEY),因为对于每个数据修订,您将多次拥有相同的键。
  • 当您ALTER通过更新(例如软件更新)对原始表中的模式和数据进行更新时,您必须确保将相同的数据或模式更正应用于历史表及其数据。否则,您在恢复到记录集的旧版本时会遇到麻烦。
  • 在现实世界的实现中,您可能想知道哪个用户修改了数据。为了保证修订安全,永远不要从 users 表中删除用户记录。您应该只使用标志设置禁用帐户。
  • 通常,单个用户操作涉及多个表。在现实世界的实现中,您还必须跟踪多个表中的哪些更改属于单个用户事务以及以何种顺序。在实际用例中,您可能希望以相反的顺序一起还原单个事务的所有更改。这将需要一个额外的修订表来跟踪用户和事务,并与历史表中的所有这些单独的修订保持松散的关系。

好处:

  • 完全在数据库中,独立于应用程序代码。(好吧,当跟踪用户事务很重要时并不重要。这将需要单个查询范围之外的一些逻辑)
  • 所有数据都是原始格式,没有隐式类型转换。
  • 在修订中的搜索性能良好
  • 轻松回滚。只需在原始表上执行一个简单的INSERT .. ON DUPLICATE KEY UPDATE ..语句,使用您要回滚的修订版中的数据。

优点:

  • 很难手动实现。
  • 在数据库迁移/应用程序更新方面很难(但并非不可能)实现自动化。

如上所述,教义versionable也有类似的作用。


选项 B:有一个中央更改日志表

前言:不好的做法,仅用于说明替代方案。

这种方法确实严重依赖应用程序逻辑,它应该隐藏在数据层/模型中。

您有一个中央历史记录表来跟踪

  • 谁干的
  • 什么时候
  • 修改、插入或删除
  • 什么数据
  • 在哪个领域
  • 哪张桌子

与其他方法一样,您可能还希望跟踪哪些单独的数据更改属于单个用户操作/事务以及以何种顺序。

好处:

  • 向表中添加字段或创建新表时无需与原始表保持同步。它透明地缩放。

优点:

  • 使用简单值 = 数据库中的密钥存储的不良做法
  • 由于隐式类型转换,搜索性能不佳
  • 当中央历史表因写锁而成为瓶颈时,可能会降低应用程序/数据库的整体性能(这仅适用于具有表锁的特定引擎,即 MyISAM)
  • 实现回滚要困难得多
  • 由于隐式类型转换可能导致的数据转换错误/精度损失
  • 当您直接访问代码中某处的数据库而不是使用模型/数据层时,不会跟踪更改,并且忘记在这种情况下您必须手动写入修订日志。与其他程序员一起工作时可能是一个大问题。

结论:

  • 选项 B对于小型应用程序来说非常方便,因为它只是用于记录更改时的简单“插入”。
  • 如果您想及时返回并能够轻松比较历史修订版123与修订版125之间的差异和/或恢复到旧数据,那么选项 A是艰难的选择。
于 2012-03-24T22:58:57.587 回答
2

使用通用 uni 更新表怎么样。表字段应包含以下值:

user,event,date,table,field,new value

  • 用户 - 做出改变的人
  • 事件 - 作为预定义事件的代码(更新、保存、插入)
  • 日期 - 进行更改的时间
  • 表和字段 - 可以从全局查询自动本地化
  • 值 - 插入的值

可以使用通用查询中的某些功能创建值和插入。

于 2012-03-24T22:45:13.780 回答