Eu estive pensando sobre isso por um tempo agora e só consigo pensar em duas maneiras de fazer isso. Ambos podem funcionar totalmente transparentes quando criados em uma camada/modelo de dados abstratos.
A propósito, existe uma implementação para dados de tabela "versionáveis" na doutrina do mapeador ORM. Veja este exemplo em seus documentos . Talvez isso atenda às suas necessidades, mas não atende às minhas. Parece excluir todos os dados do histórico quando o registro original é excluído, tornando-o não realmente seguro para revisão.
Opção A:tenha uma cópia de cada tabela para armazenar dados de revisão
Digamos que você tenha uma tabela de contatos simples:
CREATE TABLE contact (
id INT NOT NULL auto_increment,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
PRIMARY KEY (id)
)
Você criaria uma cópia dessa tabela e adicionaria dados de revisão:
CREATE TABLE contact_revisions (
id INT NOT NULL,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
revision_id INT auto_increment,
type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
change_time DEFAULT current_timestamp,
PRIMARY KEY(revision_id)
)
Acompanhe
INSERT
e UPDATE
usando AFTER
gatilhos. Em cada nova revisão de dados no original, insira uma cópia dos novos dados na tabela de revisão e defina a modificação type
devidamente. Para registrar um
DELETE
revisionalmente seguro você também deve inserir uma nova linha na tabela de histórico! Para isso você deve usar um BEFORE DELETE
acionar e armazenar os valores mais recentes antes de serem excluídos. Caso contrário, você terá que remover cada NOT NULL
restrição na tabela de histórico também. Algumas observações importantes sobre esta implementação
- Para a tabela de histórico, você deve descartar cada
UNIQUE KEY
(aqui:aPRIMARY KEY
) da tabela de revisão porque você terá a mesma chave várias vezes para cada revisão de dados. - Quando você
ALTER
o esquema e os dados na tabela original por meio de uma atualização (por exemplo, atualização de software), você deve garantir que os mesmos dados ou correções de esquema sejam aplicados à tabela de histórico e seus dados também. Caso contrário, você terá problemas ao reverter para uma revisão mais antiga de um conjunto de registros. - Em uma implementação do mundo real, você gostaria de saber qual usuário modificou os dados. Para ter esse registro de usuário revisionalmente seguro, nunca deve ser excluído da tabela de usuários. Você deve apenas definir a conta desativada com um sinalizador.
- Geralmente, uma única ação do usuário envolve mais de uma tabela. Em uma implementação do mundo real, você também teria que acompanhar quais alterações em várias tabelas pertencem a uma única transação de usuário e também em qual ordem. Em um caso de uso real, você deseja reverter todas as alterações de uma única transação juntas, em ordem inversa. Isso exigiria uma tabela de revisão adicional que rastreia os usuários e transações e mantém uma relação livre com todas essas revisões individuais nas tabelas de histórico.
Benefícios:
- completamente no banco de dados, independente do código do aplicativo. (bem, não quando o rastreamento de transações do usuário é importante. Isso exigiria alguma lógica fora do escopo da consulta única)
- todos os dados estão no formato original, sem conversões de tipo implícitas.
- bom desempenho na pesquisa nas revisões
- reversão fácil. Basta fazer um simples
INSERT .. ON DUPLICATE KEY UPDATE ..
declaração na tabela original, usando os dados da revisão que você deseja reverter.
Méritos:
- Difícil de implementar manualmente.
- Difícil (mas não impossível) de automatizar quando se trata de migrações de banco de dados/atualizações de aplicativos.
Como já foi dito acima, doutrinas
versionable
faz algo semelhante. Opção B:ter uma tabela de registro de alterações central
prefácio:má prática, mostrado apenas para ilustração da alternativa.
Essa abordagem depende muito da lógica do aplicativo, que deve estar oculta em uma camada/modelo de dados.
Você tem uma tabela de histórico central que acompanha
- Quem fez
- quando
- modificar, inserir ou excluir
- quais dados
- em qual campo
- de qual tabela
Como na outra abordagem, você também pode querer rastrear quais alterações de dados individuais pertencem a uma única ação/transação do usuário e em qual ordem.
Benefícios:
- não é necessário manter a sincronia com a tabela original ao adicionar campos a uma tabela ou criar uma nova tabela. ele é dimensionado de forma transparente.
Méritos:
- prática ruim usando um valor simples =armazenamento de chaves no banco de dados
- desempenho de pesquisa ruim devido a conversões de tipo implícitas
- pode diminuir o desempenho geral do aplicativo/banco de dados, quando a tabela de histórico central se torna um gargalo devido a bloqueios de gravação (isso se aplica apenas a mecanismos específicos com bloqueios de tabela, ou seja, MyISAM)
- É muito mais difícil implementar reversões
- possíveis erros de conversão de dados/perda de precisão devido à conversão implícita de tipo
- não acompanha as alterações quando você acessa diretamente o banco de dados em algum lugar em seu código em vez de usar seu modelo/camada de dados e esquece que, neste caso, você deve gravar manualmente no log de revisão. Pode ser um grande problema ao trabalhar em equipe com outros programadores.
Conclusão:
- Opção B pode ser muito útil para aplicativos pequenos como um simples "drop in" quando é apenas para registrar alterações.
- Se você quiser voltar no tempo e comparar facilmente as diferenças entre a revisão histórica 123 para revisão 125 e/ou reverter para os dados antigos, então Opção A é o caminho mais difícil.