Como as exclusões devem ser tratadas no banco de dados?

44

Eu gostaria de implementar um recurso "anular exclusão" em um aplicativo Web, para que um usuário possa mudar de idéia e recuperar um registro excluído. Pensamentos sobre como implementar isso? Algumas opções que considerei estão na verdade excluindo o registro em questão e armazenando as alterações em uma tabela de auditoria separada ou não excluindo o registro e usando uma coluna booleana "excluída" para marcá-la como excluída. A última solução exigiria lógica de aplicativo adicional para ignorar os registros "excluídos" em circunstâncias normais, mas facilitaria muito a implementação da recuperação dos registros no lado do aplicativo.

Abie
fonte
Esqueci de mencionar que, no segundo caso, os registros sinalizados precisariam ser excluídos ou movidos após um período de tempo decorrido razoável.
Abie
Qual banco de dados você está usando?
Evan Carroll
Tabela Temporal é a melhor solução para o SQL Server 2016 e superior.
Sameer

Respostas:

37

Sim, eu definitivamente optaria pela segunda opção, mas acrescentaria mais um campo por campo de data.

Então você adiciona:

delete       boolean
delete_date  timestamp

Isso permitiria que você desse um tempo para a ação de exclusão.

Se o tempo for inferior a uma hora, é possível cancelar a exclusão.

Para realmente excluir a entrada excluída, basta criar um procedimento armazenado que limpará todas as entradas com a exclusão definida como true e o tempo maior que uma hora e colocá-lo como uma guia cron que é executada a cada 24 horas

A hora é apenas um exemplo.

Spredzy
fonte
Como alternativa, você pode ter outro sinalizador - cleanedou algo assim - que indica que os dados associados a esse registro foram excluídos de forma adequada e abrangente. O registro pode ser cancelado, a menos que cleanedseja verdadeiro, caso em que é irrecuperável.
Gaurav
14
Essa é a abordagem comum. Eu costumo usar um campo deleted_atcontendo a semântica do deletebooleano e o delete_datecarimbo de data e hora. Se deleted_atis NULLhandle, o caso deleteé FALSEe delete_dateé NULL, deleted_atcontendo um registro de data e hora, o caso deleteé TRUEe delete_datecontém um registro de data e hora, economizando tempo, armazenamento e lógica de aplicativo.
Julien
1
Eu gosto do campo booleano e de data. Dependendo de como você implementa a lógica de exclusão, você pode até ter uma tabela distinta que contém a data e a chave exclusiva do registro que foi "excluído". Os procedimentos armazenados facilitam isso. Leva o espaço adicional por linha necessário até 1 bit vs. 8+. Você também poderá relatar exclusões por dia sem tocar na tabela de origem.
AndrewSQL 19/01
Nota: delete é uma palavra reservada no MySQL.
Jason Rikard
Lembre-se de que um índice filtrado em seu deletedcampo pode melhorar muito o desempenho quando você está consultando linhas não eliminadas
Ross Presser
21

Em nossos aplicativos, na verdade, não excluímos nada a pedido dos usuários (nossos clientes estão em ambientes regulamentados onde a exclusão de qualquer coisa pode potencialmente levar a problemas legais).

Mantemos as versões mais antigas em uma tabela de auditoria separada (portanto, para a tabela some_table, onde também é uma tabela chamada some_table_audit), que é idêntica, além de ter um identificador de versão adicional (um carimbo de data e hora se o seu banco de dados suportar valores de tempo suficientemente granulares, um número de versão inteiro) ou UUID, que é uma chave estrangeira para uma tabela de auditoria geral, etc.) e atualize a tabela de auditoria automaticamente por acionador (para que não seja necessário tornar todo o código que atualiza os registros cientes dos requisitos de auditoria).

Deste jeito:

  • a operação de exclusão é apenas uma exclusão simples - não é necessário adicionar nenhum código extra a isso (embora você possa querer registrar quem solicitou quais linhas serão excluídas, mesmo que não sejam realmente excluídas)
  • inserções e atualizações são igualmente simples
  • você pode implementar a remoção da exclusão ou a reversão retornando a linha "normal" para uma versão antiga (o gatilho de auditoria será acionado novamente, para que a tabela da trilha de auditoria também reflita essa alteração)
  • você pode revisar ou reverter para qualquer versão anterior e não apenas cancelar a exclusão da última
  • você não precisa adicionar "está marcado como excluído?" verifica todos os pontos de código que se referem à tabela em questão, ou a lógica "atualizar cópia de auditoria" para cada ponto de código que exclui / atualiza linhas (embora você precise decidir o que fazer com as linhas excluídas na tabela de auditoria: temos um sinalizador excluído / não para cada versão existente, para que não haja um buraco no histórico se os registros forem excluídos e posteriormente eliminados)
  • manter as cópias de auditoria em uma tabela separada significa que você pode particioná-las em diferentes grupos de arquivos facilmente.

