Obsługa usuniętych użytkowników - osobna czy ta sama tabela?

19

Scenariusz jest taki, że mam rosnącą grupę użytkowników, a wraz z upływem czasu użytkownicy anulują swoje konta, które obecnie oznaczamy jako „usunięte” (z flagą) w tej samej tabeli.

Jeśli użytkownicy o tym samym adresie e-mail (w taki sposób logują się użytkownicy) chcą utworzyć nowe konto, mogą się ponownie zarejestrować, ale tworzone jest NOWE konto. (Mamy unikalne identyfikatory dla każdego konta, więc adresy e-mail można duplikować między aktywnymi i usuniętymi).

Zauważyłem, że w całym naszym systemie, w normalnym toku rzeczy stale sprawdzamy tabelę użytkowników, sprawdzając, czy użytkownik nie jest usuwany, podczas gdy myślę, że wcale nie musimy tego robić ... ! [Wyjaśnienie1: „przez ciągłe zapytania” miałem na myśli, że mamy zapytania podobne do: „... od użytkowników GDZIE jest usunięte =„ 0 ”ORAZ…”. Na przykład może być konieczne pobranie wszystkich użytkowników zarejestrowanych na wszystkie spotkania w określonym dniu, więc w TYM zapytaniu mamy również OD użytkowników, GDZIE isdeleted = "0" - czy to wyjaśnia mój punkt widzenia?]

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

Jakie są zalety i wady każdego z tych podejść?

Alan Beats
źródło
Z jakich powodów zatrzymujesz użytkowników?
keppla
2
Nazywa się to miękkim usuwaniem. Zobacz także Usuwanie rekordów bazy danych unpermenantley (soft-delete)
Sjoerd
@keppla - wspomina, że: „księgowość historyczna”.
ChrisF
@ChrisF: nie był zainteresowany w zakresie: czy chce zachować ksiąg tylko użytkowników, czy jest tam jeszcze jakieś dane dołączone (np komentarze, płatności, itp)
keppla
Może pomóc przestać myśleć o nich jako usuniętych (co nie jest prawdą) i zacząć myśleć o ich koncie jako anulowanym (co jest prawdą).
Mike Sherrill 'kot' Przypomnijmy,

Odpowiedzi:

13

(1) nadal utrzymując usuniętych użytkowników w „głównym” tabeli użytkowników

  • Plusy: prostsze zapytania we wszystkich przypadkach
  • Wady: może obniżyć wydajność w czasie, gdy istnieje duża liczba użytkowników

(2) przechowywania usunięte użytkowników w osobnej tabeli (w większości wymaganej do historycznego księgowości)

można użyć np spust przenieść usuniętych użytkowników do tabeli historii automatycznie.

  • Plusy: łatwiejsze utrzymanie dla aktywnej tabeli użytkowników, stabilna praca
  • Minusy: potrzebujesz różnych zapytań do tabeli historii; Jednakże, ponieważ większość aplikacji nie powinien być zainteresowany tym, że ten negatywny efekt jest prawdopodobnie ograniczone
Péter Török
źródło
11
Tabela partycji (na IsDeleted) by usunąć problemy z wydajnością przy użyciu pojedynczej tabeli.
Ian
1
@Ian chyba każde zapytanie jest wyposażony IsDeleted jako kryteria zapytania (co nie wydaje się oryginalne pytanie), partycjonowanie nawet może spowodować obniżyć wydajność.
Adrian Shum,
1
@Adrian, byłem przy założeniu, że najczęściej zadawane pytania byłaby w momencie logowania i że tylko nikt usunięte użytkownicy będą mogli się zalogować.
Ian
1
Użyj widoku indeksowanego w isdeleted, jeśli staje się to problemem z wydajnością i chcesz skorzystać z pojedynczej tabeli.
JeffO
10

Zdecydowanie polecam korzystanie z tego samego stołu. Głównym powodem jest integralność danych. Najprawdopodobniej będzie wiele tabel z relacjami zależnymi od użytkowników. Gdy użytkownik zostanie usunięty, nie chcesz pozostawiać tych rekordów osieroconych.
Osierocenie nagrań utrudnia egzekwowanie ograniczeń i utrudnia wyszukiwanie informacji historycznych. Inne zachowanie, które należy wziąć pod uwagę, jeśli użytkownik dostarczy używaną wiadomość e-mail, jeśli chcesz, aby odzyskała wszystkie swoje stare rekordy. Działałoby to automatycznie przy użyciu miękkiego usuwania. Jeśli chodzi o kodowanie go, na przykład w moim obecnym aplikacji C # linq where skreślony = 0 klauzula jest automatycznie dołączany do końca wszystkich zapytań

