Menangani pengguna yang dihapus - tabel terpisah atau sama?


19

Skenarionya adalah saya mendapatkan sekelompok pengguna yang terus bertambah, dan seiring berjalannya waktu, pengguna akan membatalkan akun mereka yang saat ini kami tandai sebagai 'dihapus' (dengan bendera) di tabel yang sama.

Jika pengguna dengan alamat email yang sama (itulah cara pengguna masuk) ingin membuat akun baru, mereka dapat mendaftar lagi, tetapi akun BARU dibuat. (Kami memiliki id unik untuk setiap akun, sehingga alamat email dapat digandakan di antara yang hidup dan yang dihapus).

Apa yang saya perhatikan adalah bahwa di seluruh sistem kami, dalam hal normal, kami terus-menerus meminta tabel pengguna memeriksa pengguna tidak dihapus, sedangkan yang saya pikirkan adalah bahwa kami tidak perlu melakukan itu sama sekali ... ! [Klarifikasi1: dengan 'terus-menerus menanyakan', maksud saya bahwa kami memiliki pertanyaan seperti: '... DARI pengguna DI MANA isdeleted = "0" AND ...'. Misalnya, kami mungkin perlu mengambil semua pengguna yang terdaftar untuk semua rapat pada tanggal tertentu, jadi dalam kueri ITU, kami juga memiliki DARI pengguna. DI MANA isdeleted = "0" - apakah ini membuat poin saya lebih jelas?]

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

Apa pro dan kontra dari kedua pendekatan tersebut?


Untuk alasan apa Anda menyimpan pengguna?
keppla


@keppla - ia menyebutkan bahwa: "pembukuan sejarah".
ChrisF

@ ChrisF: saya tertarik dengan ruang lingkup: apakah dia ingin menyimpan buku hanya dari pengguna, atau masih ada beberapa data yang terlampir (komentar EG, pembayaran, dll)
keppla

Mungkin membantu untuk berhenti berpikir dari mereka sebagai dihapus (yang tidak benar) dan mulai berpikir dari akun mereka sebagai dibatalkan (yang merupakan benar).
Mike Sherrill 'Cat Recall'

Jawaban:


13

(1) terus menyimpan pengguna yang dihapus di tabel pengguna 'utama'

  • Pro: kueri yang lebih sederhana dalam semua kasus
  • Cons: dapat menurunkan kinerja dari waktu ke waktu, jika ada jumlah pengguna yang tinggi

(2) menjaga pengguna yang dihapus dalam tabel terpisah (sebagian besar diperlukan untuk pembukuan historis)

Anda dapat menggunakan misalnya pemicu untuk memindahkan pengguna yang dihapus ke tabel riwayat secara otomatis.

  • Kelebihan: perawatan yang lebih sederhana untuk tabel pengguna aktif, kinerja stabil
  • Cons: membutuhkan kueri yang berbeda untuk tabel histori; namun karena sebagian besar aplikasi tidak seharusnya tertarik pada hal itu, efek negatif ini mungkin terbatas

11
Tabel partisi (di IsDeleted) akan menghapus masalah kinerja dengan menggunakan tabel tunggal.
Ian

1
@Ian kecuali setiap permintaan disediakan dengan IsDeleted sebagai kriteria permintaan (yang sepertinya tidak ada dalam pertanyaan asli), partisi bahkan dapat menyebabkan penurunan kinerja.
Adrian Shum

1
@Adrian, saya berasumsi bahwa pertanyaan yang paling umum adalah pada saat masuk dan hanya tidak ada pengguna yang dihapus yang diizinkan masuk.
Ian

1
Gunakan tampilan yang diindeks pada isdeleted jika itu menjadi masalah kinerja dan Anda ingin manfaat dari satu tabel.
JeffO

10

Saya sangat merekomendasikan menggunakan tabel yang sama. Alasan utamanya adalah integritas data. Kemungkinan besar akan ada banyak tabel dengan hubungan tergantung pada pengguna. Ketika pengguna dihapus, Anda tidak ingin meninggalkan catatan itu menjadi yatim piatu.
Memiliki catatan yatim piatu membuat penegakan kendala lebih sulit, dan membuatnya lebih sulit untuk mencari informasi historis. Perilaku lain yang perlu dipertimbangkan jika ketika pengguna memasok email bekas jika Anda ingin mereka memulihkan semua catatan lama mereka. Ini akan bekerja secara otomatis dengan menggunakan penghapusan lunak. Sejauh pengkodean, misalnya dalam aplikasi c # linq saya saat ini di mana dihapus = 0 klausa secara otomatis ditambahkan ke akhir semua pertanyaan