Se estiver usando um registro de data e hora em vez de (ou também) um número de versão inteiro, você pode usá-lo para excluir as cópias mais antigas após um período de tempo definido, se necessário. Mas o espaço em disco é relativamente barato atualmente, portanto, a menos que tenhamos motivos para descartar dados antigos (ou seja, regulamentos de proteção de dados que dizem que você deve excluir dados do cliente após X meses / anos), não o faríamos.


Essa resposta existe há alguns anos e algumas coisas importantes que podem afetar esse tipo de planejamento foram alteradas desde então. Não vou entrar em detalhes maciços, mas com orgulho para as pessoas que estão lendo isso hoje:

  • O SQL Server 2016 introduziu "tabelas temporais com versão do sistema", que executam grande parte desse trabalho para você e, além disso, como um bom açúcar sintático é fornecido para facilitar a construção e a manutenção de consultas históricas, e coordenam um subconjunto de alterações de esquema entre os tabelas de base e histórico. Eles não estão isentos de advertências, mas são uma ferramenta poderosa para esse tipo de objetivo. Recursos semelhantes também estão disponíveis em outros sistemas de banco de dados.

  • Alterações na legislação de proteção de dados, principalmente a introdução do RGPD, podem alterar significativamente a questão de quando os dados devem ser excluídos. Você deve ponderar o saldo de não excluir dados que possam ser úteis (ou, de fato, legalmente exigidos) para fins de auditoria em uma data posterior, contra a necessidade de respeitar os direitos das pessoas (de maneira geral e especificamente especificada na legislação relevante) ao considerar seus projetos. Isso pode ser um problema nas tabelas temporais com versão do sistema, pois você não pode modificar o histórico para limpar dados pessoais sem alterações de curto prazo no esquema para desativar o rastreamento do histórico enquanto faz alterações.

David Spillett
fonte
Como você lida com a exclusão e renomeação de colunas? Definir tudo como anulável?
Stijn
1
@Stijn: Não é sempre que as estruturas são alteradas para que não surjam muito. Colunms geralmente nunca são removidos depois de existirem na produção - se eles deixarem de ser usados, elimine quaisquer restrições que os impediriam de NULL (ou adicione padrões para lidar com restrições usando um "valor mágico", embora isso pareça mais sujo) e pare de se referir a eles em outro código. Para renomear: adicione novo, pare de usar o antigo e copie dados do antigo para o novo, se necessário. Se você renomear colunas, verifique se a mesma alteração foi feita nas tabelas base e de auditoria ao mesmo tempo.
David Spillett
9

Com uma coluna excluída booleana, você começará a ter problemas se sua tabela começar a crescer e ficar realmente grande. Sugiro que você mova as colunas excluídas uma vez por semana (mais ou menos, dependendo de suas especificações) para uma tabela diferente. Dessa forma, você tem uma boa e pequena tabela ativa e uma grande, contendo todos os registros coletados ao longo do tempo.

poelinca
fonte
7

Eu iria com a mesa separada. O Ruby on Rails possui um acts_as_versionedplug - in, que basicamente salva uma linha em outra tabela com o postfix _versionantes de atualizá -lo. Embora você não precise desse comportamento exato, ele também deve funcionar no seu caso (copiar antes de excluir).

Como o @Spredzy, eu também recomendo adicionar uma delete_datecoluna para poder limpar programaticamente os registros que não foram restaurados após X horas / dias / qualquer coisa.

Michael Kohl
fonte
4

A solução que usamos internamente para esse assunto é ter uma coluna de status com alguns valores codificados para alguns estados específicos do objeto: Excluídos, Ativos, Inativos, Abertos, Fechados, Bloqueados - cada status com algum significado usado no aplicativo. Do ponto de vista do banco de dados, não removemos objetos, apenas alteramos o status e mantemos o histórico de cada alteração na tabela de objetos.

Marian
fonte
3

Quando você diz que "A última solução exigiria uma lógica de aplicativo adicional para ignorar os registros 'excluídos'", a solução simples é ter uma exibição que os filtre.

