Design do banco de dados para log de auditoria

151

Sempre que preciso criar um novo banco de dados, passo bastante tempo pensando em como devo configurar o esquema do banco de dados para manter um log de auditoria das alterações.

Algumas perguntas já foram feitas aqui sobre isso, mas não concordo que exista uma melhor abordagem única para todos os cenários:

Também deparei com este artigo interessante sobre como manter um log de alterações no banco de dados que tenta listar os prós e contras de cada abordagem. É muito bem escrito e tem informações interessantes, mas tornou minhas decisões ainda mais difíceis.

Minha pergunta é: Existe uma referência que eu possa usar, talvez um livro ou algo como uma árvore de decisão que eu possa me referir para decidir qual caminho devo seguir com base em algumas variáveis ​​de entrada, como:

  • A maturidade do esquema do banco de dados
  • Como os logs serão consultados
  • A probabilidade de que será necessário recriar registros
  • O que é mais importante: desempenho de gravação ou leitura
  • Natureza dos valores que estão sendo registrados (sequência, números, blobs)
  • Espaço de armazenamento disponível

As abordagens que eu sei são:

1. Adicione colunas para data e usuário criados e modificados

Exemplo de tabela:

  • Eu iria
  • value_1
  • value_2
  • value_3
  • Data de criação
  • data modificada
  • criado por
  • modificado por

Contras principais: perdemos o histórico das modificações. Não é possível reverter após a confirmação.

2. Insira apenas tabelas

Exemplo de tabela :

  • Eu iria
  • value_1
  • value_2
  • value_3
  • de
  • para
  • excluído (booleano)
  • do utilizador

Contras principais: Como manter as chaves estrangeiras atualizadas? Espaço enorme necessário

3. Crie uma tabela de histórico separada para cada tabela

Exemplo da tabela de histórico:

  • Eu iria
  • value_1
  • value_2
  • value_3
  • value_4
  • do utilizador
  • excluído (booleano)
  • timestamp

Contras principais: precisa duplicar todas as tabelas auditadas. Se o esquema mudar, será necessário migrar todos os logs também.

4. Crie uma tabela de histórico consolidada para todas as tabelas

Exemplo da tabela de histórico:

  • Nome da tabela
  • campo
  • do utilizador
  • novo valor
  • excluído (booleano)
  • timestamp

Contras principais: poderei recriar os registros (reversão) se necessário com facilidade? A coluna new_value precisa ser uma string enorme para suportar todos os diferentes tipos de coluna.

jbochi
fonte
1
e o que dizer de usar um banco de dados histórico em vez de tabelas?
Jowen 26/01
Talvez você possa verificar o dimensionamento de github.com/airblade/paper_trail
zx1986
É uma má idéia registrar todas as consultas (necessárias) executadas como estão?
Dinushan 17/07

Respostas:

87

Um método usado por algumas plataformas wiki é separar os dados de identificação e o conteúdo que você está auditando. Isso aumenta a complexidade, mas você acaba com uma trilha de auditoria de registros completos, não apenas as listagens de campos que foram editados que precisam ser combinados para fornecer ao usuário uma idéia da aparência do registro antigo.

Por exemplo, se você tivesse uma tabela chamada Oportunidades para rastrear ofertas de vendas, criaria duas tabelas separadas:

Oportunidades
Opportunities_Content (ou algo parecido)

A tabela Oportunidades teria informações que você usaria para identificar exclusivamente o registro e abrigaria a chave primária que você referenciaria para seus relacionamentos de chave estrangeira. A tabela Opportunities_Content conteria todos os campos que seus usuários podem alterar e para os quais você deseja manter uma trilha de auditoria. Cada registro na tabela Conteúdo incluiria sua própria PK e os dados de data de modificação e data de modificação. A tabela Oportunidades incluiria uma referência à versão atual, além de informações sobre quando o registro principal foi criado originalmente e por quem.

Aqui está um exemplo simples:

