Silinen kullanıcıları yönetme - ayrı mı yoksa aynı tablo mu?


19

Senaryo, genişleyen bir kullanıcı grubum var ve zaman geçtikçe, kullanıcılar şu anda aynı tabloda 'silindi' (bir bayrakla) olarak işaretlediğimiz hesaplarını iptal edecekler.

Aynı e-posta adresine sahip kullanıcılar (bu şekilde giriş yaparlar) yeni bir hesap oluşturmak isterse, tekrar kaydolabilirler, ancak YENİ bir hesap oluşturulur. (Her hesap için benzersiz kimliklerimiz vardır, bu nedenle e-posta adresleri canlı ve silinenler arasında çoğaltılabilir).

Ne fark ettim ki, tüm sistemimizde, normal bir şekilde sürekli olarak kullanıcı tablosunu kontrol eden kullanıcı tablosunun silinmediğini kontrol ederken, düşündüğüm şey bunu yapmamız gerekmediğidir ... ! [Clarification1: 'sürekli sorgulama' ile, '...' kullanıcılarından WHERE isdeleted = "0" AND ... 'gibi sorgularımız var demek istedim. Örneğin, böylece BU sorguda, belirli bir tarihte tüm toplantılar için kayıtlı tüm kullanıcıların alınmasına gerekebilir, biz de isDeleted = "0" kullanıcılar GELEN var - bu benim açımdan net hale geliyor]?

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

Her iki yaklaşımın da artıları ve eksileri nelerdir?


Kullanıcıları hangi nedenlerle saklıyorsunuz?
keppla

2
Buna yazılım silme denir. Ayrıca bkz. Veritabanı kayıtlarını silme unpermenantley (soft-delete)
Sjoerd

@keppla - bundan bahseder: "tarihsel defter tutma".
ChrisF

@ChrisF: kapsamla ilgileniyordum: sadece kullanıcıların kitaplarını tutmak mı istiyor, yoksa hala bazı veriler
ekleniyor mu

Bu silinmiş olarak onları düşünmeye durdurmak için yardımcı (doğru değil) ve iptal olarak (ki kendi hesabına düşünmeye başlayabilir olduğu doğrudur).
Mike Sherrill 'Cat Recall'

Yanıtlar:


13

(1) silinmiş kullanıcıları 'ana' kullanıcı tablosunda tutmaya devam et

  • Artıları: her durumda daha basit sorgular
  • Eksileri: çok sayıda kullanıcı varsa zaman içinde performansı düşürebilir

(2) silinmiş kullanıcıları ayrı bir tabloda tutun (çoğunlukla geçmiş defter tutma için gereklidir)

Örneğin, silinen kullanıcıları otomatik olarak geçmiş tablosuna taşımak için bir tetikleyici kullanabilirsiniz.

  • Artıları: aktif kullanıcılar tablosu için daha basit bakım, istikrarlı performans
  • Eksileri: geçmiş tablosu için farklı sorgular gerekir; Bununla birlikte, uygulamanın çoğunun bununla ilgilenmemesi gerektiğinden, bu olumsuz etki muhtemelen sınırlıdır

11
Bir bölüm tablosu (IsDeleted'de) tek bir tablo kullanarak performans sorunlarını giderir.
Ian

1
@Her sorguda IsDeleted ile sorgu ölçütü olarak sağlanmıyorsa (orijinal soruda görünmüyorsa), bölümleme performansın düşmesine neden olabilir.
Adrian Shum

1
@Adrian, en yaygın sorguların giriş zamanında olacağını ve yalnızca silinen hiçbir kullanıcının giriş yapmasına izin verilmeyeceğini varsayıyordum.
Ian

1
Performans sorunu haline gelirse ve tek bir tablonun avantajını istiyorsanız, isndeleted dizininde bir görünüm kullanın.
JeffO

10

Aynı tabloyu kullanmanızı kesinlikle tavsiye ederim. Bunun ana nedeni veri bütünlüğüdür. Büyük olasılıkla kullanıcılara bağlı olarak ilişkileri olan birçok tablo olacaktır. Bir kullanıcı silindiğinde bu kayıtları yetim bırakmak istemezsiniz.
Artık kayıtlara sahip olmak, zorlayıcı kısıtlamaları zorlaştırır ve tarihi bilgilere bakmayı zorlaştırır. Kullanıcının eski kayıtlarını kurtarmasını istiyorsanız, kullanılmış bir e-posta sağladığında dikkate alınması gereken diğer davranış. Bu, yumuşak silme kullanılarak otomatik olarak çalışır. Kodladığım kadarıyla, örneğin şu anki c # linq uygulamamda where deleted = 0 yan tümcesi tüm sorguların sonuna otomatik olarak eklenir


7

"Fark ettiğim şey, sistemimizin tamamında, normal bir şekilde, sürekli olarak kullanıcı tablosunu silerek kontrol edilmediğini kontrol ediyor."

Bu bana kötü bir tasarım kokusu veriyor. Böyle bir mantığı gizlemelisin. Örneğin, aşağıdaki gibi bir şey yapmak yerine "sisteminizde" kullanmak için UserServicebir yöntem sağlamanız gerekir isValidUser(userId):

msgstr "kullanıcı kaydını al, kullanıcının silinmiş olarak işaretlenip işaretlenmediğini kontrol et".

Silinen kullanıcıyı saklama şekliniz iş mantığını etkilememelidir.

Böyle bir kapsülleme ile, yukarıdaki argüman, kalıcılığınızın yaklaşımını artık etkilememelidir. O zaman sebatla ilgili artı ve eksilere daha fazla odaklanabilirsiniz.

Dikkate alınması gerekenler:

  • Silinen kayıt gerçekte ne kadar süreyle temizlenmelidir?
  • Silinen kayıtların oranı nedir?
  • Tablodan gerçekten kaldırırsanız, referans bütünlüğü ile ilgili bir sorun olacak mı (örneğin, kullanıcı diğer tablodan yönlendiriliyor)?
  • Kullanıcıyı yeniden açmayı düşünüyor musunuz?

Normalde kombine bir yol alırdım:

  1. Kaydı silinmiş olarak işaretleyin (ac'yi yeniden açmak veya son zamanlarda kapalı ac'ı kontrol etmek gibi işlevsel gereksinimler için saklamak için).
  2. Önceden tanımlanmış bir süreden sonra, silinen kaydı arşiv tablosuna taşıyın (defter tutma amacıyla).
  3. Önceden tanımlanmış bir arşiv döneminden sonra temizleyin.

1
[Clarification1: 'sürekli sorgulama' ile, '...' kullanıcılarından WHERE isdeleted = "0" AND ... 'gibi sorgularımız var demek istedim. Örneğin, böylece BU sorguda, belirli bir tarihte tüm toplantılar için kayıtlı tüm kullanıcıların alınmasına gerekebilir, biz de isDeleted = "0" kullanıcılar GELEN var? - Bu benim açımdan net hale gelmez] @Adrian
Alan Beats

Evet, çok daha net. :) Bunu yapıyorsam, fiziksel / mantıksal silme olarak görmek yerine, kullanıcının durum değişikliği olarak yapmayı tercih ederim. Kod miktarı azalmayacak olsa da ("ve isDeleted = '0'" vs 've "state <>' TERMINATED '"), ancak her şey çok daha makul görünecek ve farklı kullanıcı durumlarının olması normaldir. TERMINATED kullanıcılarının periyodik olarak temizlenmesi önceki
cevabımda

5

Bu soruyu doğru bir şekilde cevaplamak için önce aşağıdakilere karar vermelisiniz: "Sil" bu sistem / uygulama bağlamında ne anlama geliyor?

Cevaplamak için bu soruyu, henüz başka soruyu cevaplamak gerekir: Neden kayıtları silinir ediliyor?

Bir kullanıcının verileri silmesinin birkaç nedeni olabilir. Genellikle bir silme işleminin gerekli olabileceği tam olarak bir neden (tablo başına) olduğunu bulurum . Bazı örnekler:

  • Disk alanını geri kazanmak için;
  • Saklama / gizlilik politikasına göre kesin olarak silinmesi gerekir;
  • Bozuk / umutsuzca yanlış veriler, silinmekten ve yenilenmekten daha kolay.
  • Çoğunluk satır, örneğin bir günlük masa X kayıtları ile sınırlı / gün silinir.

Ayrıca, silmenin çok kötü nedenleri de var (daha sonra bunun nedenleri hakkında daha fazla bilgi):

  • Küçük bir hatayı düzeltmek için. Bu genellikle geliştirici tembelliğinin ve düşmanca bir kullanıcı arayüzünün altını çizer.
  • Bir işlemi "geçersiz kılmak" (örneğin, asla faturalandırılmaması gereken fatura).
  • Çünkü yapabilirsin .

Neden soruyorsun, bu gerçekten çok mu önemli? İyi ole'de sorun nedir DELETE?

  • Uzaktan paraya bağlı herhangi bir sistemde, silinme, bir arşiv / mezar taşı tablosuna taşınsa bile, her türlü muhasebe beklentisini ihlal eder. Bunu ele almanın doğru yolu geriye dönük bir olaydır .
  • Arşiv tabloları canlı şemadan ayrılma eğilimindedir. Yeni eklenen bir sütun veya basamaklamayı bile unutursanız, bu verileri kalıcı olarak kaybettiniz.
  • Sert silme, özellikle kaskadlarda çok pahalı bir işlem olabilir . Birçok kişi, birden fazla seviyenin basamaklandırılmasının (veya bazı durumlarda DBMS'ye bağlı olarak herhangi bir basamaklamanın) ayarlanan işlemler yerine kayıt düzeyinde işlemlerle sonuçlanacağının farkında değildir .
  • Tekrarlanan, sık sık sert silme indeks parçalanması sürecini hızlandırır.

Yumuşak silme daha iyidir, değil mi? Hayır gerçek değil:

  • Kaskadlar kurmak son derece zorlaşır. Neredeyse her zaman müşteriye yetim satırlar olarak görünenlerle sonuçlanırsınız.
  • Sadece bir silme işlemini takip edebilirsiniz . Satır birden çok kez silinir ve silinirse ne olur?
  • Bu, bölümleme, görünümler ve / veya filtrelenmiş dizinlerle bir şekilde azaltılabilmesine rağmen, okuma performansı düşmektedir.
  • Daha önce de belirtildiği gibi, bazı senaryolarda / yargı bölgelerinde aslında yasa dışı olabilir.

Gerçek şu ki , bu yaklaşımların her ikisi de yanlıştır. Silme yanlış. Bu soruyu gerçekten soruyorsanız , işlemler yerine mevcut durumu modellediğiniz anlamına gelir . Bu, veritabanı arazisinde kötü, kötü bir uygulamadır.

Udi Dahan bunu Silme - Sadece Yapma bölümünde yazdı . Her zaman , aslında "sil" i temsil eden bir tür görev, işlem, etkinlik veya (tercih ettiğim terim) olayı vardır. Daha sonra performans için bir "mevcut durum" tablosunda denormalize etmek istiyorsanız sorun değil, ancak işlem modelini daha önce değil, çiviledikten sonra yapın.

Bu durumda "kullanıcılarınız" olur. Kullanıcılar aslında müşteridir. Müşterilerin sizinle bir iş ilişkisi var. Bu ilişki sadece ince havaya kaybolmaz, çünkü hesaplarını iptal ederler. Gerçekten olan şey:

  • Müşteri hesap oluşturur
  • Müşteri hesabı iptal eder
  • Müşteri hesabı yeniler
  • Müşteri hesabı iptal eder
  • ...

Her durumda, aynı müşteri ve muhtemelen aynı hesaptır (yani her hesap yenilemesi yeni bir hizmet sözleşmesidir). Öyleyse neden satırları siliyorsunuz? Bunu modellemek çok kolaydır:

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

Bu kadar. Hepsi bu kadar. Hiçbir şeyi silmenize gerek yoktur. Yukarıdakiler, iyi bir esneklik derecesini barındıran oldukça yaygın bir tasarımdır, ancak biraz basitleştirebilirsiniz; "Sözleşme" düzeyine ihtiyacınız olmadığına ve yalnızca "Hesap" ın bir "Hesap Durumu" tablosuna gitmesine karar verebilirsiniz.

Uygulamanızda sıkça ihtiyaç duyulan bir şey, aktif sözleşmelerin / hesapların bir listesini almaksa, (biraz) zor bir sorgudur, ancak görünümler bunun içindir:

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
)

Ve işiniz bitti. Artık yumuşak silme işlemlerinin tüm avantajlarına sahip bir şey var, ancak hiçbir dezavantajı yok:

  • Artık kayıtlar sorun teşkil etmez, çünkü tüm kayıtlar her zaman görülebilir; sadece gerektiğinde farklı bir görünümden seçim yaparsınız.
  • "Silme" genellikle inanılmaz derecede ucuz bir işlemdir - bir olay tablosuna bir satır eklemek yeterlidir.
  • Herhangi tarihlerini kaybetme ihtimali var asla hiç , hayır berbat ne kadar kötü bir önemi.
  • Hala bir hesap sert silebilirsiniz eğer sen (gizlilik nedeniyle örneğin) gerekir ve silme temiz ve gerçekleşmesi app / veritabanının diğer kısımları müdahale edeceği bilgisiyle rahat.

Üstesinden gelinmesi gereken tek şey performans sorunudur. Birçok durumda, aslında kümelenmiş dizin nedeniyle bir sorun olmadığı ortaya çıkıyor AgreementStatus (AgreementId, EffectiveDate)- orada devam eden çok az G / Ç var. Ancak, herhangi bir sorun varsa, tetikleyicileri, dizine eklenmiş / materyalize edilmiş görünümleri, uygulama düzeyinde etkinlikleri vb. Kullanarak çözmenin yolları vardır.

Ancak performans hakkında çok erken endişelenmeyin - tasarımı doğru yapmak daha önemlidir ve bu durumda "doğru", veritabanını işlemsel bir sistem olarak kullanılması gerektiği şekilde kullanmaktır .


1

Şu anda her tablo yumuşak silme için Silinmiş bayrağı olan bir sistem ile çalışıyorum. Tüm varoluşun felaketidir. Bir kullanıcı bir tablodan bir kaydı "silebildiğinde" ilişkisel bütünlüğü tamamen bozar, ancak çocuklar o tabloya geri dönen FK'nin kademeli olarak yumuşak olarak silinmediğini kaydeder. Gerçekten zaman geçtikten sonra çöp verilerini yapar.

Bu yüzden ayrı geçmiş tabloları öneririm.


Şüphesiz basamaklı tarih değişimleri olmadan, aynı probleminiz var mı?
glenatron

Aktif kayıt tablolarınızda değil, hayır.
Jesse C. Slicer

Peki, kullanıcı geçmiş tablosuna gönderildikten sonra kullanıcı tablosundan kimlerin FK kaydettiği alt kayıtlara ne olur?
glenatron

Tetikleyiciniz (veya iş mantığı) alt kayıtları ilgili geçmiş tablolarına da gönderir. Mesele şu ki, veritabanı RI'yi kırdığınızı söylemeden üst kaydı (geçmişe gitmek için) fiziksel olarak silemezsiniz. Yani onu tasarlamak zorundasınız. Silinen bayrak basamaklı yumuşak silinmeleri zorlamaz.
Jesse C. Slicer

3
Yumuşak silme işleminizin gerçekte ne anlama geldiğine bağlıdır. Bunları yalnızca devre dışı bırakmanın bir yolu ise, devre dışı bırakılmış bir hesapla ilgili kayıtları ayarlamanıza gerek yoktur. Bana sadece veri gibi geliyor. Ve evet, tasarlamadığım bir sistemde de bununla başa çıkmak zorundayım. Hoşuna gitmen gerektiği anlamına gelmez.
JeffO

1

Masayı ikiye bölmek akla gelebilecek en saçma şey olurdu.

Tavsiye edeceğim iki çok basit adım:

  1. 'Kullanıcılar' tablosunu 'allusers' olarak yeniden adlandırın.
  2. Silinen = false 'tüm kullanıcılardan' select * olarak 'kullanıcılar' adlı bir görünüm oluşturun.

Not: Cevap vermede birkaç ay geciktiğim için üzgünüm!


0

Birisi aynı e-posta adresiyle geri geldiğinde silinen hesapları kurtarsaydınız, tüm kullanıcıları aynı tabloda tutmaya giderdim. Bu, hesap kurtarma işlemini önemsiz hale getirir.

Ancak, yeni hesaplar oluşturduğunuzda, silinen hesapları ayrı bir tabloya taşımak daha kolay olacaktır. Canlı sistem bu bilgilere ihtiyaç duymaz, bu yüzden ifşa etmeyin. Söylediğiniz gibi, daha büyük veri kümelerinde sorguları daha basit ve daha hızlı hale getirir. Daha basit kodların bakımı da daha kolaydır.


0

Kullanımda olan DBMS'den bahsetmiyorsunuz. Uygun lisansa sahip Oracle'ınız varsa, kullanıcı tablosunu iki bölüme ayırmayı düşünebilirsiniz: etkin ve silinmiş kullanıcılar.


Ardından, kullanıcıları silerken satırları bir bölümden diğerine taşımalısınız, bu kesinlikle bölümlerin kullanılması amaçlanmamıştır.
Péter Török

@ Péter: Ha? Silinen bayrak da dahil olmak üzere istediğiniz kriterlere bölüm oluşturabilirsiniz.
Aaronaught

@Aaronaught, tamam, yanlış ifade ettim. DBMS işi sizin için yapabilir, ancak yine de ekstra bir iştir (çünkü satır fiziksel olarak bir konumdan diğerine, muhtemelen farklı bir dosyaya taşınmalıdır) ve verilerin fiziksel dağılımını bozabilir.
Péter Török
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.