Jak należy usuwać dane w bazie danych?

44

Chciałbym zaimplementować funkcję „cofania” w aplikacji internetowej, aby użytkownik mógł zmienić zdanie i odzyskać usunięty zapis. Myśli, jak to wdrożyć? Niektóre opcje, które rozważałem, to w rzeczywistości usunięcie danego rekordu i zapisanie zmian w osobnej tabeli audytu lub nieusunięcie rekordu i użycie logicznej kolumny „usunięto”, aby oznaczyć go jako usunięty. To ostatnie rozwiązanie wymagałoby dodatkowej logiki aplikacji, aby zignorować „usunięte” rekordy w normalnych okolicznościach, ale znacznie ułatwiłoby wdrożenie odzyskiwania rekordów po stronie aplikacji.

Abie
źródło
Zapomniałem wspomnieć, że w drugim przypadku oflagowane rekordy będą musiały zostać usunięte lub przeniesione po pewnym rozsądnym czasie.
Abie,
Z jakiej bazy danych korzystasz?
Evan Carroll,
Tabela czasowa to najlepsze rozwiązanie dla SQL Server 2016 i nowszych wersji.
Sameer

Odpowiedzi:

37

Tak, zdecydowanie wybrałbym drugą opcję, ale dodałbym jeszcze jedno pole pole daty.

Więc dodajesz:

delete       boolean
delete_date  timestamp

Pozwoli ci to dać czas na usunięcie operacji.

Jeśli czas jest krótszy niż godzina, można cofnąć usunięcie.

Aby naprawdę usunąć usunięty wpis, po prostu stwórz procedurę składowaną, która wyczyści każdy wpis z ustawionym parametrem usuwania i czasem dłuższym niż jedna godzina i umieści go jako zakładkę cron, która będzie uruchamiana co 24 godziny

Godzina jest tylko przykładem.

Spredzy
źródło
Alternatywnie możesz mieć inną flagę - cleanedlub coś - co wskazuje, że dane powiązane z tym rekordem zostały poprawnie, kompleksowo usunięte. Rekord może zostać cofnięty, chyba że cleanedjest prawdziwy, w takim przypadku nie można go odzyskać.
Gaurav
14
To jest wspólne podejście. Zwykle używam jednego pola deleted_atzawierającego zarówno semantyczną wartość deletelogiczną, jak i delete_dateznacznik czasu. Jeśli deleted_atjest NULLobsługiwany, sprawa deletejest FALSEi delete_datejest NULL, deleted_atzawierająca uchwyt znacznika czasu, sprawa deletejest TRUEi delete_datezawiera znacznik czasu, co oszczędza czas, pamięć i logikę aplikacji.
Julien
1
Lubię pole logiczne i datę. W zależności od tego, jak zaimplementujesz logikę usuwania, możesz nawet mieć odrębną tabelę z datą i unikalnym kluczem dla rekordu, który został „usunięty”. Przechowywane procedury ułatwiają to. Zajmuje dodatkowe wymagane miejsce na wiersz do 1 bitu w porównaniu do 8+. Będziesz także mógł raportować o usunięciach dziennie bez dotykania tabeli źródłowej.
AndrewSQL,
Uwaga: delete jest zastrzeżonym słowem w MySQL.
Jason Rikard,
Pamiętaj, że filtrowany indeks w twoim deletedpolu może znacznie poprawić wydajność, gdy pytasz o niededelowane wiersze
Ross Presser
21

W naszych aplikacjach tak naprawdę nie usuwamy niczego na żądanie użytkowników (nasi klienci znajdują się w regulowanych środowiskach, w których usuwanie czegokolwiek może potencjalnie prowadzić do problemów prawnych).

