¿Cómo se deben manejar las eliminaciones en la base de datos?

44

Me gustaría implementar una función de "recuperación" en una aplicación web para que un usuario pueda cambiar de opinión y recuperar un registro eliminado. ¿Pensamientos sobre cómo implementar esto? Algunas opciones que he considerado son en realidad eliminar el registro en cuestión y almacenar los cambios en una tabla de auditoría separada, o no eliminar el registro y usar una columna booleana "eliminada" para marcarlo como eliminado. La última solución requeriría una lógica de aplicación adicional para ignorar los registros "eliminados" en circunstancias normales, pero facilitaría mucho la implementación de la recuperación de los registros en el lado de la aplicación.

Abie
fuente
Olvidé mencionar que en el segundo caso, los registros marcados tendrían que eliminarse o moverse después de un período de tiempo razonable.
Abie
¿Qué base de datos está utilizando?
Evan Carroll
Temporal Table es la mejor solución para SQL Server 2016 y superior.
Sameer

Respuestas:

37

Sí, definitivamente elegiría la segunda opción, pero agregaría un campo más a un campo de fecha.

Entonces agregas:

delete       boolean
delete_date  timestamp

Te dejaría un tiempo para la acción de recuperación.

Si el tiempo es inferior a una hora, se puede recuperar.

Para eliminar realmente la entrada eliminada, simplemente cree un procedimiento almacenado que limpiará cada entrada con la eliminación establecida en verdadero y el tiempo superior a una hora y póngalo como una pestaña cron que se ejecuta cada 24 horas

La hora es solo un ejemplo.

Spredzy
fuente
Alternativamente, podría tener otra bandera cleaned, o algo así, que indique que los datos asociados con este registro se han eliminado de manera adecuada y completa. El registro puede recuperarse a menos que cleanedsea ​​verdadero, en cuyo caso es irrecuperable.
Gaurav
14
Este es el enfoque común. Usualmente uso un campo que deleted_atcontiene tanto la semántica del deletebooleano como la delete_datemarca de tiempo. Si se deleted_attrata de NULLun caso, el caso deletees FALSEy delete_datees NULL, deleted_atcontiene una marca de fecha y hora, el caso deletees TRUEy delete_datecontiene una marca de tiempo, lo que le ahorra tiempo, almacenamiento y lógica de aplicación.
Julien
1
Me gusta el campo booleano y de fecha. Dependiendo de cómo implemente la lógica de eliminación, incluso podría tener una tabla distinta que contenga la fecha y la clave única para el registro que se "eliminó". Los procedimientos almacenados facilitan esto. Se necesita el espacio adicional por fila requerido hasta 1 bit frente a 8+. También podría informar sobre eliminaciones por día sin tocar la tabla de origen.
AndrewSQL
Nota: eliminar es una palabra reservada en MySQL.
Jason Rikard
Recuerde que un índice filtrado en su deletedcampo puede mejorar enormemente el rendimiento cuando consulta filas no eliminadas
Ross Presser
21

En nuestras aplicaciones no lo hacemos realmente nada de borrado en una solicitan los usuarios de todos modos (nuestros clientes están en entornos regulados en los que borrar nada puede potencialmente conducir a problemas legales).

Mantenemos las versiones anteriores en una tabla de auditoría separada (por lo que para la tabla some_table donde también hay una tabla llamada some_table_audit) que es idéntica aparte de tener un identificador de versión adicional (una marca de tiempo si su DB admite valores de tiempo lo suficientemente granulares, un número de versión entero) o UUID que es una clave foránea para una tabla de auditoría general, o así sucesivamente), y actualice la tabla de auditoría automáticamente por disparador (por lo que no es necesario que todo el código que actualiza los registros conozca el requisito de auditoría).

De esta manera:

  • la operación de eliminación es simplemente una eliminación simple: no es necesario agregar ningún código adicional (aunque es posible que desee registrar quién solicitó qué filas se eliminarán, incluso si en realidad no se eliminan)
  • las inserciones y actualizaciones son igualmente simples
  • puede implementar recuperar o revertir simplemente devolviendo la fila "normal" a una versión anterior (el disparador de auditoría se activará nuevamente, por lo que la tabla de seguimiento de auditoría también reflejará este cambio)
  • puede ofrecer la oportunidad de revisar o volver a cualquier versión anterior, no solo recuperar la última
  • no tiene que agregar "¿está marcado como eliminado?" verifica cada punto de código que se refiere a la tabla en cuestión, o la lógica de "actualizar copia de auditoría" a cada punto de código que elimina / actualiza filas (aunque debe decidir qué hacer con las filas eliminadas en la tabla de auditoría: tenemos un eliminado / no marcado para cada versión allí, por lo que no hay un agujero en el historial si los registros se eliminan y luego se eliminan)
  • mantener las copias de auditoría en una tabla separada significa que puede dividirlas fácilmente en diferentes grupos de archivos.