7

"Yang saya perhatikan adalah bahwa di seluruh sistem kami, dalam hal normal, kami terus-menerus meminta tabel pengguna memeriksa pengguna tidak dihapus"

Ini memberi saya bau desain. Anda harus menyembunyikan logika semacam itu. Misalnya, Anda harus UserServicemenyediakan metode isValidUser(userId)untuk digunakan "di seluruh sistem Anda", daripada melakukan sesuatu seperti:

+ msgstr "dapatkan catatan pengguna, periksa apakah pengguna ditandai sebagai terhapus".

Cara Anda untuk menyimpan pengguna yang dihapus tidak boleh memengaruhi logika bisnis.

Dengan semacam enkapsulasi, argumen di atas seharusnya tidak lagi mempengaruhi pendekatan kegigihan Anda. Maka Anda bisa lebih fokus pada pro dan kontra yang terkait dengan kegigihan itu sendiri.

Hal-hal yang perlu dipertimbangkan termasuk:

  • Berapa lama seharusnya catatan yang dihapus dibersihkan?
  • Berapa proporsi catatan yang dihapus?
  • Akankah ada masalah integritas referensial (mis. Pengguna dirujuk dari tabel lain) jika Anda benar-benar menghapusnya dari tabel?
  • Apakah Anda mempertimbangkan untuk membuka kembali pengguna?

Biasanya saya akan mengambil cara gabungan:

  1. Tandai catatan sebagai terhapus (untuk menyimpannya untuk persyaratan fungsional, seperti membuka kembali ac, atau memeriksa ac yang baru saja ditutup).
  2. Setelah periode yang telah ditentukan, pindahkan catatan yang dihapus ke tabel arsip (untuk tujuan pembukuan).
  3. Bersihkan setelah beberapa periode arsip yang telah ditentukan.

1
[Klarifikasi1: dengan 'terus-menerus menanyakan', maksud saya bahwa kami memiliki pertanyaan seperti: '... DARI pengguna DI MANA isdeleted = "0" AND ...'. Misalnya, kita mungkin perlu mengambil semua pengguna yang terdaftar untuk semua rapat pada tanggal tertentu, jadi dalam kueri ITU, kita juga memiliki DARI pengguna DI MANA isdeleted = "0" - apakah ini membuat poin saya lebih jelas?] @Adrian
Alan Beats