Andrey
źródło
7

„Zauważyłem, że w całym naszym systemie w normalnym trybie rzeczy stale sprawdzamy tabelę użytkowników, sprawdzając, czy użytkownik nie jest usuwany”

Daje mi to nieprzyjemny zapach projektowania. Powinieneś ukryć taką logikę. Na przykład powinieneś mieć UserServicemetodę dostarczania isValidUser(userId)„w całym systemie”, zamiast robić coś takiego:

„pobierz rekord użytkownika, sprawdź, czy użytkownik jest oflagowany jako usunięty”.

Sposób przechowywania usuniętego użytkownika nie powinien mieć wpływu na logikę biznesową.

Przy takim rodzaju enkapsulacji powyższy argument nie powinien już wpływać na podejście twojego wytrwałości. Następnie możesz skupić się bardziej na zaletach i wadach związanych z samym uporem.

Do rozważenia należą:

  • Jak długo należy usuwać usunięty rekord?
  • Jaki jest odsetek usuniętych rekordów?
  • Czy będzie to problem dla więzy integralności (np użytkownik jest określany z innej tabeli), jeśli rzeczywiście usunąć go z tabeli?
  • Czy uważają Państwo, ponownym otwarciem użytkownikowi?

Normalnie wziąłbym połączoną sposób:

  1. Oznacz rekord jako usunięte (jak do utrzymania go na wymaganie funkcjonalne, takie jak ponowne otwarcie sieciowego lub sprawdzania ostatnio zamkniętą ac).
  2. Po ustalonym okresie, przesuń usunięty rekord do tabeli archiwum (dla celów księgowych).
  3. Oczyścić je po pewnym zdefiniowanym okresie archiwum.
