【问题标题】:How to proper track record changes and the author of the changes如何正确更改跟踪记录和更改的作者
【发布时间】:2016-03-19 05:28:50
【问题描述】:

我有下表:

CREATE TABLE car
(
    car_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,

    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY (car_id)
);

要审核此表上的数据更改,我正在创建一个历史表。通过使用触发器,历史表将为在原始表上执行的每个插入、更新和删除查询都有一个条目。历史表的结构与它跟踪的表相同,除了 2 个附加列,一个用于存储发生的操作的列和一个用于存储序列号的列。我已经使用这个解决方案很长时间了,主要是因为它实施起来很轻松,而且几乎不需要维护。

以下脚本生成在数据库中实现这些历史表所需的 SQL:

  1. 表不以 _ 开头(因为历史表会这样做);
  2. 这些表有一个非复合主键;
  3. 这些表没有名为 action 和 revision 的列。

SQL:

SELECT
    'CREATE TABLE __' || table_name || ' LIKE ' || table_name || ';\r\n' ||
    'RENAME TABLE __' || table_name || ' TO _' || table_name || ';\r\n' ||
    'ALTER TABLE _' || table_name || ' ADD action TINYINT UNSIGNED NOT NULL FIRST;\r\n' ||
    'ALTER TABLE _' || table_name || ' ADD revision INT UNSIGNED NOT NULL FIRST;\r\n' ||
    'ALTER TABLE _' || table_name || ' MODIFY ' || column_name || ' ' || UPPER(data_type) || IF(is_nullable = 'NO', ' NOT NULL', '') || ';\r\n' ||
    'ALTER TABLE _' || table_name || ' DROP PRIMARY KEY;\r\n' ||
    'ALTER TABLE _' || table_name || ' MODIFY COLUMN revision INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;\r\n' ||
    'CREATE TRIGGER i' || table_name || ' AFTER INSERT ON ' || table_name || ' FOR EACH ROW INSERT INTO _' || table_name || ' SELECT NULL, 1, ' || table_name || '.* FROM ' || table_name || ' WHERE ' || table_name || '.' || column_name || ' = NEW.' || column_name || ';\r\n' ||
    'CREATE TRIGGER u' || table_name || ' AFTER UPDATE ON ' || table_name || ' FOR EACH ROW INSERT INTO _' || table_name || ' SELECT NULL, 2, ' || table_name || '.* FROM ' || table_name || ' WHERE ' || table_name || '.' || column_name || ' = NEW.' || column_name || ';\r\n' ||
    'CREATE TRIGGER d' || table_name || ' BEFORE DELETE ON ' || table_name || ' FOR EACH ROW INSERT INTO _' || table_name || ' SELECT NULL, 3, ' || table_name || '.* FROM ' || table_name || ' WHERE ' || table_name || '.' || column_name || ' = OLD.' || column_name || ';'
FROM information_schema.tables
JOIN information_schema.table_constraints USING (table_schema, table_name)
JOIN information_schema.key_column_usage USING (table_schema, table_name, constraint_name)
JOIN information_schema.columns USING (table_schema, table_name, column_name) 
WHERE
    information_schema.tables.table_schema = (SELECT DATABASE()) AND
    information_schema.table_constraints.constraint_type = 'PRIMARY KEY';

现在我有一个新的业务规则,它稍微打破了我的模型。每一行都需要保存 author_id,即对数据进行最后一次更改的用户,因此我将 author_id 列添加到 car 表和跟踪其更改的历史表中。这是一个问题,因为当用户删除一行时,该更改的作者将丢失。针对这个问题,我找到了 3 个解决方案:

  1. 在删除行之前更新 author_id 列。

我不喜欢这个解决方案,因为它会在历史记录表中创建额外的行。此外,它还为每个删除例程添加了额外的逻辑。

  1. 删除行并根据最后插入的 id 直接在历史记录表中更新 author_id。

可以在不向删除例程添加额外代码的情况下完成这项工作,但直接访问历史表对我来说听起来像是一个很大的问题(最终这些表会非常大)。

  1. 逻辑删除而不是物理删除。

逻辑删除还是物理删除是根据数据和业务需求做出的决定,而不是修复模型上的警告。

您认为哪种解决方案最好?为什么?这个问题还有其他解决方案吗?

【问题讨论】:

    标签: mysql sql database-design


    【解决方案1】:

    您应该考虑让您的DELETE 触发器将新行插入到记录每个删除操作的历史表中。该历史记录行可以携带作者删除主表行的author_id

    使用逻辑删除(将每个已删除的行标记为非活动行)也是一个不错的方法。这是处理过时行的常用方法。您可以使用每个表的适当视图隐藏应用程序逻辑中逻辑删除的行。

    无论如何,您都不应该更新历史记录表中的一行。相反,您应该为要记录的每个事件插入一个新行。如果你这样做,你将提高你的历史的完整性。您不必在桌子上授予UPDATE priv。你可以使用便宜的ARCHIVE storage engine。而且,如果您的主表具有某种财务价值,您的审计师会为您赞不绝口。

    教授。 Richard Snodgrass 写了一本好书Developing Time-Oriented Database Applications in SQL,并已在线提供。它有很多关于如何处理这类信息的好主意。

    【讨论】:

    • 很好的输入,谢谢,特别是这篇文章。我会再等几天,希望有人在那里找到一点魔法并修复我的模型,然后再开始重写每个删除例程。另外,我无法在 1 和 3 之间下定决心,这两种解决方案看起来都很糟糕。
    猜你喜欢
    • 2021-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-11
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多