是否有一个MySQL选项/function来跟踪logging更改的历史logging?
我被问到是否可以跟踪对MySQL数据库中logging的更改。 所以当一个领域被改变时,旧的vs新的可用的和这个发生的date。 有没有一个function或共同的技术来做到这一点?
如果是这样,我正在考虑做这样的事情。 创build一个名为更改的表。 它将包含与主表TABLE相同的字段,但前缀为新旧,但仅适用于实际更改的那些字段以及一个TIMESTAMP。 它将被编入一个ID。 这样,可以运行一个SELECT报告来显示每条logging的历史logging。 这是一个好方法吗? 谢谢!
这是微妙的。
如果业务需求是“我想审计数据的变化 – 谁做什么,什么时候做什么?”,通常可以使用审计表(根据Keethanjan发布的触发器示例)。 我不是一个触发器的粉丝,但它有很大的好处,实现相对容易 – 你现有的代码不需要知道触发器和审计的东西。
如果业务需求是“告诉我过去某个特定date的数据状态”,那么意味着随着时间的推移已经进入了您的解决scheme。 虽然你可以通过查看审计表来重build数据库的状态,但这很难且容易出错,而且对于任何复杂的数据库逻辑来说都变得笨拙。 例如,如果企业想知道“find我们应该发送给那些在本月的第一天有未付款发票的客户的地址”,那么您可能需要翻查六张审计表格。
相反,您可以随着时间的推移将变化的概念烘焙到您的模式devise中(这是Keethanjanbuild议的第二种select)。 这是对应用程序的更改,绝对是在业务逻辑和持久层面,所以这不是微不足道的。
例如,如果你有这样一个表:
CUSTOMER --------- CUSTOMER_ID PK CUSTOMER_NAME CUSTOMER_ADDRESS
你想跟踪一段时间,你会修改它如下:
CUSTOMER ------------ CUSTOMER_ID PK CUSTOMER_VALID_FROM PK CUSTOMER_VALID_UNTIL PK CUSTOMER_STATUS CUSTOMER_USER CUSTOMER_NAME CUSTOMER_ADDRESS
每次要更改客户logging(而不是更新logging)时,都应将当前logging上的VALID_UNTIL设置为NOW(),然后插入带有VALID_FROM(现在)和空VALID_UNTIL的新logging。 您将“CUSTOMER_USER”状态设置为当前用户的loginID(如果需要保留)。 如果客户需要删除,您可以使用CUSTOMER_STATUS标志来表明这一点 – 您可能永远不会从这个表中删除logging。
这样,你总是可以find客户表的状态是什么date – 地址是什么? 他们改名了吗? 通过join其他具有相似valid_from和valid_untildate的表,您可以重build历史上的整个图片。 要查找当前状态,请searchVALID_UNTILdate为空的logging。
这很笨拙(严格来说,你不需要valid_from,但它使查询更容易一些)。 这使您的devise和数据库访问复杂化。 但它使重build世界变得更容易。
这是一个简单的方法来做到这一点:
首先,为每个要跟踪的数据表创build一个历史logging表(以下示例查询)。 此表将为每个插入,更新和删除数据表中的每一行执行的查询条目。
历史表的结构将与其追踪的数据表相同,除了三个附加列:存储发生的操作的列(我们称之为“操作”),操作的date和时间以及列存储一个序列号('revision'),每个操作增加一个序列号,并按照数据表的主键列进行分组。
要执行此sorting行为,将在主键列和修订列上创build一个双列(复合)索引。 请注意,如果历史logging表使用的引擎是MyISAM,则只能按此方式进行sorting( 请参阅本页上的“MyISAM注释”)
历史表很容易创build。 在下面的ALTER TABLE查询中(以及在下面的触发查询中),将“primary_key_column”replace为数据表中该列的实际名称。
CREATE TABLE MyDB.data_history LIKE MyDB.data; ALTER TABLE MyDB.data_history MODIFY COLUMN primary_key_column int(11) NOT NULL, DROP PRIMARY KEY, ENGINE = MyISAM, ADD action VARCHAR(8) DEFAULT 'insert' FIRST, ADD revision INT(6) NOT NULL AUTO_INCREMENT AFTER action, ADD dt_datetime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER revision, ADD PRIMARY KEY (primary_key_column, revision);
然后你创build触发器:
DROP TRIGGER IF EXISTS MyDB.data__ai; DROP TRIGGER IF EXISTS MyDB.data__au; DROP TRIGGER IF EXISTS MyDB.data__bd; CREATE TRIGGER MyDB.data__ai AFTER INSERT ON MyDB.data FOR EACH ROW INSERT INTO MyDB.data_history SELECT 'insert', NULL, NOW(), d.* FROM MyDB.data AS d WHERE d.primary_key_column = NEW.primary_key_column; CREATE TRIGGER MyDB.data__au AFTER UPDATE ON MyDB.data FOR EACH ROW INSERT INTO MyDB.data_history SELECT 'update', NULL, NOW(), d.* FROM MyDB.data AS d WHERE d.primary_key_column = NEW.primary_key_column; CREATE TRIGGER MyDB.data__bd BEFORE DELETE ON MyDB.data FOR EACH ROW INSERT INTO MyDB.data_history SELECT 'delete', NULL, NOW(), d.* FROM MyDB.data AS d WHERE d.primary_key_column = OLD.primary_key_column;
你完成了。 现在,'MyDb.data'中的所有插入,更新和删除都将被logging在'MyDb.data_history'中,为您提供这样的历史logging表(减去人为的'data_columns'列)
ID revision action data columns.. 1 1 'insert' .... initial entry for row where ID = 1 1 2 'update' .... changes made to row where ID = 1 2 1 'insert' .... initial entry, ID = 2 3 1 'insert' .... initial entry, ID = 3 1 3 'update' .... more changes made to row where ID = 1 3 2 'update' .... changes made to row where ID = 3 2 2 'delete' .... deletion of row where ID = 2
要显示从更新到更新的给定列的更改,您需要在主键和序列列上将自己的历史logging表连接到自己。 您可以为此创build一个视图,例如:
CREATE VIEW data_history_changes AS SELECT t2.dt_datetime, t2.action, t1.primary_key_column as 'row id', IF(t1.a_column = t2.a_column, t1.a_column, CONCAT(t1.a_column, " to ", t2.a_column)) as a_column FROM MyDB.data_history as t1 INNER join MyDB.data_history as t2 on t1.primary_key_column = t2.primary_key_column WHERE (t1.revision = 1 AND t2.revision = 1) OR t2.revision = t1.revision+1 ORDER BY t1.primary_key_column ASC, t2.revision ASC
你可以创build触发器来解决这个问题。 这是一个教程 (存档链接)。
在数据库中设置约束和规则要好于编写特殊代码来处理相同的任务,因为它会阻止另一个开发人员编写绕过所有特殊代码的不同查询,并可能使数据库保持较差的数据完整性。
很长一段时间,我使用脚本将信息复制到另一个表,因为MySQL当时不支持触发器。 现在我发现这个触发器在跟踪所有事情方面更有效。
如果某人在编辑某一行时发生了更改,则此触发器会将旧值复制到历史logging表中。 每当有人编辑该行时,
Editor ID
和last mod
都存储在原始表中; 该时间对应于何时改变为其当前forms。
DROP TRIGGER IF EXISTS history_trigger $$ CREATE TRIGGER history_trigger BEFORE UPDATE ON clients FOR EACH ROW BEGIN IF OLD.first_name != NEW.first_name THEN INSERT INTO history_clients ( client_id , col , value , user_id , edit_time ) VALUES ( NEW.client_id, 'first_name', NEW.first_name, NEW.editor_id, NEW.last_mod ); END IF; IF OLD.last_name != NEW.last_name THEN INSERT INTO history_clients ( client_id , col , value , user_id , edit_time ) VALUES ( NEW.client_id, 'last_name', NEW.last_name, NEW.editor_id, NEW.last_mod ); END IF; END; $$
另一个解决scheme是保留修订字段并在保存时更新该字段。 你可以决定max是最新的版本,或者0是最新的版本。 这取决于你。
这是我们如何解决它
用户表看起来像这样
Users ------------------------------------------------- id | name | address | phone | email | created_on | updated_on
业务需求发生了变化,我们需要检查用户以前的所有地址和电话号码。 新的模式看起来像这样
Users (the data that won't change over time) ------------- id | name UserData (the data that can change over time and needs to be tracked) ------------------------------------------------- id | id_user | revision | city | address | phone | email | created_on 1 | 1 | 0 | NY | lake st | 9809 | @long | 2015-10-24 10:24:20 2 | 1 | 2 | Tokyo| lake st | 9809 | @long | 2015-10-24 10:24:20 3 | 1 | 3 | Sdny | lake st | 9809 | @long | 2015-10-24 10:24:20 4 | 2 | 0 | Ankr | lake st | 9809 | @long | 2015-10-24 10:24:20 5 | 2 | 1 | Lond | lake st | 9809 | @long | 2015-10-24 10:24:20
要查找任何用户的当前地址,我们search带有修订版本DESC和LIMIT 1的UserData
为了在一段时间内获得用户的地址,我们可以使用created_on bewteen(date1,date2)
只是我2美分。 我会创build一个解决scheme,logging改变了什么,非常类似于瞬态的解决scheme。
我的ChangesTable很简单:
DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue
1)在主表中更改整行时,会有很多条目进入该表,但是这不太可能,所以不是一个大问题(人们通常只会改变一件事情)2)OldVaue(和NewValue,如果你想)必须是某种史诗般的“任何types”,因为它可以是任何数据,可能有办法做到这一点与RAWtypes或只是使用JSONstring转换进出。
最小的数据使用率,存储您需要的一切,并可以一次用于所有表格。 我现在正在自己研究这个,但是这可能最终成为我走的路。
对于创build和删除,只需要行ID,不需要字段。 在主表上删除一个标志(active?)会很好。
为什么不简单地使用bin日志文件? 如果在Mysql服务器上设置复制,并且binlog文件格式设置为ROW,则可以捕获所有更改。
可以使用一个名为noplay的好的Python库。 更多信息在这里 。
这样做的直接方法是在表上创build触发器。 设置一些条件或映射方法。 当更新或删除发生时,它会自动插入“更改”表。
但最大的部分是如果我们有很多专栏和大量的表格。 我们必须input每个表的每个列的名称。 显然,这是浪费时间。
为了更好地处理这个问题,我们可以创build一些程序或函数来检索列的名称。
我们也可以简单地使用第三方工具来做到这一点。 在这里,我写了一个Java程序Mysql Tracker