Adrian Shum
źródło
1
[Wyjaśnienie1: „przez ciągłe zapytania” miałem na myśli, że mamy zapytania podobne do: „... od użytkowników GDZIE jest usunięte =„ 0 ”ORAZ…”. Na przykład może być konieczne pobranie wszystkich użytkowników zarejestrowanych na wszystkie spotkania w określonym dniu, więc w TYM zapytaniu mamy również OD użytkowników, GDZIE isdeleted = "0" - czy to wyjaśnia moje zdanie?] @Adrian
Alan Beats
Tak, o wiele jaśniej. :) Jeśli to robię, wolę, aby zmieniło się to jako zmiana statusu użytkownika, zamiast patrzeć na to jak usunięcie fizyczne / logiczne. Choć ilość kodu nie zmniejszy ( „i isDeleted =«0»” vs "i«stan <> «TERMINATED»»), ale wszystko będzie wyglądać znacznie bardziej rozsądne, i to jest normalne, że inny stan użytkownika zbyt. Okresowe-czystka rozwiązanych użytkowników można wykonać też, jak sugeruje się w mojej poprzedniej odpowiedzi)
Adrian Shum
5

Aby poprawnie odpowiedzieć na to pytanie, musisz najpierw zdecydować: co oznacza „usuń” w kontekście tego systemu / aplikacji?

Aby odpowiedzieć na to pytanie, trzeba odpowiedzieć na kolejny pytanie: Dlaczego są zapisy zostać usunięty?

Istnieje wiele dobrych powodów, dla których użytkownik może potrzebować do danych usunąć. Zwykle uważam, że istnieje dokładnie jeden powód (na stole), dlaczego kasowania może być konieczne. Oto niektóre przykłady:

  • Aby odzyskać miejsce na dysku;
  • Hard-usunięcie wymagane zgodnie z polityką prywatności / retencji;
  • Uszkodzony / beznadziejnie błędne dane, łatwiej usuwać i regenerują niż do naprawy.
  • Większość wierszy są usuwane, np tabeli dziennika ograniczone do rejestrów X / dzień.

Istnieje również kilka bardzo słabych powodów twardego usuwania (więcej o przyczynach później):

  • Aby poprawić błąd Mniejszej. Zwykle podkreśla lenistwo programistów i UI wrogie.
  • Do „void” transakcji (np faktury, które powinny nigdy nie zostały rozliczone).
  • Bo może .

Dlaczego, można zapytać, czy naprawdę jest to taka wielka sprawa? Co złego jest w dobrej ole” DELETE?

  • W każdym systemie nawet zdalnie przywiązany do pieniędzy, ciężko narusza usunięcie wszelkiego rodzaju oczekiwań księgowe, nawet jeśli przeniósł się do tabeli archiwum / nagrobek. Prawidłowym sposobem radzenia sobie z tym jest zdarzenie wsteczne .
  • Stoły Archive mają tendencję do odchylania się od schematu na żywo. Jeśli zapomnisz nawet o jednej nowo dodanej kolumnie lub kaskadzie, właśnie utraciłeś te dane na stałe.
  • Ciężko usunięcie może być bardzo kosztowna operacja, zwłaszcza z kaskadami . Wiele osób nie zdaje sobie sprawy, że kaskadowe więcej niż jeden poziom (lub w niektórych przypadkach każdy kaskadowy, zależnie od DBMS) spowoduje operacji na poziomie rekordu zamiast zestawu operacji.
  • Powtarza się często trudne usunięcie przyspiesza proces rozdrobnienia indeksu.

Tak miękkie kasowania jest lepiej, prawda? Nie, nie bardzo:

  • Konfigurowanie kaskady staje się niezwykle trudne. Prawie zawsze skończyć z tym, co wydaje się klientowi jako osieroconych wierszy.
  • Można dostać tylko do śledzenia jednej usunięcie. Co zrobić, jeśli rząd zostanie usunięty i przywrócił wiele razy?
  • Wydajność odczytu spada, chociaż można to nieco złagodzić za pomocą partycjonowania, widoków i / lub filtrowanych indeksów.
  • Jak zasugerował wcześniej, to może faktycznie być nielegalne w niektórych scenariuszach / jurysdykcjach.

Prawda jest taka, że oba te podejścia są błędne. Usuwanie jest źle. Jeśli rzeczywiście tym pytaniem to znaczy, jesteś modelowania aktualny stan zamiast transakcji. Jest to zła, zła praktyka w obszarze bazy danych.

Udi Dahan napisał o tym w Don't Don't - Just Don't . Zawsze istnieje jakieś zadanie, transakcja, aktywność lub (moje preferowane pojęcie) zdarzenie, które faktycznie reprezentuje „usuń”. Jest OK, jeśli chcesz denormalize następnie w „stan obecny” stół do wydajności, ale zrobić to po już przybita modelu transakcyjnym, a nie przed.

W tym przypadku masz „użytkowników”. Użytkownicy są głównie klienci. Klienci mają stosunki handlowe z Państwem. Ten związek nie rozpłynie się w powietrzu, ponieważ anulowali swoje konto. Co się naprawdę dzieje, jest:

  • Klient tworzy konto
  • Klient anuluje konto
  • Klient odnawia konto
  • Klient anuluje konto
  • ...

W każdym razie, jest to ten sam klient , i ewentualnie samego konta (czyli każdego przedłużenia konta to nowa umowa serwisowa). Więc czemu usuwanie wierszy? Jest to bardzo łatwe do modelu:

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

Otóż ​​to. To wszystko. Nigdy nie trzeba niczego usuwać. Powyższe jest dość powszechne projekt, który może pomieścić dobrą elastyczność, ale można uprościć to trochę; może zdecydować, że nie trzeba poziom „Umowa” i po prostu „Konto” idź do stołu „AccountStatus”.

Jeśli często potrzeba w danej aplikacji jest uzyskać listę aktywnych umów / rachunki to jest to (nieznacznie) zapytanie trudne, ale to właśnie poglądy są dla:

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
)

I jesteś skończony. Teraz masz coś ze wszystkimi zaletami miękkiego usuwania, ale nie ma żadnych wad:

  • Osierocone rekordy nie stanowią problemu, ponieważ wszystkie rekordy są widoczne przez cały czas; w razie potrzeby po prostu wybierasz z innego widoku.
  • „Usuwanie” jest zwykle niezwykle tanią operacją - wystarczy wstawić jeden wiersz do tabeli zdarzeń.
  • Nigdy nie jest jakaś szansa utraty historię, nigdy , bez względu na to jak bardzo zepsuć.
  • Nadal możesz usunąć konto, jeśli zajdzie taka potrzeba (np. Ze względu na prywatność), i zachowaj wygodę, wiedząc, że usunięcie nastąpi czysto i nie będzie kolidować z żadną inną częścią aplikacji / bazy danych.