Si usa una marca de tiempo en lugar de (o además de) un número de versión entero, puede usar esto para eliminar las copias más antiguas después de un período de tiempo establecido si es necesario. Pero el espacio en disco es relativamente barato en estos días, por lo que a menos que tengamos razones para descartar datos antiguos (es decir, regulaciones de protección de datos que dicen que debe eliminar los datos del cliente después de X meses / años), no lo haríamos.


Esta respuesta ha existido hace unos años y desde entonces un par de cosas clave que podrían afectar este tipo de planificación han cambiado. No entraré en detalles masivos, pero brevemente para el beneficio de las personas que leen esto hoy:

  • SQL Server 2016 introdujo "tablas temporales versionadas por el sistema" que hacen mucho de este trabajo por usted, y más además, ya que se proporciona un poco de azúcar sintáctica agradable para hacer que las consultas históricas sean más fáciles de construir y mantener, y coordinan un subconjunto de cambios de esquema entre tablas de base e historia. No carecen de advertencias, pero son una herramienta poderosa para este tipo de propósito. Características similares también están disponibles en otros sistemas de base de datos.

  • Los cambios en la legislación de protección de datos, en particular la introducción de GDPR, pueden alterar significativamente la cuestión de cuándo los datos deben eliminarse por completo. Debe sopesar el saldo de no eliminar datos que podrían ser útiles (o, de hecho, legalmente requeridos) para fines de auditoría en una fecha posterior en contra de la necesidad de respetar los derechos de las personas (tanto en general como específicamente establecido en la legislación pertinente) al considerar tus diseños Esto puede ser un problema con las tablas temporales con versión del sistema, ya que no puede modificar el historial para purgar los datos personales sin cambios de esquema a corto plazo para desactivar el seguimiento del historial mientras realiza cambios.

David Spillett
fuente
¿Cómo manejas la eliminación y el cambio de nombre de las columnas? ¿Establecer todo en anulable?
Stijn
1
@Stijn: No es frecuente que las estructuras cambien, por lo que no surge mucho. Los colunms generalmente nunca se eliminan una vez que han existido en la producción; si dejan de usarse, simplemente elimine cualquier restricción que los detenga en NULL (o agregue valores predeterminados para lidiar con las restricciones mediante el uso de un "valor mágico", aunque eso se siente más sucio) y deja de referirte a ellos en otro código. Para cambiar el nombre: agregue nuevo, deje de usar viejo y copie datos de viejo a nuevo si es necesario. Si cambia el nombre de las columnas, asegúrese de realizar el mismo cambio en las tablas base y de auditoría al mismo tiempo.
David Spillett
9

Con una columna borrada booleana, comenzará a tener problemas si su tabla comienza a crecer y se hace realmente grande. Le sugiero que mueva las columnas eliminadas una vez por semana (más o menos según sus especificaciones) a una tabla diferente. De esa manera, tiene una bonita y pequeña tabla activa y una grande que contiene todos los registros recopilados con el tiempo.

poelinca
fuente
7

Yo iría con la mesa separada. Ruby on Rails tiene un acts_as_versionedcomplemento, que básicamente guarda una fila en otra tabla con el postfix _versionantes de actualizarlo. Si bien no necesita ese comportamiento exacto, también debería funcionar para su caso (copie antes de eliminar).

Al igual que @Spredzy, también recomendaría agregar una delete_datecolumna para poder purgar mediante programación los registros que no se han restaurado después de X horas / días / lo que sea.

Michael Kohl
fuente
4

La solución que utilizamos internamente para este asunto es tener una columna de estado con algunos valores codificados para algunos estados específicos del objeto: Eliminado, Activo, Inactivo, Abierto, Cerrado, Bloqueado: cada estado con algún significado utilizado en la aplicación. Desde el punto de vista de db, no eliminamos objetos, simplemente cambiamos el estado y mantenemos el historial de cada cambio en la tabla de objetos.

Mariana
fuente
3

Cuando dice que "la última solución requeriría una lógica de aplicación adicional para ignorar los registros 'eliminados'", la solución simple es tener una vista que los filtre.