Yup jauh lebih jelas. :) Jika saya melakukan itu, saya lebih suka menjadikannya sebagai perubahan status pengguna, daripada melihatnya sebagai penghapusan fisik / logis. Meskipun jumlah kode tidak akan berkurang ("dan ​​isDeleted = '0'" vs 'dan "state <>' TERMINATED '") tetapi semuanya akan terlihat jauh lebih masuk akal, dan itu normal untuk memiliki kondisi pengguna yang berbeda juga. Pembersihan berkala terhadap pengguna TERMINASI dapat dilakukan juga, seperti yang disarankan dalam jawaban saya sebelumnya)
Adrian Shum

5

Untuk menjawab dengan benar pertanyaan ini, Anda harus terlebih dahulu memutuskan: Apa arti "hapus" dalam konteks sistem / aplikasi ini?

Untuk menjawab bahwa pertanyaan, Anda perlu menjawab pertanyaan lain: Mengapa yang catatan yang dihapus?

Ada sejumlah alasan bagus mengapa pengguna mungkin perlu menghapus data. Biasanya saya menemukan bahwa hanya ada satu alasan (per tabel) mengapa penghapusan mungkin diperlukan. Beberapa contoh adalah:

  • Untuk mendapatkan kembali ruang disk;
  • Penghapusan keras diperlukan sesuai dengan retensi / kebijakan privasi;
  • Data yang salah / putus asa, lebih mudah dihapus dan dibuat ulang daripada diperbaiki.
  • The Mayoritas dari baris yang dihapus, misalnya log meja terbatas pada catatan X / hari.

Ada juga beberapa alasan yang sangat buruk untuk penghapusan-keras (lebih lanjut tentang alasan untuk ini nanti):

  • Untuk memperbaiki kesalahan kecil. Ini biasanya menggarisbawahi kemalasan pengembang dan UI yang bermusuhan.
  • Untuk "membatalkan" transaksi (mis. Faktur yang seharusnya tidak pernah ditagih).
  • Karena kamu bisa .

Mengapa, Anda bertanya, apakah ini benar-benar masalah besar? Apa yang salah dengan ole bagus DELETE?

  • Dalam sistem apa pun yang bahkan terkait dengan uang, penghapusan-keras melanggar segala macam harapan akuntansi, bahkan jika dipindahkan ke tabel arsip / batu nisan. Cara yang benar untuk menangani ini adalah peristiwa retroaktif .
  • Tabel arsip memiliki kecenderungan untuk menyimpang dari skema langsung. Jika Anda lupa bahkan satu kolom atau kaskade yang baru ditambahkan, Anda baru saja kehilangan data itu secara permanen.
  • Penghapusan sulit bisa menjadi operasi yang sangat mahal, terutama dengan kaskade . Banyak orang tidak menyadari bahwa cascading lebih dari satu level (atau dalam beberapa kasus cascading apa pun , tergantung pada DBMS) akan menghasilkan operasi tingkat rekor alih-alih mengatur operasi.
  • Penghapusan yang sering dan berulang-ulang mempercepat proses fragmentasi indeks.

Jadi, hapus lunak lebih baik, bukan? Tidak terlalu:

  • Menyiapkan kaskade menjadi sangat sulit. Anda hampir selalu berakhir dengan apa yang tampak oleh klien sebagai baris yatim.
  • Anda hanya bisa melacak satu penghapusan. Bagaimana jika baris dihapus dan dihapus beberapa kali?
  • Baca kinerja menderita, meskipun hal ini dapat dikurangi dengan partisi, tampilan, dan / atau indeks yang difilter.
  • Seperti yang diisyaratkan sebelumnya, mungkin sebenarnya ilegal di beberapa skenario / yurisdiksi.

Yang benar adalah kedua pendekatan ini salah. Menghapus itu salah. Jika Anda benar-benar mengajukan pertanyaan ini maka itu berarti Anda memodelkan keadaan saat ini alih-alih transaksi. Ini adalah praktik buruk, buruk di basis data.

Udi Dahan menulis tentang ini di Don't Delete - Just Don't . Ada selalu semacam tugas, transaksi, aktivitas , atau (jangka pilihan saya) acara yang benar-benar mewakili "delete". Tidak apa-apa jika Anda kemudian ingin melakukan denormalisasi ke dalam tabel "kondisi saat ini" untuk kinerja, tetapi lakukan itu setelah Anda berhasil menyelesaikan model transaksional, bukan sebelumnya.

Dalam hal ini Anda memiliki "pengguna". Pengguna pada dasarnya adalah pelanggan. Pelanggan memiliki hubungan bisnis dengan Anda. Hubungan itu tidak hilang begitu saja karena mereka membatalkan akun mereka. Apa yang sebenarnya terjadi adalah:

  • Pelanggan membuat akun
  • Pelanggan membatalkan akun
  • Pelanggan memperbarui akun
  • Pelanggan membatalkan akun
  • ...

Dalam setiap kasus, itu adalah pelanggan yang sama , dan mungkin akun yang sama (yaitu setiap pembaruan akun adalah perjanjian layanan baru). Jadi mengapa Anda menghapus baris? Ini sangat mudah untuk dimodelkan:

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

Itu dia. Itu semua yang ada untuk itu. Anda tidak perlu menghapus apa pun. Di atas adalah desain yang cukup umum yang mengakomodasi tingkat fleksibilitas yang baik tetapi Anda dapat menyederhanakannya sedikit; Anda mungkin memutuskan bahwa Anda tidak memerlukan level "Perjanjian" dan cukup "Account" masuk ke tabel "AccountStatus".

Jika sering dibutuhkan dalam aplikasi Anda untuk mendapatkan daftar perjanjian / akun aktif maka itu adalah (sedikit) permintaan rumit, tapi itulah gunanya tampilan:

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
)

Dan kamu sudah selesai. Sekarang Anda memiliki sesuatu dengan semua manfaat penghapusan-lunak tetapi tidak ada kekurangan:

  • Catatan yatim adalah masalah karena semua catatan terlihat setiap saat; Anda cukup memilih dari tampilan yang berbeda kapan pun diperlukan.
  • "Menghapus" biasanya merupakan operasi yang sangat murah - hanya memasukkan satu baris ke tabel acara.
  • Tidak pernah ada kemungkinan kehilangan sejarah apapun, pernah , tidak peduli seberapa buruk Anda mengacaukan.
  • Anda masih dapat menghapus akun dengan susah payah jika perlu (misalnya karena alasan privasi), dan merasa nyaman dengan pengetahuan bahwa penghapusan akan terjadi dengan bersih dan tidak mengganggu bagian lain dari aplikasi / basis data.