Starsze wersje przechowujemy w osobnej tabeli audytu (więc dla tabeli tabela_tablic, gdzie jest również tabela o nazwie tabela_tablicy_tablicy), która jest identyczna oprócz posiadania dodatkowego identyfikatora wersji (znacznik czasu, jeśli twoja baza danych obsługuje wystarczająco szczegółowe wartości czasu, numer wersji całkowitej) lub UUID, który jest kluczem obcym do ogólnej tabeli kontroli itp., i automatycznie aktualizuj tabelę kontroli za pomocą wyzwalacza (więc nie musimy informować całego kodu aktualizującego rekordy o wymaganiu kontroli).

Tą drogą:

  • operacja usuwania jest po prostu prostym usunięciem - nie trzeba do tego dodawać żadnego dodatkowego kodu (chociaż możesz chcieć zapisać, kto poprosił o to, które wiersze mają zostać usunięte, nawet jeśli nie są faktycznie usunięte)
  • wstawki i aktualizacje są podobnie proste
  • możesz zaimplementować cofnięcie usunięcia lub przywrócenie, po prostu przywracając „normalny” wiersz do starej wersji (wyzwalacz audytu zostanie ponownie uruchomiony, więc tabela śladu inspekcji również odzwierciedli tę zmianę)
  • możesz zaoferować możliwość przejrzenia lub przywrócenia dowolnej poprzedniej wersji, a nie tylko cofnięcia poprzedniej
  • nie musisz dodawać „jest oznaczony jako usunięty?” sprawdza każdy punkt kodu odnoszący się do danej tabeli lub logikę „aktualizuj kopię kontroli” do każdego punktu kodu, który usuwa / aktualizuje wiersze (chociaż musisz zdecydować, co zrobić z usuniętymi wierszami w tabeli audytu: mamy usunięto / nie oznaczono dla każdej tam wersji, więc nie ma dziury w historii, jeśli rekordy zostaną usunięte, a później usunięte)
  • Przechowywanie kopii kontroli w osobnej tabeli oznacza, że ​​możesz je łatwo podzielić na różne grupy plików.

Jeśli używasz znacznika czasu zamiast (lub również jako) liczb całkowitych wersji, możesz użyć tego, aby usunąć starsze kopie po określonym czasie, jeśli to konieczne. Ale przestrzeń dyskowa jest obecnie stosunkowo tania, więc jeśli nie mamy powodu, aby upuścić stare dane (tj. Przepisy dotyczące ochrony danych, które mówią, że powinieneś usunąć dane klienta po X miesiącach / latach), nie zrobilibyśmy tego.


Ta odpowiedź trwa już kilka lat i od tego czasu zmieniło się kilka kluczowych rzeczy, które mogą wpłynąć na tego rodzaju planowanie. Nie zajmę się szczegółowymi szczegółami, ale krótko dla korzyści osób czytających to dzisiaj:

  • SQL Server 2016 wprowadził „tabele czasowe z wersją systemową”, które wykonują dla ciebie wiele pracy, a ponadto, ponieważ zapewniono trochę miłego cukru syntaktycznego, aby ułatwić tworzenie i obsługę zapytań historycznych oraz koordynować podzbiór zmian schematu między tabele podstawowe i historyczne. Nie są obojętni, ale są potężnym narzędziem do tego celu. Podobne funkcje są również dostępne w innych systemach DB.

  • Zmiany w przepisach dotyczących ochrony danych, w szczególności wprowadzenie RODO, mogą znacząco zmienić kwestię, kiedy dane powinny zostać trwale usunięte. Musisz rozważyć równowagę między nieusługiwaniem się danymi, które mogą być przydatne (a nawet wymagane przez prawo) do celów audytu w późniejszym terminie, a koniecznością poszanowania praw ludzi (zarówno ogólnie, jak i szczegółowo określonych w odpowiednich przepisach) przy rozważaniu twoje projekty. Może to być problem z tabelami czasowymi w wersji systemowej, ponieważ nie można modyfikować historii w celu wyczyszczenia danych osobowych bez krótkoterminowych zmian schematu w celu wyłączenia śledzenia historii podczas wprowadzania zmian.

