【发布时间】: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:
- 表不以 _ 开头(因为历史表会这样做);
- 这些表有一个非复合主键;
- 这些表没有名为 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 个解决方案:
- 在删除行之前更新 author_id 列。
我不喜欢这个解决方案,因为它会在历史记录表中创建额外的行。此外,它还为每个删除例程添加了额外的逻辑。
- 删除行并根据最后插入的 id 直接在历史记录表中更新 author_id。
可以在不向删除例程添加额外代码的情况下完成这项工作,但直接访问历史表对我来说听起来像是一个很大的问题(最终这些表会非常大)。
- 逻辑删除而不是物理删除。
逻辑删除还是物理删除是根据数据和业务需求做出的决定,而不是修复模型上的警告。
您认为哪种解决方案最好?为什么?这个问题还有其他解决方案吗?
【问题讨论】:
标签: mysql sql database-design