Bagaimana seharusnya penghapusan ditangani dalam database?

44

Saya ingin menerapkan fitur "hapus" dalam aplikasi web sehingga pengguna dapat berubah pikiran dan memulihkan catatan yang dihapus. Pikiran tentang bagaimana menerapkan ini? Beberapa opsi yang saya pertimbangkan sebenarnya menghapus catatan yang dimaksud dan menyimpan perubahan dalam tabel audit terpisah, atau tidak menghapus catatan dan menggunakan kolom "dihapus" boolean untuk menandainya sebagai dihapus. Solusi terakhir akan membutuhkan logika aplikasi tambahan untuk mengabaikan catatan "dihapus" dalam keadaan normal, tetapi akan membuatnya lebih mudah untuk mengimplementasikan memulihkan catatan di sisi aplikasi.

Abie
sumber
Saya lupa menyebutkan bahwa dalam kasus kedua catatan yang ditandai perlu dihapus atau dipindahkan setelah beberapa periode waktu yang wajar berlalu.
Abie
Database apa yang Anda gunakan?
Evan Carroll
Tabel Temporal adalah solusi terbaik untuk SQL Server 2016 dan di atas.
Sameer

Jawaban:

37

Ya, saya pasti akan memilih opsi kedua, tetapi saya akan menambahkan satu bidang lagi sebagai bidang tanggal.

Jadi, Anda menambahkan:

delete       boolean
delete_date  timestamp

Ini akan memberi Anda waktu untuk tindakan yang tidak terhapuskan.

Jika waktu kurang dari satu jam, seseorang dapat membatalkan penghapusan.

Untuk benar-benar menghapus entri yang dihapus, cukup buat prosedur tersimpan yang akan membersihkan setiap entri dengan penghapusan yang disetel ke true dan waktu lebih dari satu jam dan meletakkannya sebagai tab cron yang berjalan setiap 24 jam

Jam hanyalah sebuah contoh.

Spredzy
sumber
Atau, Anda dapat memiliki bendera lain - cleaned, atau sesuatu - yang menunjukkan bahwa data yang terkait dengan catatan ini telah dihapus dengan benar dan komprehensif. Catatan dapat dibatalkan penghapusan kecuali cleanedbenar, dalam hal ini tidak dapat dipulihkan.
Gaurav
14
Ini adalah pendekatan umum. Saya biasanya menggunakan satu bidang yang deleted_atmemegang semantik deleteboolean dan delete_datetimestamp. Jika deleted_atyaitu NULLmenangani kasus ini deleteadalah FALSEdan delete_dateadalah NULL, deleted_atmengandung pegangan timestamp kasus deleteini TRUEdan delete_dateberisi timestamp, menghemat waktu, penyimpanan dan logika aplikasi.
Julien
1
Saya suka bidang boolean dan tanggal. Bergantung pada bagaimana Anda menerapkan logika penghapusan, Anda bahkan bisa memiliki tabel berbeda yang menyimpan tanggal dan kunci unik untuk catatan yang "dihapus". Prosedur tersimpan membuat ini mudah. Dibutuhkan ruang tambahan per baris yang diperlukan hingga 1 bit vs 8+. Anda juga dapat melaporkan penghapusan per hari tanpa menyentuh tabel sumber.
AndrewSQL
Catatan: delete adalah kata yang disimpan di MySQL.
Jason Rikard
Ingatlah bahwa indeks yang difilter pada deletedbidang Anda dapat sangat meningkatkan kinerja ketika Anda meminta untuk baris yang tidak dihapus
Ross Presser
21

Dalam aplikasi kami, kami tidak benar - benar menghapus apa pun atas permintaan pengguna (klien kami berada di lingkungan yang diatur di mana menghapus sesuatu yang berpotensi menyebabkan masalah hukum).