David Spillett
źródło
Jak radzisz sobie z usuwaniem i zmienianiem nazw kolumn? Ustawić wszystko na wartość zerową?
Stijn
1
@Stijn: Często nie zmienia się struktur, więc niewiele się dzieje. Colunmy na ogół nigdy nie są usuwane, gdy już istnieją w produkcji - jeśli przestaną być używane, po prostu usuń wszelkie ograniczenia, które powstrzymałyby ich od NULL (lub dodaj wartości domyślne, aby poradzić sobie z ograniczeniami za pomocą „magicznej wartości”, choć wydaje się to bardziej brudne) i przestańcie się do nich odwoływać w innym kodzie. W przypadku nazw: dodaj nowy, przestań używać starego i w razie potrzeby skopiuj dane ze starego na nowy. Jeśli zmienisz nazwy kolumn, upewnij się, że dokonano tej samej zmiany zarówno w tabeli podstawowej, jak i tabelach kontroli.
David Spillett
9

Z kolumną usuniętą boolean zaczniesz mieć problemy, jeśli twoja tabela zacznie rosnąć i stanie się naprawdę duża. Sugeruję przeniesienie usuniętych kolumn raz w tygodniu (mniej więcej w zależności od specyfikacji) do innej tabeli. W ten sposób masz ładny mały aktywny stół i duży zawierający wszystkie rekordy zebrane w czasie.

poelinca
źródło
7

Poszedłbym z oddzielnym stołem. Ruby on Rails ma acts_as_versionedwtyczkę, która zasadniczo zapisuje wiersz do innej tabeli z postfiksem _versionprzed jego aktualizacją. Chociaż nie potrzebujesz tego dokładnego zachowania, powinno również działać w twoim przypadku (skopiuj przed usunięciem).

Podobnie jak @Spredzy polecam również dodanie delete_datekolumny, aby móc programowo wyczyścić rekordy, które nie zostały przywrócone po X godzinach / dniach / czymkolwiek.

Michael Kohl
źródło
4

Rozwiązaniem, którego używamy wewnętrznie w tej sprawie, jest kolumna stanu z pewnymi zakodowanymi wartościami dla niektórych określonych stanów obiektu: Usunięte, Aktywne, Nieaktywne, Otwarte, Zamknięte, Zablokowane - każdy status ma pewne znaczenie użyte w aplikacji. Z punktu widzenia db nie usuwamy obiektów, po prostu zmieniamy status i przechowujemy historię dla każdej zmiany w tabeli obiektów.

Marian
źródło
3

Kiedy powiesz, że „to drugie rozwiązanie wymagałoby dodatkowej logiki aplikacji, aby zignorować„ usunięte ”rekordy”, prostym rozwiązaniem jest widok, który je odfiltrowuje.

Peter Taylor
źródło
To nie tylko kwestia widoku. Wszelkie operacje wykonywane na zestawie musiałyby wykluczać „usunięte” rekordy.
Abie,
2

Podobnie jak sugerował Spredzy, używamy pola znacznika czasu do usuwania we wszystkich naszych aplikacjach. Wartość logiczna jest zbędna, ponieważ ustawienie znacznika czasu wskazuje, że rekord został usunięty. W ten sposób nasz ChNP zawsze dodaje AND (deleted IS NULL OR deleted = 0)do instrukcji select, chyba że model wyraźnie zażąda włączenia usuniętych rekordów.

Obecnie nie zbieramy śmieci w żadnym z wyjątkiem tabel zawierających obiekty BLOB lub teksty; przestrzeń jest trywialna, jeśli rekordy są dobrze znormalizowane, a indeksowanie deletedpola ma ograniczony wpływ na wybraną prędkość.

Bryan Agee
źródło
0

