Manejo de usuarios eliminados: ¿tabla separada o la misma?

19

El escenario es que tengo un conjunto de usuarios en expansión, y a medida que pasa el tiempo, los usuarios cancelarán sus cuentas que actualmente marcamos como 'eliminadas' (con un indicador) en la misma tabla.

Si los usuarios con la misma dirección de correo electrónico (así es como los usuarios inician sesión) desean crear una cuenta nueva, pueden registrarse nuevamente, pero se crea una cuenta NUEVA. (Tenemos identificadores únicos para cada cuenta, por lo que las direcciones de correo electrónico pueden duplicarse entre las en vivo y las eliminadas).

Lo que he notado es que en todo nuestro sistema, en el curso normal de las cosas, consultamos constantemente la tabla de usuarios para verificar que el usuario no se elimine, mientras que lo que estoy pensando es que no necesitamos hacer eso en absoluto ... ! [Aclaración1: por 'consultar constantemente', quise decir que tenemos consultas que son como: '... DE usuarios DONDE está eliminado = "0" Y ...'. Por ejemplo, es posible que tengamos que buscar a todos los usuarios registrados para todas las reuniones en una fecha en particular, por lo que en ESA consulta, también tenemos usuarios de FROM WHERE isdeleted = "0": ¿esto aclara mi punto?]

(1) continue keeping deleted users in the 'main' users table
(2) keep deleted users in a separate table (mostly required for historical
    book-keeping)

¿Cuáles son los pros y los contras de cualquiera de los enfoques?

Alan Beats
fuente
¿Por qué motivos mantienes a los usuarios?
keppla
2
Esto se llama eliminación suave. Consulte también Eliminar registros de la base de datos unpermenantley (eliminación suave)
Sjoerd
@keppla - menciona que: "contabilidad histórica".
ChrisF
@ChrisF: estaba interesado en el alcance: ¿quiere mantener libros solo de los usuarios, o todavía hay algunos datos adjuntos (comentarios de eG, pagos, etc.)
Keppla
Puede ser útil dejar de pensar en ellos como eliminados (lo cual no es cierto) y comenzar a pensar en su cuenta como cancelada (lo cual es cierto).
Mike Sherrill 'Cat Recall'

Respuestas:

13

(1) continúe manteniendo usuarios eliminados en la tabla de usuarios 'principal'

  • Pros: consultas más simples en todos los casos
  • Contras: puede degradar el rendimiento con el tiempo, si hay una gran cantidad de usuarios

(2) mantenga a los usuarios eliminados en una tabla separada (principalmente requerida para la contabilidad histórica)

Puede utilizar, por ejemplo, un activador para mover automáticamente a los usuarios eliminados a la tabla de historial.

  • Pros: mantenimiento más simple para la tabla de usuarios activos, rendimiento estable
  • Contras: necesita diferentes consultas para la tabla de historial; sin embargo, dado que se supone que la mayor parte de la aplicación no está interesada en eso, este efecto negativo probablemente sea limitado
Péter Török
fuente
11
Una tabla de particiones (en IsDeleted) eliminaría los problemas de rendimiento al usar una sola tabla.
Ian
1
@Ian a menos que cada consulta se proporcione con IsDeleted como criterio de consulta (que parece no estar en la pregunta original), la partición puede incluso causar una degradación del rendimiento.
Adrian Shum
1
@Adrian, estaba asumiendo que las consultas más comunes serían en el momento de iniciar sesión y que solo ninguno de los usuarios eliminados podría iniciar sesión.
Ian
1
Utilice una vista indizada en isdeleted si se convierte en un problema de rendimiento y desea el beneficio de una sola tabla.
JeffO
10

Recomiendo usar la misma tabla. La razón principal es la integridad de los datos. Lo más probable es que haya muchas tablas con relaciones dependiendo de los usuarios. Cuando se elimina un usuario, no desea dejar esos registros huérfanos.
Tener registros huérfanos hace que sea más difícil hacer cumplir las restricciones y hace que sea más difícil buscar información histórica. El otro comportamiento a tener en cuenta es cuando un usuario proporciona un correo electrónico usado si desea que recupere todos sus registros antiguos. Esto funcionaría automáticamente mediante la eliminación suave. En cuanto a la codificación, por ejemplo, en mi aplicación actual de c # linq, la cláusula where deleted = 0 se agrega automáticamente al final de todas las consultas