Kami menyimpan versi lama dalam tabel audit terpisah (jadi untuk tabel some_table di mana juga merupakan tabel yang disebut some_table_audit) yang identik selain memiliki pengenal versi tambahan (stempel waktu jika DB Anda mendukung nilai waktu cukup granular, nomor versi bilangan bulat atau UUID yang merupakan kunci asing ke tabel audit umum, atau sebagainya), dan memperbarui tabel audit secara otomatis oleh pemicu (jadi kita tidak perlu membuat semua kode yang memperbarui catatan mengetahui persyaratan audit).

Cara ini:

  • operasi penghapusan hanyalah penghapusan sederhana - tidak perlu menambahkan kode tambahan apa pun untuk itu (meskipun Anda mungkin ingin merekam siapa yang meminta baris apa yang akan dihapus, meskipun sebenarnya tidak dihapus)
  • sisipan dan pembaruan juga sederhana
  • Anda dapat menerapkan hapus atau kembalikan dengan hanya mengembalikan baris "normal" ke versi lama (pemicu audit akan menyala lagi sehingga tabel jejak audit juga akan mencerminkan perubahan ini)
  • Anda dapat menawarkan kesempatan untuk meninjau atau kembali ke versi masa lalu tidak hanya membatalkan penghapusan yang terakhir
  • Anda tidak perlu menambahkan "ditandai sebagai dihapus?" memeriksa setiap titik kode yang merujuk ke tabel yang dimaksud, atau logika "perbarui salinan audit" ke setiap titik kode yang menghapus / memperbarui baris (meskipun Anda harus memutuskan apa yang harus dilakukan dengan baris yang dihapus dalam tabel audit: kami memang memiliki dihapus / tidak ditandai untuk setiap versi di sana sehingga tidak ada lubang dalam riwayat jika catatan dihapus dan kemudian dihapus)
  • menyimpan salinan audit dalam tabel terpisah berarti Anda dapat mempartisinya menjadi beberapa grup file dengan mudah.

Jika menggunakan cap waktu alih-alih (atau juga) nomor versi integer, Anda dapat menggunakan ini untuk menghapus salinan lama setelah jumlah waktu yang ditentukan jika diperlukan. Tetapi ruang disk relatif murah akhir-akhir ini, jadi kecuali kami memiliki alasan untuk menghapus data lama (yaitu peraturan perlindungan data yang mengatakan Anda harus menghapus data klien setelah X bulan / tahun) kami tidak akan melakukannya.


Jawaban ini sudah ada beberapa tahun dan beberapa hal penting yang dapat memengaruhi perencanaan semacam ini telah berubah sejak saat itu. Saya tidak akan membahas detail besar, tetapi dengan singkat untuk kepentingan orang-orang yang membaca ini hari ini:

  • SQL Server 2016 memperkenalkan "tabel temporal versi sistem" yang melakukan banyak pekerjaan ini untuk Anda, dan lebih dari itu karena beberapa gula sintaksis yang bagus disediakan untuk membuat kueri historis lebih mudah dibangun & dipelihara, dan mereka mengoordinasikan sejumlah perubahan skema di antara tabel dasar dan sejarah. Mereka bukannya tanpa peringatan, tetapi mereka adalah alat yang ampuh untuk tujuan semacam ini. Fitur serupa juga tersedia di sistem DB lainnya.

  • Perubahan pada undang-undang perlindungan data, khususnya pengenalan GDPR, dapat secara signifikan mengubah masalah kapan data harus dihapus dengan keras. Anda harus mempertimbangkan keseimbangan tidak menghapus data yang mungkin berguna (atau, memang, secara hukum diperlukan) untuk keperluan audit di kemudian hari terhadap keharusan menghormati hak-hak masyarakat (baik secara umum maupun yang secara khusus diatur dalam undang-undang yang relevan) ketika mempertimbangkan desain Anda. Ini bisa menjadi masalah dengan tabel temporal versi sistem karena Anda tidak dapat mengubah riwayat untuk membersihkan data pribadi tanpa skema perubahan jangka pendek untuk mematikan pelacakan riwayat saat Anda membuat perubahan.