Alternatywnie możesz nałożyć ciężar na użytkowników (i programistów) i przejść do sekwencji „Jesteś pewien?”, „Czy na pewno jesteś pewien?” i „Czy jesteś absolutnie, dobrze i naprawdę pewny?” pytania przed usunięciem rekordu. Mało żartobliwe, ale warte rozważenia.

YaHozna
źródło
0

Przyzwyczaiłem się do wyświetlania wierszy tabeli z kolumnami takimi jak „Usunięte” i nie lubię ich. Samo pojęcie „skreślony” polega na tym, że wpis nie powinien był zostać dokonany w pierwszej kolejności. Praktycznie nie można ich usunąć z bazy danych, ale nie chcę ich z moimi gorącymi danymi. Logicznie usunięte wiersze są z definicji zimnymi danymi, chyba że ktoś konkretnie chce zobaczyć usunięte dane.

Ponadto każde napisane zapytanie musi je konkretnie wykluczać, a indeksy również je uwzględniają.

Chciałbym zobaczyć zmianę na poziomie architektury bazy danych i na poziomie aplikacji: utwórz schemat o nazwie „usunięty”. Każda tabela zdefiniowana przez użytkownika ma identyczny odpowiednik w schemacie „usuniętym” z dodatkowym polem przechowującym metadane - użytkownika, który ją usunął i kiedy. Konieczne jest utworzenie kluczy obcych.

Następnie usuwa staje się usuwanie-wstawianie. Najpierw wiersz do usunięcia jest wstawiany do jego „usuniętego” odpowiednika schematu. Wiersz w głównej tabeli można następnie usunąć. Należy jednak dodać dodatkową logikę gdzieś wzdłuż linii. Naruszenie klucza obcego można rozwiązać.

Klucze obce muszą być odpowiednio obsługiwane. Złą praktyką jest logiczne usuwanie wiersza, ale którego podstawowy / niepowtarzalny ma kolumny w innych tabelach, które go dotyczą. To i tak nie powinno się zdarzyć. Zwykłe zadanie może usuwać wiersze wdów (wiersze, których klucze podstawowe nie mają odniesień w innych tabelach pomimo obecności klucza obcego. Jest to jednak logika biznesowa.

Ogólną korzyścią jest zmniejszenie metadanych w tabeli i poprawa wydajności. Kolumna „deleteDate” mówi, że ten wiersz nie powinien być tutaj, ale dla wygody zostawiamy go tam i pozwalamy zapytaniu SQL go obsłużyć. Jeśli kopia usuniętego wiersza jest przechowywana w schemacie „usuniętym”, wówczas główna tabela z gorącymi danymi ma wyższy procent gorących danych (zakładając, że jest archiwizowany w odpowiednim czasie) i mniej niepotrzebnych kolumn metadanych. Indeksy i zapytania nie muszą już uwzględniać tego pola. Im mniejszy rozmiar wiersza, tym więcej wierszy można dopasować do strony, tym szybciej działa SQL Server.

Główną wadą jest rozmiar operacji. Istnieją teraz dwie operacje zamiast jednej, a także dodatkowa logika i obsługa błędów. Może to prowadzić do większego blokowania niż aktualizacja pojedynczej kolumny, w przeciwnym razie zajęłoby to. Transakcja utrzymuje blokady na stole dłużej i są zaangażowane dwie tabele. Usuwanie danych produkcyjnych, przynajmniej z mojego doświadczenia, jest rzadkością. Mimo to w jednej z głównych tabel 7,5% z prawie 100 milionów wpisów ma wpis w kolumnie „Usunięte”.

W odpowiedzi na pytanie aplikacja musiałaby mieć świadomość „cofnięcia usunięcia”. Musiałby po prostu zrobić to samo w odwrotnej kolejności: wstawić wiersz ze schematu „usunięty” do głównej tabeli, a następnie usunąć wiersz ze „usuniętego schematu”. Znowu potrzebna jest dodatkowa logika i obsługa błędów, aby uniknąć błędów, problemów z kluczami obcymi i tym podobnych.

Sean Redmond
źródło