Peter Taylor
fonte
Não é apenas uma questão de visão. Quaisquer operações que estão sendo executadas no aparelho teriam que excluir os registros "excluídos".
Abie
2

Semelhante ao sugerido pela Spredzy, usamos um campo de carimbo de data / hora para exclusão em todos os nossos aplicativos. O booleano é supérfluo, pois o registro de data e hora está sendo definido indica que o registro foi excluído. Dessa forma, nosso PDO sempre adiciona AND (deleted IS NULL OR deleted = 0)às instruções de seleção, a menos que o modelo solicite explicitamente os registros excluídos sejam incluídos.

Atualmente, não coletamos lixo em nenhuma tabela, exceto as que contêm blobs ou textos; o espaço é trivial se os registros estiverem bem normalizados e a indexação do deletedcampo causa um impacto limitado na velocidade de seleção.

Bryan Agee
fonte
0

Como alternativa, você pode colocar o ônus sobre os usuários (e desenvolvedores) e seguir uma sequência de 'Tem certeza?', 'Tem certeza?' e 'Você tem certeza absoluta, boa e verdadeira?' perguntas antes que o registro seja excluído. Ligeiramente faceta, mas vale a pena considerar.

YaHozna
fonte
0

Estou acostumado a ver linhas da tabela com colunas como 'DeletedDate' nelas e não gosto delas. A própria noção de 'excluído' é que a entrada não deveria ter sido feita em primeiro lugar. Praticamente, eles não podem ser removidos do banco de dados, mas eu não os quero com meus dados quentes. Linhas excluídas logicamente são, por definição, dados frios, a menos que alguém queira especificamente ver dados excluídos.

Além disso, toda consulta escrita deve excluí-los especificamente e os índices precisam considerá-los também.

O que eu gostaria de ver é uma alteração no nível da arquitetura do banco de dados e no nível do aplicativo: crie um esquema chamado 'excluído'. Cada tabela definida pelo usuário tem um equivalente idêntico no esquema 'excluído' com um campo extra contendo metadados - o usuário que a excluiu e quando. Chaves estrangeiras estão exigindo a criação.

Em seguida, exclusões tornam-se exclusões de inserção. Primeiro, a linha a ser excluída é inserida na sua contraparte de esquema 'excluída'. A linha em questão na tabela principal pode ser excluída. Lógica extra, no entanto, precisa ser adicionada em algum lugar ao longo da linha. Violações de chave estrangeira podem ser tratadas.

Chaves estrangeiras devem ser manuseadas adequadamente. É uma prática recomendada excluir uma linha logicamente, mas cujo principal / exclusivo tenha colunas em outras tabelas que se referem a ela. Isso não deveria acontecer de qualquer maneira. Um trabalho regular pode remover linhas de viúva (linhas cujas chaves primárias não têm referências em outras tabelas, apesar da presença de uma chave estrangeira. No entanto, essa é a lógica de negócios.

O benefício geral é a redução de metadados na tabela e a melhoria de desempenho que ela traz. A coluna 'deleteDate' diz que essa linha não deveria estar aqui, mas, por uma questão de conveniência, deixamos lá e deixamos a consulta SQL lidar com isso. Se uma cópia da linha excluída for mantida em um esquema 'excluído', a tabela principal com os dados ativos terá uma porcentagem maior de dados ativos (supondo que eles sejam arquivados em tempo hábil) e menos colunas de metadados desnecessárias. Os índices e as consultas não precisam mais considerar esse campo. Quanto menor o tamanho da linha, mais linhas podem ser ajustadas em uma página, mais rápido o SQL Server pode funcionar.

A principal desvantagem é o tamanho da operação. Agora existem duas operações em vez de uma, além da lógica extra e tratamento de erros. Isso pode levar a mais bloqueios do que a atualização de uma única coluna seria necessária. A transação mantém bloqueios na tabela por mais tempo e há duas tabelas envolvidas. Excluir dados de produção, pelo menos na minha experiência, é algo raramente feito. Mesmo assim, em uma das tabelas principais, 7,5% dos quase 100 milhões de entradas tem uma entrada na coluna 'Data excluída'.

Como resposta à pergunta, o aplicativo teria que estar ciente de 'cancelar a exclusão'. Basta fazer o mesmo na ordem inversa: insira a linha do esquema 'excluído' na tabela principal e, em seguida, exclua a linha do esquema 'excluído. Novamente, é necessária alguma lógica extra e manipulação de erros para garantir erros, problemas com chaves estrangeiras e similares.

Sean Redmond
fonte