Satu-satunya masalah yang tersisa untuk ditangani adalah masalah kinerja. Dalam banyak kasus itu ternyata bukan masalah karena indeks berkerumun di AgreementStatus (AgreementId, EffectiveDate)- sangat sedikit I / O mencari terjadi di sana. Tetapi jika itu pernah menjadi masalah, ada cara untuk menyelesaikannya, menggunakan pemicu, indeks / tampilan terwujud, peristiwa tingkat aplikasi, dll.

Jangan khawatir tentang kinerja terlalu dini - itu lebih penting untuk mendapatkan desain yang benar, dan "benar" dalam hal ini berarti menggunakan database cara database dimaksudkan untuk digunakan, sebagai sistem transaksional .


1

Saat ini saya sedang bekerja dengan sistem saat ini di mana setiap tabel memiliki bendera Dihapus untuk penghapusan-lunak. Ini adalah kutukan dari semua keberadaan. Ini benar-benar merusak integritas relasional ketika pengguna dapat "menghapus" catatan dari satu tabel, namun anak-anak mencatat FK yang kembali ke tabel itu tidak kaskade yang dihapus dengan lembut. Benar-benar membuat data sampah setelah waktu berlalu.

Jadi, saya sarankan tabel sejarah terpisah.


Tentunya tanpa pergeseran sejarah bertingkat, Anda memiliki masalah yang sama persis?
glenatron

Tidak dalam tabel catatan aktif Anda, tidak.
Jesse C. Slicer

Jadi apa yang terjadi pada catatan anak yang FK dari tabel pengguna setelah pengguna telah diasingkan ke tabel sejarah?
glenatron

Pemicu Anda (atau logika bisnis) akan mengirimkan catatan anak ke tabel riwayat masing-masing juga. Intinya adalah, Anda tidak dapat secara fisik menghapus catatan induk (untuk pindah ke riwayat) tanpa database yang memberi tahu Anda bahwa Anda melanggar RI. Jadi, Anda terpaksa mendesainnya. Bendera yang dihapus tidak memaksa cascading soft-delete.
Jesse C. Slicer

3
Tergantung apa arti soft delete Anda sebenarnya. Jika itu hanya cara untuk menonaktifkannya, tidak perlu menyesuaikan catatan yang terkait dengan akun yang dinonaktifkan. Sepertinya hanya data bagi saya. Dan ya, saya harus menghadapinya juga dalam sistem yang tidak saya desain. Bukan berarti Anda harus menyukainya.
JeffO

1

Memecah meja menjadi dua adalah hal paling baik yang bisa dibayangkan.

Berikut adalah dua langkah sederhana yang akan saya rekomendasikan:

  1. Ubah nama tabel 'pengguna' menjadi 'pengguna'.
  2. Buat tampilan yang disebut 'pengguna' sebagai 'pilih * dari semua pengguna yang dihapus = salah'.

PS Maaf atas keterlambatan beberapa bulan dalam menjawab!


0

Jika Anda telah memulihkan akun yang dihapus ketika seseorang kembali dengan alamat email yang sama maka saya akan pergi dengan menjaga semua pengguna di tabel yang sama. Ini akan membuat proses pemulihan akun menjadi sepele.

Namun, saat Anda membuat akun baru, mungkin akan lebih mudah untuk memindahkan akun yang dihapus ke tabel terpisah. Sistem live tidak memerlukan informasi ini jadi jangan memaparkannya. Seperti yang Anda katakan itu membuat kueri lebih sederhana dan sangat mungkin lebih cepat pada kumpulan data yang lebih besar. Kode yang lebih sederhana juga lebih mudah dipelihara.


0

Anda tidak menyebutkan DBMS sedang digunakan. Jika Anda memiliki Oracle dengan lisensi yang tepat, Anda dapat mempertimbangkan mempartisi tabel pengguna menjadi dua partisi: pengguna aktif dan yang dihapus.


Maka Anda harus memindahkan baris dari satu partisi ke partisi lain saat menghapus pengguna, yang jelas bukan bagaimana partisi dimaksudkan untuk digunakan.
Péter Török

@ Péter: Hah? Anda dapat mempartisi kriteria apa pun yang Anda inginkan, termasuk bendera yang dihapus.
Aaronaught

@Aronaught, OK, saya salah mengartikannya. DBMS dapat melakukan pekerjaan untuk Anda, tetapi masih merupakan pekerjaan tambahan (karena baris harus dipindahkan secara fisik dari satu lokasi ke lokasi lain, mungkin ke file yang berbeda), dan itu dapat memperburuk distribusi fisik data.
Péter Török
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.