Versi mengendalikan konten basis data


16

Saya sedang mengerjakan proyek web yang melibatkan konten yang dapat diedit pengguna, dan saya ingin dapat melakukan pelacakan versi dari konten aktual, yang hidup dalam database. Pada dasarnya, saya ingin mengimplementasikan histori perubahan gaya wiki.

Melakukan riset latar belakang, saya melihat banyak dokumentasi tentang bagaimana versi skema database Anda (saya sebenarnya sudah dikendalikan), tetapi setiap strategi yang ada tentang bagaimana melacak perubahan konten database Anda hilang dalam longsoran barang skema versi, setidaknya dalam pencarian saya.

Saya bisa memikirkan beberapa cara untuk menerapkan pelacakan perubahan saya sendiri, tetapi semuanya tampak agak kasar:

  • Simpan seluruh baris pada setiap perubahan, hubungkan kembali baris ke sumber id dengan kunci Primer (apa yang saya condong ke arah saat ini, itu yang paling sederhana). Namun, banyak perubahan kecil dapat menghasilkan banyak mengasapi meja.
  • simpan sebelum / sesudah / pengguna / cap waktu untuk setiap perubahan, dengan nama kolom untuk menghubungkan perubahan kembali ke kolom yang relevan.
  • simpan sebelum / sesudah / pengguna / cap waktu dengan tabel untuk setiap kolom (akan menghasilkan terlalu banyak tabel).
  • simpan diffs / user / timestamp untuk setiap perubahan dengan sebuah kolom (ini berarti Anda harus mengikuti seluruh sejarah perubahan yang terkait untuk kembali ke tanggal tertentu).

Apa pendekatan terbaik di sini? Menggulirkan sendiri sepertinya saya mungkin menemukan kembali basis kode (lebih baik) orang lain.


Poin bonus untuk PostgreSQL.


Pertanyaan ini telah dibahas pada SO: stackoverflow.com/questions/3874199/… . Google untuk "riwayat catatan basis data", dan Anda akan menemukan beberapa artikel lagi.
Doc Brown

1
Kedengarannya seperti kandidat yang ideal untuk Acara Sourcing
James

Mengapa tidak menggunakan log transaksi SQL-Server untuk melakukan trik?
Thomas Junk

Jawaban:


11

Teknik yang biasanya saya gunakan adalah menyimpan catatan lengkap, dengan bidang end_timestamp. Ada aturan bisnis yang hanya satu baris yang dapat memiliki end_timestamp nol, dan ini tentu saja konten yang sedang aktif.

Jika Anda mengadopsi sistem ini, saya sangat menyarankan Anda menambahkan indeks atau batasan untuk menegakkan aturan. Ini mudah dengan Oracle, karena indeks unik dapat berisi satu dan hanya satu nol. Basis data lain mungkin lebih menjadi masalah. Memiliki database menegakkan aturan akan menjaga kode Anda jujur.

Anda cukup benar bahwa banyak perubahan kecil akan membuat mengasapi, tetapi Anda harus menukarkannya dengan kode dan melaporkan kesederhanaan.


Perhatikan bahwa mesin database lain mungkin berperilaku berbeda, misalnya MySQL memungkinkan beberapa nilai NULL dalam kolom dengan indeks unik. Ini membuat kendala ini jauh lebih sulit untuk ditegakkan.
qbd

Menggunakan stempel waktu yang sebenarnya tidak aman, tetapi beberapa database MVCC bekerja secara internal dengan menyimpan nomor seri transaksi minimum dan maksimum bersama dengan tupel.
user2313838

"Ini mudah dengan Oracle, karena indeks unik dapat berisi satu dan hanya satu nol". Salah. Oracle tidak memasukkan nilai nol dalam indeks sama sekali. Tidak ada batasan jumlah nol dalam kolom dengan indeks unik.
Gerrat

@ Gerrat Ini adalah beberapa tahun sejak saya merancang database yang memiliki persyaratan ini, dan saya tidak lagi memiliki akses ke database itu. Anda benar bahwa indeks unik standar dapat mendukung banyak null, tetapi saya pikir kami menggunakan batasan unik atau mungkin indeks fungsional.
kiwiron

8

Perhatikan bahwa jika Anda menggunakan Microsoft SQL Server, sudah ada fitur untuk yang disebut Ubah Data Capture . Anda masih perlu menulis kode untuk diakses revisi sebelumnya nanti (CDC membuat tampilan spesifik untuk itu), tetapi setidaknya Anda tidak perlu mengubah skema tabel Anda, atau mengimplementasikan pelacakan perubahan itu sendiri.

Di bawah tenda , yang terjadi adalah:

  • CDC membuat tabel tambahan yang berisi revisi,

  • Tabel asli Anda digunakan seperti sebelumnya, yaitu setiap pembaruan tercermin dalam tabel ini secara langsung,

  • Tabel CDC hanya menyimpan nilai yang diubah, yang berarti bahwa duplikasi data dijaga agar tetap minimum.