David Spillett
sumber
Bagaimana Anda menangani penghapusan dan penggantian nama kolom? Setel semuanya menjadi nullable?
Stijn
1
@Stijn: Jarang struktur diubah sehingga tidak muncul banyak. Kolom umumnya tidak pernah dihapus begitu mereka ada di produksi - jika mereka berhenti digunakan cukup lepaskan semua kendala yang akan menghentikannya. NULL (atau menambahkan default untuk mengatasi kendala dengan menggunakan "nilai ajaib", meskipun itu terasa lebih kotor) dan berhenti merujuk mereka dalam kode lain. Untuk mengganti nama: tambahkan yang baru, hentikan penggunaan yang lama, dan salin data dari yang lama ke yang baru jika diperlukan. Jika Anda mengganti nama kolom, pastikan perubahan yang sama dilakukan pada tabel dasar dan audit pada saat yang bersamaan.
David Spillett
9

Dengan kolom yang dihapus boolean, Anda akan mulai mengalami masalah jika tabel Anda mulai tumbuh dan menjadi sangat besar. Saya sarankan Anda memindahkan kolom yang dihapus seminggu sekali (lebih atau kurang tergantung pada spesifikasi Anda) ke tabel yang berbeda. Dengan begitu Anda memiliki meja aktif kecil yang bagus dan yang besar yang berisi semua catatan yang dikumpulkan dari waktu ke waktu.

poelinca
sumber
7

Saya akan pergi dengan meja terpisah. Ruby on Rails memiliki acts_as_versionedplugin, yang pada dasarnya menyimpan satu baris ke tabel lain dengan postfix _versionsebelum memperbaruinya. Meskipun Anda tidak memerlukan perilaku yang tepat, itu juga harus berfungsi untuk kasus Anda (salin sebelum menghapus).

Seperti @Spredzy, saya juga merekomendasikan untuk menambahkan delete_datekolom agar dapat secara sistematis membersihkan catatan yang belum dipulihkan setelah X jam / hari / apa pun.

Michael Kohl
sumber
4

Solusi yang kami gunakan secara internal untuk masalah ini adalah memiliki kolom status dengan beberapa nilai kode keras untuk beberapa status objek tertentu: Dihapus, Aktif, Tidak Aktif, Terbuka, Tertutup, Diblokir - setiap status dengan beberapa makna yang digunakan dalam aplikasi. Dari sudut pandang db kami tidak menghapus objek, kami hanya mengubah status dan menyimpan histori untuk setiap perubahan dalam tabel objek.

Marian
sumber
3

Ketika Anda mengatakan bahwa "Solusi terakhir akan membutuhkan logika aplikasi tambahan untuk mengabaikan catatan 'dihapus'", solusi sederhana adalah memiliki tampilan yang menyaring mereka.

Peter Taylor
sumber
Ini bukan hanya masalah pandangan. Setiap operasi yang dilakukan pada set harus mengecualikan catatan "dihapus".
Abie
2

Mirip dengan apa yang disarankan Spredzy, kami menggunakan bidang stempel waktu untuk dihapus di semua aplikasi kami. Boolean berlebihan, karena cap waktu yang ditetapkan menunjukkan bahwa catatan telah dihapus. Dengan cara ini, PDO kami selalu menambah AND (deleted IS NULL OR deleted = 0)pernyataan pilih, kecuali model secara eksplisit meminta catatan yang dihapus dimasukkan.

Saat ini kami tidak mengumpulkan sampah kecuali pada tabel yang berisi gumpalan atau teks; ruang sepele jika catatan dinormalisasi dengan baik, dan pengindeksan deletedbidang membuat dampak terbatas pada kecepatan pilih.

Bryan Agee
sumber
0

Sebagai alternatif, Anda dapat menempatkan tanggung jawab pada pengguna (dan pengembang) dan pergi dengan urutan 'Apakah Anda yakin?', 'Apakah Anda yakin?' dan "Apakah Anda benar-benar yakin?" pertanyaan sebelum catatan dihapus. Agak licik tapi layak dipertimbangkan.