Andrey
fuente
7

"Lo que he notado es que en todo nuestro sistema, en el curso normal de las cosas, consultamos constantemente la tabla de usuarios para verificar que el usuario no se elimine"

Esto me da un mal olor a diseño. Deberías ocultar ese tipo de lógica. Por ejemplo, debe UserServiceproporcionar un método isValidUser(userId)para usar "en todo el sistema", en lugar de hacer algo como:

"obtener el registro de usuario, verificar si el usuario está marcado como eliminado".

Su forma de almacenar usuarios eliminados no debería afectar la lógica empresarial.

Con este tipo de encapsulación, el argumento anterior ya no debería afectar el enfoque de su persistencia. Entonces puede concentrarse más en los pros y los contras relacionados con la persistencia misma.

Las cosas a considerar incluyen:

  • ¿Cuánto tiempo debe purgarse realmente el registro eliminado?
  • ¿Cuál es la proporción de registros eliminados?
  • ¿Habrá un problema para la integridad referencial (p. Ej., El usuario se deriva de otra tabla) si realmente lo elimina de la tabla?
  • ¿Considera volver a abrir el usuario?

Normalmente tomaría una forma combinada:

  1. Marque el registro como eliminado (para mantenerlo como requisito funcional, como reabrir ac o verificar ac cerrado recientemente).
  2. Después de un período predefinido, mueva el registro eliminado a la tabla de archivo (para fines de contabilidad).
  3. Purgue después de un período de archivo predefinido.
