É sutil.
Se o requisito de negócios for "Quero auditar as alterações nos dados - quem fez o quê e quando?", Você geralmente pode usar tabelas de auditoria (conforme o exemplo de gatilho postado por Keethanjan). Não sou um grande fã de gatilhos, mas tem o grande benefício de ser relativamente fácil de implementar - seu código existente não precisa saber sobre os gatilhos e outras coisas de auditoria.
Se o requisito de negócios for "mostre-me qual era o estado dos dados em uma determinada data no passado", isso significa que o aspecto da mudança ao longo do tempo entrou em sua solução. Embora você possa reconstruir o estado do banco de dados apenas olhando as tabelas de auditoria, é difícil e sujeito a erros e, para qualquer lógica de banco de dados complicada, torna-se difícil de controlar. Por exemplo, se a empresa deseja saber "encontrar os endereços das cartas que deveríamos ter enviado aos clientes que tinham faturas pendentes e não pagas no primeiro dia do mês", você provavelmente terá que vasculhar meia dúzia de tabelas de auditoria.
Em vez disso, você pode incorporar o conceito de mudança ao longo do tempo no design do seu esquema (esta é a segunda opção que Keethanjan sugere). Esta é uma mudança em seu aplicativo, definitivamente no nível de lógica de negócios e persistência, portanto, não é trivial.
Por exemplo, se você tiver uma mesa como esta:
CUSTOMER
---------
CUSTOMER_ID PK
CUSTOMER_NAME
CUSTOMER_ADDRESS
e você quisesse acompanhar ao longo do tempo, você o alteraria da seguinte maneira:
CUSTOMER
------------
CUSTOMER_ID PK
CUSTOMER_VALID_FROM PK
CUSTOMER_VALID_UNTIL PK
CUSTOMER_STATUS
CUSTOMER_USER
CUSTOMER_NAME
CUSTOMER_ADDRESS
Cada vez que você deseja alterar um registro de cliente, em vez de atualizar o registro, você define VALID_UNTIL no registro atual para NOW () e insere um novo registro com um VALID_FROM (agora) e um VALID_UNTIL nulo. Você define o status "CUSTOMER_USER" para a ID de login do usuário atual (se precisar mantê-la). Se o cliente precisar ser excluído, use o sinalizador CUSTOMER_STATUS para indicar isso - você nunca pode excluir registros desta tabela.
Dessa forma, você sempre poderá saber qual era o status da mesa do cliente para uma determinada data - qual era o endereço? Eles mudaram de nome? Ao juntar a outras tabelas com datas valid_from e valid_until semelhantes, você pode reconstruir a imagem inteira historicamente. Para encontrar o status atual, você procura por registros com uma data VALID_UNTIL nula.
É complicado (estritamente falando, você não precisa do valid_from, mas torna as consultas um pouco mais fáceis). Isso complica seu design e seu acesso ao banco de dados. Mas torna a reconstrução do mundo muito mais fácil.
Esta é uma maneira simples de fazer isso:
Primeiro, crie uma tabela de histórico para cada tabela de dados que deseja rastrear (consulta de exemplo abaixo). Esta 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 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 da chave primária e na coluna de revisão. Note que você só pode fazer o sequenciamento desta maneira se o motor usado pela tabela de histórico for MyISAM ( Veja 'Notas 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 dela), substitua 'primary_key_column' pelo nome real dessa coluna em sua tabela de dados.
E então você cria os gatilhos:
E pronto. Agora, todas as inserções, atualizações e exclusões em 'MyDb.data' serão registradas em 'MyDb.data_history', dando a você uma tabela de histórico como esta (menos a coluna 'data_columns' inventada)
Para exibir as alterações para uma determinada coluna ou colunas de atualização para atualização, você precisará juntar a tabela de histórico a si mesma na chave primária e colunas de sequência. Você pode criar uma vista para este propósito, por exemplo:
Edit: Oh wow, as pessoas gostam da minha história na tabela de 6 anos atrás: P
Minha implementação ainda está zumbindo, ficando maior e mais difícil de manejar, eu diria. Eu escrevi visualizações e uma interface de usuário muito boa para olhar o histórico neste banco de dados, mas não acho que tenha sido muito usado. Assim vai.
Para abordar alguns comentários sem ordem específica:
Fiz minha própria implementação em PHP que era um pouco mais envolvente e evitei alguns dos problemas descritos nos comentários (transferência de índices, significativamente. Se você transferir índices exclusivos 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 como está estabelecido seu banco de dados.
Se o relacionamento entre a chave primária e a coluna de revisão parecer incorreto, geralmente significa que a chave composta está danificada de alguma forma. Em algumas raras ocasiões, isso aconteceu e não sabia a causa.
Achei que essa solução tinha um ótimo desempenho, usando gatilhos como faz. Além disso, MyISAM é rápido em inserções, que é tudo o que os triggers 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, realmente, a menos que você tenha problemas significativos acontecendo em outro lugar. Durante todo o tempo em que estive executando o banco de dados MySQL, essa implementação da tabela de histórico nunca foi a causa de nenhum dos (muitos) problemas de desempenho que surgiram.
se você estiver obtendo inserções repetidas, verifique se há consultas do tipo INSERT IGNORE em sua camada de software. 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 a ter em conta, pelo menos.
É importante que os campos da tabela de histórico e da tabela de dados correspondam. Ou melhor, que sua tabela de dados não tenha MAIS colunas do que a tabela de histórico. Caso contrário, as consultas de inserção / atualização / del na tabela de dados irão falhar, 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 colunas fossem adicionadas à tabela de dados. O MySQL tem isso agora? Eu reajo hoje em dia: P
fonte
CREATE TABLE MyDB.data_history as select * from MyDB.data limit 0;
owner
campo, e para atualização eu poderia adicionar umupdatedby
campo, mas para excluir não tenho certeza de como faria isso via triggers. atualizar adata_history
linha com o ID do usuário parece sujo: PVocê pode criar gatilhos para resolver isso. Aqui está um tutorial para fazer isso (link arquivado).
Outra solução seria manter um campo Revisão e atualizar esse campo ao salvar. Você pode decidir que max é a revisão mais recente ou que 0 é a linha mais recente. Isso é contigo.
fonte
Aqui está como resolvemos isso
uma tabela de usuários era parecida com esta
E os requisitos de negócios mudaram e precisávamos verificar todos os endereços e números de telefone anteriores que um usuário já teve. novo esquema se parece com este
Para encontrar o endereço atual de qualquer usuário, procuramos UserData com revisão DESC e LIMIT 1
Para obter o endereço de um usuário entre um determinado período de tempo, podemos usar created_on bewteen (data1, data 2)
fonte
revision=1
doid_user=1
? Primeiro pensei que a sua contagem era,0,2,3,...
mas depois vi que paraid_user=2
a revisão a contagem é0,1, ...
id
eid_user
colunas. Just use a group ID of
id` (ID de usuário) erevision
.MariaDB suporta o controle de versão do sistema desde 10.3, que é o recurso padrão do SQL que faz exatamente o que você deseja: armazena o histórico dos registros da tabela e fornece acesso a ele por meio de
SELECT
consultas. MariaDB é um fork de desenvolvimento aberto do MySQL. Você pode encontrar mais informações sobre o controle de versão do sistema por meio deste link:https://mariadb.com/kb/en/library/system-versioned-tables/
fonte
Por que não simplesmente usar arquivos de log bin? Se a replicação for definida no servidor Mysql e o formato do arquivo binlog for definido como ROW, todas as alterações podem ser capturadas.
Uma boa biblioteca python chamada noplay pode ser usada. Mais informações aqui .
fonte
Apenas meus 2 centavos. Eu criaria uma solução que registrasse exatamente o que mudou, muito semelhante à solução do transiente.
Minha tabela de alterações seria simples:
DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue
1) Quando uma linha inteira é alterada na tabela principal, muitas entradas irão para esta tabela, MAS isso é muito improvável, então não é um grande problema (as pessoas geralmente estão alterando apenas uma coisa) 2) OldVaue (e NewValue se você deseja) tem que ser algum tipo de "qualquer tipo" épico, pois pode ser qualquer dado, pode haver uma maneira de fazer isso com tipos RAW ou apenas usando strings JSON para converter dentro e fora.
Uso mínimo de dados, armazena tudo que você precisa e pode ser usado para todas as tabelas de uma vez. Estou pesquisando isso agora, mas pode acabar sendo meu caminho.
Para criar e excluir, apenas o ID da linha, nenhum campo necessário. Ao excluir um sinalizador na mesa principal (ativo?) Seria bom.
fonte
A maneira direta de fazer isso é criar gatilhos nas tabelas. Defina algumas condições ou métodos de mapeamento. Quando a atualização ou exclusão ocorrer, ele será inserido na tabela de 'alteração' automaticamente.
Mas a maior parte é o que aconteceria se tivéssemos muitas colunas e muitas tabelas. Temos que digitar o nome de cada coluna de cada tabela. Obviamente, é perda de tempo.
Para lidar com isso de forma mais bonita, podemos criar alguns procedimentos ou funções para recuperar nomes de colunas.
Também podemos usar a ferramenta de 3ª parte simplesmente para fazer isso. Aqui, eu escrevo um programa java Mysql Tracker
fonte
create table like table
Acho que replica todas as colunas facilmente