CREATE TABLE dbo.Page(  
    ID int PRIMARY KEY,  
    Name nvarchar(200) NOT NULL,  
    CreatedByName nvarchar(100) NOT NULL, 
    CurrentRevision int NOT NULL, 
    CreatedDateTime datetime NOT NULL

E o conteúdo:

CREATE TABLE dbo.PageContent(
    PageID int NOT NULL,
    Revision int NOT NULL,
    Title nvarchar(200) NOT NULL,
    User nvarchar(100) NOT NULL,
    LastModified datetime NOT NULL,
    Comment nvarchar(300) NULL,
    Content nvarchar(max) NOT NULL,
    Description nvarchar(200) NULL

Eu provavelmente tornaria a PK da tabela de conteúdo uma chave de várias colunas do PageID e da Revisão, desde que a Revisão fosse um tipo de identidade. Você usaria a coluna Revisão como o FK. Em seguida, você puxa o registro consolidado JOINing assim:

SELECT * FROM Page
JOIN PageContent ON CurrentRevision = Revision AND ID = PageID

Pode haver alguns erros lá em cima ... isso está no topo da minha cabeça. No entanto, você deve ter uma idéia de um padrão alternativo.

Josh Anderson
fonte
10
Em termos de boa abordagem de auditoria, mas para produção, levará muito tempo desenvolvendo uma tabela de auditoria separada para cada tabela no banco de dados, gravando gatilhos para cada tabela para capturar alterações e gravando-a na tabela de auditoria. Além disso, um grande desafio no desenvolvimento de um único relatório de auditoria para todas as tabelas, pois cada tabela de auditoria é diferente em sua estrutura.
asim-ishaq
11
Se escrever e manter scripts para cada tabela é uma preocupação para uma organização que pretende gerenciar um banco de dados auditado, eu recomendaria naturalmente que eles contratem um DBA experiente ou um engenheiro de software altamente flexível e experiente, com experiência adequada na criação de bancos de dados auditados .
Hardryv
1
É correto PageContent.PageIDFK para Page.IDe Page.CurrentRevisionFK para PageContent.Revision? Essa dependência é realmente circular?
2
Eu votei para baixo, uma vez que não aborda as alternativas mencionadas. Ele fornece outra opção, que é uma solução muito específica para um caso de uso muito específico. Mas vejo os méritos do design sugerido
acteon
1
Eu posso pensar em muito poucos campos que eu poderia dizer com confiança não mudarão, então todas as tabelas "principais" para cada entidade acabariam sendo apenas id, revision_id; mais uma mesa de junção, realmente. Isso parece um pouco fedorento para mim. Que vantagem isso tem sobre a abordagem 3 no OP (tabela de histórico por tabela auditada)?
Kenmore
14

Se você estiver usando o SQL Server 2008, provavelmente deverá considerar o Change Data Capture. Isso é novo para 2008 e pode economizar uma quantidade considerável de trabalho.

Randy Minder
fonte
Aqui está o link para as informações de rastreamento de alterações do SQL 2012. msdn.microsoft.com/en-us/library/bb933994.aspx +1 para usar a funcionalidade incorporada, não adianta reinventar a roda.
5283 Chris
4
@ Chris, você já usou isso sozinho? Na verdade, ele rastreia tudo ... mas ser capaz de obter informações úteis com isso é outra história. Não posso usar uma roda de trator para minha bicicleta.
Jowen 25/03
Isso realmente teria sido incrível. Mas se você tiver apenas a edição Standard do SQL Server, como eu, não terá sorte: "A captura de dados alterados está disponível apenas nas edições Enterprise , Developer e Enterprise Evaluation ".
22418 Brad Turek
6

Não conheço nenhuma referência, mas tenho certeza de que alguém escreveu alguma coisa.

No entanto, se o objetivo é simplesmente ter um registro do que aconteceu - o uso mais comum de um log de auditoria -, por que não simplesmente manter tudo:

timestamp
username
ip_address
procedureName (if called from a stored procedure)
database
table
field
accesstype (insert, delete, modify)
oldvalue
newvalue

Presumivelmente, isso é mantido por um gatilho.

Wallyk
fonte
Não conheço nenhuma maneira de obter isso dentro do servidor de banco de dados, mas é claro que isso poderia ser feito de fora com facilidade.
wallyk
5
Parece-me que esse é o mesmo padrão de design da quarta opção mostrada na pergunta original.
givanse
3

Criaremos um pequeno banco de dados de exemplo para um aplicativo de blog. São necessárias duas tabelas:

blog: armazena um ID de postagem exclusivo, o título, o conteúdo e um sinalizador excluído. audit: armazena um conjunto básico de alterações históricas com um ID de registro, o ID da postagem do blog, o tipo de alteração (NEW, EDIT ou DELETE) e a data / hora dessa alteração. O SQL a seguir cria bloge indexa a coluna excluída:

CREATE TABLE `blog` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `title` text,
    `content` text,
    `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`),
    KEY `ix_deleted` (`deleted`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Blog posts';

O SQL a seguir cria a audittabela. Todas as colunas são indexadas e uma chave estrangeira é definida para audit.blog_id que faz referência a blog.id. Portanto, quando EXCLUEM fisicamente uma entrada de blog, seu histórico completo de auditoria também é removido.

CREATE TABLE `audit` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `blog_id` mediumint(8) unsigned NOT NULL,
    `changetype` enum('NEW','EDIT','DELETE') NOT NULL,
    `changetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `ix_blog_id` (`blog_id`),
    KEY `ix_changetype` (`changetype`),
    KEY `ix_changetime` (`changetime`),
    CONSTRAINT `FK_audit_blog_id` FOREIGN KEY (`blog_id`) REFERENCES `blog` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
ajit
fonte
2

Eu acho que não há nada como uma árvore de decisão. Uma vez que alguns dos prós e contras (ou requisitos) não são realmente contáveis. Como você mede a maturidade, por exemplo?

Portanto, basta alinhar os requisitos de negócios para o log de auditoria. Tente prever como esses requisitos podem mudar no futuro e gerar seus requisitos técnicos. Agora você pode compará-lo com os prós e contras e escolher a opção certa / melhor.

E tenha certeza, não importa como você decida, sempre haverá alguém que acha que você tomou a decisão errada. No entanto, você fez sua lição de casa e justifica sua decisão.

Peter Schuetze
fonte