Adrian Shum
fuente
1
[Aclaración1: por 'consultar constantemente', quise decir que tenemos consultas que son como: '... DE usuarios DONDE está eliminado = "0" Y ...'. Por ejemplo, es posible que tengamos que buscar a todos los usuarios registrados para todas las reuniones en una fecha en particular, por lo tanto, en ESA consulta, también tenemos usuarios de FROM WHERE isdeleted = "0": ¿esto aclara mi punto?] @Adrian
Alan Beats
Sí, mucho más claro. :) Si estoy haciendo eso, prefiero hacerlo como cambio de estado del usuario, en lugar de verlo como eliminación física / lógica. Aunque la cantidad de código no se reducirá ("and isDeleted = '0'" vs 'y "state <>' TERMINATED '") pero todo se verá mucho más razonable, y es normal tener un estado de usuario diferente también. La purga periódica de usuarios TERMINADOS también se puede realizar, como se sugirió en mi respuesta anterior)
Adrian Shum
5

Para responder adecuadamente a esta pregunta, primero debe decidir: ¿Qué significa "eliminar" en el contexto de este sistema / aplicación?

Para responder a esa pregunta, debe responder otra pregunta más: ¿por qué se eliminan los registros?

Existen varias buenas razones por las cuales un usuario podría necesitar eliminar datos. Por lo general, encuentro que hay exactamente una razón (por tabla) por la cual una eliminación podría ser necesaria. Algunos ejemplos son:

  • Para reclamar espacio en disco;
  • Se requiere eliminación de acuerdo con la política de retención / privacidad;
  • Datos corruptos / irremediablemente incorrectos, más fáciles de eliminar y regenerar que de reparar.
  • La mayoría de las filas se eliminan, por ejemplo, una tabla de registro limitada a X registros / días.

También hay algunas razones muy pobres para la eliminación forzada (más sobre las razones para esto más adelante):

  • Para corregir un error menor. Esto generalmente subraya la pereza del desarrollador y una interfaz de usuario hostil.
  • Para "anular" una transacción (por ejemplo, una factura que nunca debería haberse facturado).
  • Porque tu puedes .

¿Por qué, preguntas, es realmente tan importante? ¿Qué hay de malo con el bueno DELETE?

  • En cualquier sistema, incluso vinculado remotamente al dinero, la eliminación dura viola todo tipo de expectativas contables, incluso si se traslada a una tabla de archivo / lápida. La forma correcta de manejar esto es un evento retroactivo .
  • Las tablas de archivo tienden a diferir del esquema en vivo. Si olvida incluso una columna o cascada recién agregada, acaba de perder esos datos de forma permanente.
  • La eliminación dura puede ser una operación muy costosa, especialmente con cascadas . Mucha gente no se da cuenta de que en cascada más de un nivel (o en algunos casos cualquier cascada, dependiendo de DBMS) dará lugar a operaciones a nivel de registro en lugar de operaciones de conjuntos.
  • La eliminación frecuente y repetida acelera el proceso de fragmentación del índice.

Entonces, la eliminación suave es mejor, ¿verdad? No en realidad no:

  • Configurar cascadas se vuelve extremadamente difícil. Casi siempre terminas con lo que le parece al cliente como filas huérfanas.
  • Solo puedes rastrear una eliminación. ¿Qué pasa si la fila se elimina y se recupera varias veces?
  • El rendimiento de lectura sufre, aunque esto puede mitigarse un poco con particiones, vistas y / o índices filtrados.
  • Como se indicó anteriormente, en realidad puede ser ilegal en algunos escenarios / jurisdicciones.

La verdad es que ambos enfoques están equivocados. Eliminar está mal. Si realmente está haciendo esta pregunta, significa que está modelando el estado actual en lugar de las transacciones. Esta es una mala, mala práctica en tierra de bases de datos.

Udi Dahan escribió sobre esto en Don't Delete - Just Don't . Hay siempre algún tipo de tarea, transacción, la actividad , o (mi término preferido) evento que en realidad representa el "borrado". Está bien si posteriormente desea desnormalizar en una tabla de "estado actual" para el rendimiento, pero hágalo después de haber definido el modelo transaccional, no antes.

En este caso tienes "usuarios". Los usuarios son esencialmente clientes. Los clientes tienen una relación comercial con usted. Esa relación no se desvanece simplemente porque cancelaron su cuenta. Lo que realmente está sucediendo es:

  • El cliente crea una cuenta
  • El cliente cancela la cuenta
  • El cliente renueva la cuenta
  • El cliente cancela la cuenta
  • ...

En todos los casos, es el mismo cliente y posiblemente la misma cuenta (es decir, cada renovación de cuenta es un nuevo acuerdo de servicio). Entonces, ¿por qué estás eliminando filas? Esto es muy fácil de modelar:

+-----------+       +-------------+       +-----------------+
| Account   | --->* | Agreement   | --->* | AgreementStatus |
+-----------+       +-------------+       +----------------+
| Id        |       | Id          |       | AgreementId     |
| Name      |       | AccountId   |       | EffectiveDate   |
| Email     |       | ...         |       | StatusCode      |
+-----------+       +-------------+       +-----------------+

Eso es. Eso es todo al respecto. Nunca necesitas borrar nada. Lo anterior es un diseño bastante común que se adapta a un buen grado de flexibilidad, pero puede simplificarlo un poco; puede decidir que no necesita el nivel "Acuerdo" y simplemente hacer que "Cuenta" vaya a una tabla "Estado de cuenta".

Si una necesidad frecuente en su aplicación es obtener una lista de acuerdos / cuentas activas , entonces es una consulta (ligeramente) difícil, pero para eso están las vistas:

CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
    ON agg.Id = s.AgreementId
INNER JOIN Account acc
    ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
    SELECT 1
    FROM AgreementStatus so
    WHERE so.AgreementId = s.AgreementId
    AND so.EffectiveDate > s.EffectiveDate
)

Y tu estas listo. Ahora tiene algo con todos los beneficios de las eliminaciones suaves pero ninguno de los inconvenientes:

  • Los registros huérfanos no son un problema porque todos los registros son visibles en todo momento; simplemente selecciona desde una vista diferente cuando sea necesario.
  • "Eliminar" suele ser una operación increíblemente barata: solo inserta una fila en una tabla de eventos.
  • Nunca hay ninguna posibilidad de perder ningún historial, nunca , no importa cuán mal lo arruines.
  • Todavía puede eliminar una cuenta de forma rígida si lo necesita (por ejemplo, por razones de privacidad) y estar seguro de que la eliminación se realizará de manera limpia y no interferirá con ninguna otra parte de la aplicación / base de datos.