Fakta bahwa perubahan disimpan dalam tabel yang berbeda memiliki dua konsekuensi utama:

  • Memilih dari tabel asli secepat tanpa CDC. Jika saya ingat dengan baik, CDC terjadi setelah pembaruan, jadi pembaruan sama cepatnya (walaupun saya tidak ingat betul bagaimana CDC mengelola konsistensi data).

  • Beberapa perubahan pada skema tabel asli mengarah ke penghapusan CDC. Misalnya, jika Anda menambahkan kolom, CDC tidak tahu bagaimana mengatasinya. Di sisi lain, menambahkan indeks atau batasan harus baik-baik saja. Ini dengan cepat menjadi masalah jika Anda mengaktifkan CDC di atas meja yang sering mengalami perubahan. Mungkin ada solusi yang memungkinkan untuk mengubah skema tanpa kehilangan CDC, tapi saya belum mencarinya.


6

Pecahkan masalah "secara filosofis" dan dalam kode terlebih dahulu. Dan kemudian "bernegosiasi" dengan kode dan basis data untuk mewujudkannya.

Sebagai contoh , jika Anda berurusan dengan artikel umum, konsep awal untuk artikel mungkin terlihat seperti ini:

class Article {
  public Int32 Id;
  public String Body;
}

Dan pada tingkat paling dasar berikutnya, saya ingin menyimpan daftar revisi:

class Article {
  public Int32 Id;
  public String Body;
  public List<String> Revisions;
}

Dan mungkin saya sadar bahwa tubuh saat ini hanyalah revisi terbaru. Dan itu berarti dua hal: Saya perlu setiap Revisi diberi tanggal atau nomor:

class Revision {
  public Int32 Id;
  public Article ParentArticle;
  public DateTime Created;
  public String Body;
}

Dan ... dan badan artikel saat ini tidak perlu berbeda dari revisi terbaru:

class Article {
  public Int32 Id;
  public String Body {
    get {
      return (Revisions.OrderByDesc(r => r.Created))[0];
    }
    set {
      Revisions.Add(new Revision(value));
    }
  }
  public List<Revision> Revisions;
}

Beberapa detail hilang; tetapi ini menggambarkan bahwa Anda mungkin menginginkan dua entitas . Satu mewakili artikel (atau tipe header lainnya), dan yang lainnya adalah daftar revisi (pengelompokan bidang apa pun yang masuk akal "filosofis" baik untuk dikelompokkan). Anda tidak perlu kendala basis data khusus pada awalnya, karena kode Anda tidak mempedulikan revisi apa pun di dalamnya - itu adalah properti dari artikel yang tahu tentang revisi.

Jadi, Anda tidak perlu khawatir tentang menandai revisi dengan cara khusus atau bersandar pada batasan basis data untuk menandai artikel "saat ini". Anda cukup cap waktu mereka (bahkan ID otomatis akan baik-baik saja), buat mereka terkait dengan Artikel induknya, dan biarkan artikel yang bertanggung jawab untuk mengetahui yang "terbaru" adalah yang paling relevan.

Dan Anda membiarkan ORM menangani detail yang kurang filosofis - atau Anda menyembunyikannya di kelas utilitas khusus jika Anda tidak menggunakan ORM out-of-the-box.

Jauh kemudian, setelah Anda melakukan beberapa pengujian stres, Anda mungkin berpikir untuk membuat properti revisi itu menjadi malas-load, atau membuat atribut Tubuh Anda malas memuat hanya revisi teratas. Namun, struktur data Anda dalam hal ini tidak harus berubah untuk mengakomodasi optimasi tersebut.


2

Ada halaman wiki PostgreSQL untuk pemicu pelacakan audit yang menuntun Anda bagaimana mengatur log audit yang akan melakukan apa yang Anda butuhkan.

Ini melacak data asli lengkap dari perubahan, serta daftar nilai baru untuk pembaruan (untuk memasukkan dan menghapus, hanya ada satu nilai). Jika Anda ingin mengembalikan versi lama, Anda dapat mengambil salinan data asli dari catatan audit. Perhatikan bahwa jika data Anda melibatkan kunci asing, catatan itu mungkin juga harus diputar kembali untuk mempertahankan konsistensi.

Secara umum, jika aplikasi database Anda menghabiskan sebagian besar waktunya hanya pada data saat ini, saya pikir Anda lebih baik melacak versi alternatif dalam tabel terpisah dari data saat ini. Ini akan membuat indeks tabel aktif Anda lebih mudah dikelola.

Jika baris yang Anda lacak sangat besar dan ruang menjadi masalah serius, Anda bisa mencoba memecah perubahan dan menyimpan perbedaan minimal / tambalan, tapi itu pasti lebih banyak pekerjaan untuk mencakup semua jenis data Anda. Saya telah melakukan ini sebelumnya, dan itu menyakitkan untuk membangun kembali versi data lama dengan berjalan melalui semua perubahan ke belakang, satu per satu.


1

Yah, saya akhirnya hanya pergi dengan opsi paling sederhana, pemicu yang menyalin versi lama baris ke log sejarah per-tabel.

Jika saya berakhir dengan mengasapi basis data terlalu banyak, saya dapat melihat kemungkinan runtuh beberapa perubahan sejarah kecil, jika diperlukan.

Solusinya akhirnya menjadi agak berantakan, karena saya ingin menghasilkan fungsi pemicu secara otomatis. Saya SQLAlchemy, jadi saya bisa menghasilkan tabel sejarah dengan melakukan beberapa hijinks warisan, yang bagus, tetapi fungsi pemicu sebenarnya memerlukan beberapa string munging untuk menghasilkan fungsi PostgreSQL dengan benar, dan memetakan kolom dari satu tabel ke lain dengan benar.

Ngomong-ngomong, semuanya ada di github di sini .

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.