Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Existe uma opção/recurso do MySQL para rastrear o histórico de alterações nos registros?


Aqui está uma maneira direta de fazer isso:

Primeiro, crie uma tabela de histórico para cada tabela de dados que você deseja acompanhar (exemplo de consulta abaixo). Essa tabela terá uma entrada para cada consulta de inserção, atualização e exclusão realizada em cada linha da tabela de dados.

A estrutura da tabela de histórico será a mesma da tabela de dados que ela rastreia, exceto por três colunas adicionais:uma coluna para armazenar a operação que ocorreu (vamos chamá-la de 'ação'), a data e hora da operação e uma coluna para armazenar um número de sequência ('revisão'), que aumenta por operação e é agrupado pela coluna de chave primária da tabela de dados.

Para fazer esse comportamento de sequenciamento, um índice de duas colunas (composto) é criado na coluna de chave primária e na coluna de revisão. Observe que você só pode fazer o sequenciamento dessa maneira se o mecanismo usado pela tabela de histórico for MyISAM (Veja 'Notas do MyISAM' nesta página)

A tabela de histórico é bastante fácil de criar. Na consulta ALTER TABLE abaixo (e nas consultas de gatilho abaixo), substitua 'primary_key_column' pelo nome real dessa coluna em sua tabela de dados.
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);

E então você cria os gatilhos:
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;

E você está feito. Agora, todas as inserções, atualizações e exclusões em 'MyDb.data' serão registradas em 'MyDb.data_history', dando-lhe uma tabela de histórico como esta (menos a coluna 'data_columns' artificial)
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 

Para exibir as alterações para uma determinada coluna ou colunas de atualização para atualização, você precisará unir a tabela de histórico a si mesma na chave primária e nas colunas de sequência. Você pode criar uma visualização para essa finalidade, por exemplo:
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

Edit:Oh uau, as pessoas gostam da minha mesa de história de 6 anos atrás :P

Minha implementação ainda está zumbindo, ficando maior e mais difícil de manejar, eu presumo. Eu escrevi visualizações e uma interface de usuário muito boa para ver o histórico neste banco de dados, mas acho que nunca foi muito usado. Assim vai.

Para abordar alguns comentários em nenhuma ordem específica:

  • Eu fiz minha própria implementação em PHP que foi um pouco mais complicada, e evitei alguns dos problemas descritos nos comentários (ter índices transferidos, signifcativamente. Se você transferir índices únicos para a tabela de histórico, as coisas vão quebrar. Existem soluções para isso nos comentários). Seguir este post ao pé da letra pode ser uma aventura, dependendo de quão estabelecido é o seu banco de dados.

  • Se a relação entre a chave primária e a coluna de revisão parecer incorreta, geralmente significa que a chave composta está quebrada de alguma forma. Em algumas raras ocasiões, isso aconteceu e fiquei sem saber a causa.

  • Achei essa solução bastante eficiente, usando gatilhos como ela faz. Além disso, o MyISAM é rápido em inserções, que é tudo o que os gatilhos fazem. Você pode melhorar ainda mais com a indexação inteligente (ou a falta de...). Inserir uma única linha em uma tabela MyISAM com uma chave primária não deve ser uma operação que você precise otimizar, a menos que você tenha problemas significativos acontecendo em outro lugar. Durante todo o tempo em que eu estava executando o banco de dados MySQL, essa implementação da tabela de histórico estava ativada, nunca foi a causa de nenhum dos (muitos) problemas de desempenho que surgiram.

  • se você estiver recebendo inserções repetidas, verifique sua camada de software para consultas do tipo INSERT IGNORE. Hrmm, não me lembro agora, mas acho que há problemas com esse esquema e transações que falham depois de executar várias ações DML. Algo para estar ciente, pelo menos.

  • É importante que os campos na tabela de histórico e na tabela de dados correspondam. Ou melhor, que sua tabela de dados não tem MAIS colunas do que a tabela de histórico. Caso contrário, as consultas insert/update/del na tabela de dados falharão, quando as inserções nas tabelas de histórico colocarem colunas na consulta que não existem (devido a d.* nas consultas do gatilho), e o gatilho falhará. Seria incrível se o MySQL tivesse algo como gatilhos de esquema, onde você pudesse alterar a tabela de histórico se as colunas fossem adicionadas à tabela de dados. O MySQL tem isso agora? Eu reajo esses dias :P