Peter Taylor
fuente
No es solo una cuestión de vista. Cualquier operación que se realice en el conjunto debería excluir los registros "eliminados".
Abie
2

Similar a lo que sugirió Spredzy, utilizamos un campo de marca de tiempo para eliminarlo en todas nuestras aplicaciones. El booleano es superfluo, ya que la marca de tiempo que se establece indica que el registro se ha eliminado. De esta manera, nuestro PDO siempre agrega AND (deleted IS NULL OR deleted = 0)a las declaraciones select, a menos que el modelo solicite explícitamente que se incluyan registros eliminados.

Actualmente no recolectamos basura en ninguna tabla excepto que contenga blobs o textos; el espacio es trivial si los registros están bien normalizados, y la indexación del deletedcampo tiene un impacto limitado en la velocidad de selección.

Bryan Agee
fuente
0

Alternativamente, puede colocar la responsabilidad en los usuarios (y desarrolladores) e ir con una secuencia de '¿Está seguro?', '¿Está definitivamente seguro?' y '¿Estás absolutamente, bien y verdaderamente seguro?' preguntas antes de que se elimine el registro. Ligeramente gracioso pero vale la pena considerarlo.

YaHozna
fuente
0

Estoy acostumbrado a ver filas de tablas con columnas como 'DeletedDate' en ellas y no me gustan. La noción misma de 'eliminado' es que la entrada no debería haberse hecho en primer lugar. Prácticamente, no se pueden eliminar de la base de datos, pero no los quiero con mis datos activos. Las filas eliminadas lógicamente son, por definición, datos fríos a menos que alguien específicamente quiera ver los datos eliminados.

Además, cada consulta que se escriba tiene que excluirlos específicamente y los índices también deben considerarlos.

Lo que me gustaría ver es un cambio en el nivel de arquitectura de la base de datos y el nivel de la aplicación: cree un esquema llamado 'eliminado'. Cada tabla definida por el usuario tiene un equivalente idéntico en el esquema 'eliminado' con un campo adicional que contiene metadatos: el usuario que lo eliminó y cuándo. Las claves foráneas requieren ser creadas.

A continuación, elimina se convierte en insertar-eliminar. Primero, la fila que se eliminará se inserta en su contraparte del esquema 'eliminado'. La fila en cuestión en la tabla principal se puede eliminar. Sin embargo, es necesario agregar lógica adicional en algún lugar a lo largo de la línea. Las infracciones de claves externas pueden ser manejadas.

Las claves foráneas deben manejarse adecuadamente. Es una mala práctica tener una fila eliminada lógicamente pero cuyo primario / único tiene columnas en otras tablas que se refieren a él. Esto no debería suceder de todos modos. Un trabajo normal puede eliminar filas de viudas (filas cuyas claves principales no tienen referencias en otras tablas a pesar de la presencia de una clave externa. Sin embargo, esto es lógica empresarial.

El beneficio general es la reducción de metadatos en la tabla y la mejora del rendimiento que aporta. La columna 'deletedDate' dice que esta fila en realidad no debería estar aquí, pero, por conveniencia, la dejamos allí y dejamos que la consulta SQL la maneje. Si una copia de la fila eliminada se mantiene en un esquema 'eliminado', entonces la tabla principal con los datos activos tiene un mayor porcentaje de datos activos (suponiendo que se archiven de manera oportuna) y menos columnas de metadatos innecesarios. Los índices y consultas ya no necesitan considerar este campo. Cuanto más corto sea el tamaño de la fila, más filas se pueden ajustar en una página, más rápido puede funcionar SQL Server.

La principal desventaja es el tamaño de la operación. Ahora hay dos operaciones en lugar de una, así como la lógica adicional y el manejo de errores. Puede llevar a más bloqueo que actualizar una sola columna, de lo contrario tomaría. La transacción mantiene bloqueos en la tabla por más tiempo y hay dos tablas involucradas. Eliminar datos de producción, al menos en mi experiencia, es algo que rara vez se hace. Aún así, en una de las tablas principales, el 7,5% de casi 100 millones de entradas tiene una entrada en la columna 'Fecha eliminada'.

Como respuesta a la pregunta, la aplicación debería ser consciente de 'recuperar'. Simplemente tendría que hacer lo mismo en orden inverso: inserte la fila del esquema 'eliminado' en la tabla principal y luego elimine la fila del esquema 'eliminado'. Nuevamente, se necesita algo más de lógica y manejo de errores para garantizar que se eviten errores, problemas con claves externas y similares.

Sean Redmond
fuente