YaHozna
sumber
0

Saya terbiasa melihat baris tabel dengan kolom seperti 'DeletedDate' di dalamnya dan saya tidak menyukainya. Gagasan 'dihapus' adalah bahwa entri seharusnya tidak dibuat sejak awal. Secara praktis, mereka tidak dapat dihapus dari database tetapi saya tidak ingin mereka dengan data panas saya. Baris yang secara logis dihapus adalah, menurut definisi, data dingin kecuali seseorang secara spesifik ingin melihat data yang dihapus.

Selain itu, setiap permintaan yang ditulis harus secara khusus mengecualikannya dan indeks harus mempertimbangkannya juga.

Yang ingin saya lihat adalah perubahan pada tingkat arsitektur basis data dan tingkat aplikasi: buat skema yang disebut 'dihapus'. Setiap tabel yang ditentukan pengguna memiliki padanan yang identik dalam skema 'dihapus' dengan bidang tambahan yang menahan metadata - pengguna yang menghapusnya dan kapan. Kunci asing perlu dibuat.

Selanjutnya, hapus menjadi sisipan-hapus. Pertama, baris yang akan dihapus dimasukkan ke dalam skema mitra 'dihapus'. Baris yang dimaksud di tabel utama kemudian dapat dihapus. Namun, logika tambahan perlu ditambahkan di suatu tempat di sepanjang garis. Pelanggaran kunci asing dapat ditangani.

Kunci asing harus ditangani dengan benar. Ini adalah praktik yang buruk untuk menghapus baris secara logis tetapi yang primer / uniknya memiliki kolom di tabel lain yang merujuknya. Ini seharusnya tidak terjadi. Pekerjaan biasa dapat menghapus baris janda (baris yang kunci utamanya tidak memiliki referensi di tabel lain meskipun ada kunci asing. Namun, ini adalah logika bisnis.

Manfaat keseluruhan adalah pengurangan metadata dalam tabel dan peningkatan kinerja yang dimilikinya. Kolom 'deleteDate' mengatakan bahwa baris ini seharusnya tidak benar-benar ada di sini, tetapi demi kenyamanan, kita membiarkannya di sana dan membiarkan query SQL menanganinya. Jika salinan baris yang dihapus disimpan dalam skema 'dihapus', maka tabel utama dengan data panas memiliki persentase lebih tinggi dari data panas (dengan asumsi itu diarsipkan secara tepat waktu) dan lebih sedikit kolom metadata yang tidak perlu. Indeks & kueri tidak perlu lagi mempertimbangkan bidang ini. Semakin pendek ukuran baris, semakin banyak baris yang bisa dipasang ke halaman, semakin cepat SQL Server dapat bekerja.

Kerugian utama adalah ukuran operasi. Sekarang ada dua operasi bukan satu serta logika tambahan dan penanganan kesalahan. Ini dapat menyebabkan lebih banyak penguncian daripada memperbarui satu kolom jika tidak akan mengambil. Transaksi memegang kunci di atas meja lebih lama dan ada dua meja yang terlibat. Menghapus data produksi, setidaknya dalam pengalaman saya, adalah sesuatu yang jarang dilakukan. Meski begitu, di salah satu tabel utama, 7,5% dari hampir 100 juta entri memiliki entri di kolom 'DeletedDate'.

Sebagai jawaban untuk pertanyaan, aplikasi harus menyadari 'membatalkan penghapusan. Ini hanya perlu melakukan hal yang sama dalam urutan terbalik: masukkan baris dari skema 'dihapus' ke tabel utama dan kemudian hapus baris dari skema 'dihapus. Lagi-lagi beberapa logika & penanganan kesalahan tambahan diperlukan untuk memastikan untuk menghindari kesalahan, masalah dengan kunci asing dan sejenisnya.

Sean Redmond
sumber