如何正确跟踪记录更改和作者的更改

时间:2016-03-18 20:41:35

标签: mysql sql database-design

我有下表:

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 and revision的列。
  4. 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列。
    2. 我不喜欢这个解决方案,因为它在历史记录表中创建了额外的行。此外,它为每个删除例程添加了额外的逻辑。

      1. 删除行并根据上次插入的ID直接在历史记录表中更新author_id。
      2. 可以在不向删除例程添加额外代码的情况下完成这项工作,但直接访问历史表对我来说听起来不是很大(最终这些表会非常大)。

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

          您认为哪种解决方案最好?为什么?还有另一个解决此问题的方法吗?

1 个答案:

答案 0 :(得分:1)

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

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

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

教授。理查德斯诺德格拉斯写了一本精美的书Developing Time-Oriented Database Applications in SQL,并在线提供。它有很多关于如何处理这类信息的好主意。