El único problema que queda por abordar es el problema de rendimiento. En muchos casos, en realidad resulta no ser un problema debido al índice agrupado activado AgreementStatus (AgreementId, EffectiveDate): hay muy poca búsqueda de E / S allí. Pero si alguna vez es un problema, hay formas de resolverlo, utilizando disparadores, vistas indexadas / materializadas, eventos a nivel de aplicación, etc.

Sin embargo, no se preocupe por el rendimiento demasiado pronto: es más importante hacer que el diseño sea correcto, y "correcto" en este caso significa usar la base de datos de la manera en que se debe usar una base de datos, como un sistema transaccional .

Aaronaught
fuente
1

Actualmente estoy trabajando con un sistema en el que todas las tablas tienen un indicador de borrado para la eliminación suave. Es la ruina de toda existencia. Rompe totalmente la integridad relacional cuando un usuario puede "borrar" un registro de una tabla, pero los registros secundarios que FK vuelve a esa tabla no se borran en cascada. Realmente hace que los datos de basura después de que pase el tiempo.

Por lo tanto, recomiendo tablas de historial separadas.

Jesse C. Slicer
fuente
Seguramente sin cambios en la historia en cascada, ¿tiene exactamente el mismo problema?
glenatron
No en sus tablas de registros activos, no.
Jesse C. Slicer
Entonces, ¿qué sucede con los registros secundarios que FK de la tabla de usuario después de que el usuario ha sido enviado a la tabla de historial?
glenatron
Su disparador (o lógica de negocios) también consignaría los registros secundarios a sus respectivas tablas de historial. El punto es que no puede eliminar físicamente el registro principal (para pasar al historial) sin que la base de datos le diga que rompió RI. Por lo tanto, se ve obligado a diseñarlo. La bandera eliminada no fuerza los borrados suaves en cascada.
Jesse C. Slicer
3
Depende de lo que realmente significa su eliminación suave. Si es solo una forma de desactivarlos, no es necesario ajustar los registros relacionados con una cuenta desactivada. Parece que solo son datos para mí. Y sí, tengo que lidiar con eso también en un sistema que no diseñé. No significa que te tenga que gustar.
JeffO
1

Romper la mesa en dos sería lo más lamentable que se pueda imaginar.

Estos son los dos pasos muy simples que recomendaría:

  1. Cambie el nombre de la tabla 'usuarios' a 'alusores'.
  2. Cree una vista llamada 'usuarios' como 'select * from allusers donde deleted = false'.

PD: ¡Perdón por la demora de varios meses en responder!

Mike Nakis
fuente
0

Si hubiera estado recuperando cuentas eliminadas cuando alguien regresa con la misma dirección de correo electrónico, habría seguido manteniendo a todos los usuarios en la misma tabla. Esto haría que el proceso de recuperación de la cuenta sea trivial.

Sin embargo, a medida que crea nuevas cuentas, probablemente sea más sencillo mover las cuentas eliminadas a una tabla separada. El sistema en vivo no necesita esta información, así que no la exponga. Como usted dice, hace que las consultas sean más simples y posiblemente más rápidas en conjuntos de datos más grandes. El código más simple también es más fácil de mantener.

ChrisF
fuente
0

No menciona DBMS en uso. Si tiene Oracle con la licencia adecuada, puede considerar dividir la tabla de usuarios en dos particiones: usuarios activos y eliminados.

mczajk
fuente
Luego, debe mover las filas de una partición a otra al eliminar usuarios, lo que definitivamente no es cómo se deben usar las particiones.
Péter Török
@ Péter: ¿Eh? Puede particionar en cualquier criterio que desee, incluida la bandera eliminada.
Aaronaught
@Aaronaught, OK, lo expresé mal. El DBMS puede hacer el trabajo por usted, pero sigue siendo un trabajo adicional (porque la fila debe moverse físicamente de una ubicación a otra, posiblemente a un archivo diferente), y puede deteriorar la distribución física de los datos.
Péter Török