Jedynym problemem w lewo do rozwiązania jest kwestia wydajności. W wielu przypadkach to rzeczywiście okazuje się, że to nie problem, bo z indeksu klastrowego AgreementStatus (AgreementId, EffectiveDate)- jest bardzo niewiele I / O szukając tam dzieje. Ale jeśli to jest zawsze problem, istnieją sposoby, aby rozwiązać ten, za pomocą wyzwalaczy, indeksowane / zmaterializowane perspektywy, zdarzeń na poziomie aplikacji, etc.

Nie martw się o wydajności zbyt wcześnie, chociaż - to ważne, aby uzyskać prawo do projektowania i „prawo” oznacza w tym przypadku korzystania z bazy danych tak, jak baza danych ma być używany, jako transakcyjnej systemu.

Aaronaught
źródło
1

Obecnie pracuję z systemem, w którym każda tabela ma flagę Usunięte do miękkiego usuwania. Jest zmorą wszelkiego istnienia. To całkowicie zrywa relacyjnej integralności, gdy użytkownik może „usunąć” rekord z jednej tabeli, ale dzieci, które rekordy FK powrotem do tej tabeli nie kaskady są miękkie usunięte. Naprawdę sprawia danych śmieci po upływem czasu.

Tak, polecam oddzielne tabele historii.

Jesse C. Slicer
źródło
Z pewnością bez kaskadowo History-zmianowym, masz dokładnie ten sam problem?
glenatron
Nie w twoich aktywnych tabelach rekordów, nie.
Jesse C krajalnica
Co dzieje się z rekordami potomnymi, które FK zniknęły z tabeli użytkowników po wysłaniu użytkownika do tabeli historii?
glenatron
Twój wyzwalacz (lub logika biznesowa) również przeniesie rekordy potomne do odpowiednich tabel historii. Chodzi o to, że nie można fizycznie usunąć rekord nadrzędny (przejścia do historii) bez bazy danych z informacją, że złamałeś RI. Więc musisz go zaprojektować. Usunięta flaga nie wymusza kaskadowego usuwania z pamięci.
Jesse C. Slicer
3
Zależy, co tak naprawdę oznacza twoje miękkie usunięcie. Jeśli jest to tylko sposób, aby je wyłączyć, nie ma potrzeby, aby dostosować dokumentację związaną z kontem wyłączona. Wydaje mi się, że to tylko dane. I tak, muszę sobie z tym poradzić w systemie, którego nie zaprojektowałem. Nie oznacza, że musisz się podoba.
JeffO
1

Aby przełamać stolik w dwóch byłby Lamest co można sobie wyobrazić.

Oto dwa bardzo proste kroki, które poleciłbym:

  1. Zmiana nazwy tabeli Users 'do' AllUsers.
  2. Tworzenie widoku o nazwie „użytkownicy” jako „select * from AllUsers gdzie usuniętych = false”.

PS Przepraszam za opóźnienie kilka miesięcy długi odpowiedziami!

Mike Nakis
źródło
0

Gdybyś był odzyskiwania skasowanych kont, gdy ktoś wraca z tego samego adresu e-mail, a następnie ja bym poszedł z zachowaniem wszystkich użytkowników w tej samej tabeli. To może sprawić, że proces odzyskiwania konta trywialne.

Jednak podczas tworzenia nowych kont prawdopodobnie łatwiej byłoby przenieść usunięte konta do osobnej tabeli. System na żywo nie potrzebuje tej informacji, więc nie należy jej wystawiać. Jak mówisz, sprawia, że ​​zapytania są prostsze i być może szybsze w przypadku większych zbiorów danych. Prostszy kod jest również łatwiejszy w utrzymaniu.

ChrisF
źródło
0

Nie wspominając DBMS w użyciu. Jeśli masz Oracle z odpowiednią licencją, możesz rozważyć podzielenie tabeli użytkowników na dwie partycje: użytkowników aktywnych i usuniętych.

mczajk
źródło
Następnie należy przenieść wiersze z jednej strefy do drugiej, gdy usuwanie użytkowników, co na pewno nie jest jak partycje mają być używane.
Péter Török,
@ Peter: Huh? Można podzielić na dowolnych kryteriów, które chcesz, w tym usuniętego flagi.
Aaronaught
@Aaronaught, OK, ja to źle sformułowane. DBMS może wykonać pracę za Ciebie, ale nadal jest to dodatkowa praca (ponieważ wiersz musi zostać fizycznie przeniesiony z jednej lokalizacji do innej, być może do innego pliku) i może pogorszyć fizyczną dystrybucję danych.
Péter Török