Bagaimana cara mendesain database untuk menyimpan daftar yang disortir?


42

Saya mencari untuk menyimpan daftar yang diurutkan di dalam database. Saya ingin melakukan operasi berikut secara efisien.

  1. Sisipkan (x) - Sisipkan catatan x ke dalam tabel
  2. Delete (x) - Hapus record x dari tabel
  3. Sebelum (x, n) - Mengembalikan catatan 'n' sebelum catatan x dalam daftar yang diurutkan.
  4. After (x, n) - Mengembalikan catatan 'n' yang menggantikan catatan x dalam daftar yang diurutkan.
  5. First (n) - Mengembalikan catatan 'n' pertama dari daftar yang diurutkan.
  6. Terakhir (n) - Mengembalikan catatan 'n' terakhir dari daftar yang diurutkan.
  7. Bandingkan (x, y) - Diberikan dua catatan x dan y dari tabel, cari apakah x> y.

Metode sederhana yang bisa saya pikirkan adalah untuk menyimpan semacam atribut 'peringkat' dalam tabel dan permintaan dengan mengurutkan atribut tersebut. Tetapi dalam metode ini memasukkan / memodifikasi catatan dengan peringkat menjadi operasi yang mahal. Apakah ada metode yang lebih baik?

Secara khusus, saya ingin mengimplementasikan tabel menggunakan SimpleDB Amazon. Tetapi jawaban umum untuk database relasional juga harus membantu.

Perbarui profil yang dimuat:

Karena saya merencanakan ini untuk aplikasi web, itu tergantung pada jumlah pengguna yang menggunakan aplikasi.

Jika ada 100k pengguna aktif (super optimisme: P), maka perkiraan saya yang sangat per hari akan menjadi

500k memilih, 100k menyisipkan dan menghapus, pembaruan 500k

Saya berharap meja tumbuh total hingga 500 ribu.

Saya mencari untuk mengoptimalkan pada pembaruan, masukkan dan operasi Bandingkan. Peringkat item akan terus berubah dan saya harus terus memperbarui tabel.


Uraikan sedikit tentang profil muatan yang Anda harapkan. Berapa banyak pilihan / sisipan / pembaruan per hari? Operasi apa yang paling Anda inginkan untuk dioptimalkan? Seberapa besar harapan Anda terhadap pertumbuhan meja per hari atau totalnya?
Nick Chammas

Apakah ini untuk papan peringkat pemain? Ngomong-ngomong, saya telah memperbarui jawaban saya di bawah ini dengan umpan balik berdasarkan profil beban yang Anda proyeksikan.
Nick Chammas

tidak, itu bukan papan peringkat pemain.
chitti

Pendekatan apa yang akhirnya Anda gunakan?
Nick Chammas

Saya bahkan tidak yakin dengan apa yang ditanyakan di sini atau apa yang tidak perlu Anda lakukan dari daftar cucian yang perlu Anda lakukan.
Evan Carroll

Jawaban:


22

Jika peringkat tidak sepenuhnya arbitrer tetapi dapat diturunkan dari beberapa properti lain (mis. Nama, skor pemain, dll.) Maka perhatikan baik-baik jawaban Joel .

Jika itu adalah properti sewenang-wenang dari data Anda, maka itu harus disimpan sebagai kolom di tabel catatan Anda. Dengan asumsi SimpleDB Amazon mirip dengan RDBMS biasa, Anda kemudian dapat mengindeks kolom ini dan dengan cepat memenuhi semua pertanyaan Anda di atas dengan strategi pengindeksan yang sesuai. Ini normal untuk RDBMS.

Mengingat Anda mengharapkan aktivitas memasukkan dan memperbarui yang tinggi, tetapi juga aktivitas membaca yang relatif tinggi, saya sarankan melakukan hal berikut:

  • Klasterkan tabel pada peringkat, terutama jika sebagian besar pertanyaan Anda menentang peringkat. Jika tidak, atau jika memilih kunci pengelompokan tidak tersedia di SimpleDB, maka buat saja indeks dengan peringkat sebagai kolom utama. Ini akan memuaskan kueri 3-6.
  • Indeks pada catatan pertama dan kemudian peringkat (atau, di dunia SQL Server, hanya merekam dan INCLUDE-ing peringkat, atau hanya merekam jika Anda sudah mengelompokkan pada peringkat) akan memenuhi permintaan 7.
  • Operasi 1 dan 2 dapat dioptimalkan dengan membuat spasi data Anda secara tepat (mis. Pengaturan FILLFACTORdalam SQL Server). Ini sangat penting jika Anda mengelompokkan berdasarkan peringkat.
  • Saat Anda menyisipkan atau memperbarui peringkat, pertahankan sebanyak mungkin celah di antara jumlah peringkat untuk meminimalkan kemungkinan bahwa Anda perlu memeringkat ulang catatan yang ada untuk mengakomodasi penyisipan atau pembaruan peringkat. Sebagai contoh, jika Anda memberi peringkat catatan Anda dalam langkah-langkah 1000 Anda meninggalkan ruang yang cukup untuk sekitar setengah dari banyak perubahan dan menyisipkan dengan kesempatan minimal Anda harus peringkat ulang catatan yang tidak terlibat langsung dalam perubahan itu.
  • Setiap malam ulang peringkat semua catatan untuk mengatur ulang kesenjangan peringkat di antara mereka.
  • Anda dapat menyetel frekuensi peringkat ulang massa serta ukuran kesenjangan peringkat untuk mengakomodasi jumlah insert atau pembaruan yang Anda harapkan relatif terhadap jumlah catatan yang ada. Jadi, jika Anda memiliki catatan 100 ribu dan mengharapkan sisipan dan pembaruan Anda menjadi 10% dari itu, sisakan ruang yang cukup untuk 10 ribu peringkat baru dan ulang peringkat malam.
  • Merangking ulang rekam 500 ribu adalah operasi yang mahal, tetapi dilakukan sekali sehari atau seminggu di luar jam kerja tidak masalah untuk database seperti itu. Pemeringkatan kembali massa secara off-jam ini untuk menjaga kesenjangan peringkat adalah apa yang menyelamatkan Anda karena harus menentukan peringkat ulang banyak catatan untuk setiap pembaruan peringkat atau menyisipkan selama jam-jam normal dan puncak Anda.

Jika Anda mengharapkan 100K + membaca tabel berukuran 100K + saya tidak merekomendasikan menggunakan pendekatan daftar tertaut. Itu tidak akan skala dengan baik untuk ukuran-ukuran itu.


Peringkat dapat dimodifikasi. Saya berharap peringkat akan terus berubah dan catatan baru dimasukkan terus-menerus. Saya khawatir tentang kasus ketika saya memasukkan elemen baru dengan peringkat maka peringkat semua catatan di bawah catatan baru dalam urutan harus diubah. Bukankah itu operasi yang mahal ketika saya memiliki ribuan catatan di database saya?
chitti

@ Chitti - Ah, itu masalah. Anda dapat menentukan peringkat Anda (mis. 0, 1000, 2000, 3000, ...) dan secara berkala memberi peringkat ulang semua catatan saat kesenjangan peringkat terisi. Ini tidak akan skala jika Anda mengharapkan lebih dari beberapa puluh ribu catatan, meskipun.
Nick Chammas

1
@ Chitti - Ini agak lucu, sebenarnya. Ini persis masalah yang dihadapi oleh mesin database saat pengindeksan data, karena mereka memesan dan memesan ulang saat data ditambahkan atau diubah. Jika Anda melihat ke atas, FILLFACTORAnda akan melihatnya pada dasarnya dimaksudkan untuk membuat ruang ekstra untuk catatan dalam indeks, sama seperti kesenjangan peringkat yang saya jelaskan membuat ruang untuk perubahan dan penyisipan peringkat.
Nick Chammas

2
Terima kasih atas jawaban yang diperbarui. 'Pangkat' adalah properti sewenang-wenang dari data saya. Saya hampir yakin bahwa kolom indeks kustom adalah yang saya butuhkan. Lihatlah tautan SO ini dengan pertanyaan serupa. Jawaban teratas memberikan rekomendasi tentang bagaimana menangani kolom peringkat tersebut.
chitti

@chitti - Jawaban yang diterima untuk pertanyaan SO itu bagus. Ini menyarankan pendekatan yang sama yang telah saya jelaskan di sini, dengan saran tambahan menggunakan desimal alih-alih bilangan bulat untuk memperluas fleksibilitas Anda dalam menetapkan dan mengubah peringkat. Great ditemukan.
Nick Chammas

13

Saya biasanya menggunakan metode "peringkat" yang Anda jelaskan. Daripada main-main dengan memperbarui baris ketika item perlu dipesan ulang saya sering bisa lolos dengan menghapus semua catatan dalam daftar dan memasukkan kembali item baru dalam urutan yang tepat. Metode ini jelas dioptimalkan untuk pengambilan.

Pendekatan alternatif adalah memodelkan catatan sebagai daftar tertaut dengan menggunakan kolom kunci asing refleksif "pendahulu" pada tabel:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Anda dapat dengan mudah mengambil daftar dan menambah dan menghapus item dengan sedikit overhead, tetapi mengeluarkan catatan dalam urutan yang tepat akan sulit. Mungkin ada cara cerdas untuk melakukannya dalam satu permintaan, mungkin dengan banyak gabungan tabel alias.

Saya menggunakan pendekatan terakhir ini sering ketika saya memodelkan hubungan gaya pohon (kategori, folder, set dan himpunan bagian). Saya biasanya memiliki fungsi rekursif semacam untuk merekonstruksi pohon lengkap dalam aplikasi saya.


2
Model daftar tertaut rapi. Untuk mengambil hierarki tersebut dalam urutan di SQL Server Anda akan menggunakan CTE rekursif .
Nick Chammas

Membangun hierarki itu akan cukup mahal untuk sebuah meja tinggi. Keuntungannya adalah perubahan peringkat / sisipan / dll dapat dibuat dengan mudah. Tergantung pada profil muatan yang diharapkan dari chitti, ini mungkin sebenarnya merupakan pendekatan terbaik.
Nick Chammas

Opsi daftar tertaut tampak seperti ide terbaik untuk semua operasi kecuali Bandingkan. Adakah yang tahu bagaimana saya akan mengimplementasikan Bandingkan tanpa harus melacak jalur antara dua elemen yang dibandingkan?
chitti

Jika Anda memiliki ID item yang saya pikir Bandingkan () akan langsung, kecuali saya salah mengerti apa yang Anda maksud dengan Bandingkan (). Ketika Anda berkata: "cari jika x> y", maksud Anda "temukan jika x mendahului y"? Saya tidak dapat melihat bahwa menjadi mudah tanpa indeks kustom atau prosedur tersimpan yang akan berjalan dalam daftar (atau fitur CTE menarik yang disebutkan oleh @Nick).
bpanulla

5
Jenis solusi ini juga mendekati model data grafik ( en.wikipedia.org/wiki/Graph_theory ). Sistem penyimpanan yang dioptimalkan untuk menyimpan simpul dan tepi grafik mungkin merupakan solusi yang lebih baik daripada RDBMS. Triple-and-Quad-store dan basis data grafik seperti Neo4J cukup bagus dalam hal ini.
bpanulla

6

Saya akan berpikir hal yang harus dilakukan adalah menyimpan properti atau properti yang digunakan untuk menghitung peringkat dan kemudian membangun indeks di atasnya. Daripada mencoba memaksa database untuk secara fisik menyimpan data dalam urutan peringkat atau menggunakan daftar tertaut yang dikelola secara manual, mengapa tidak membiarkan mesin database melakukan apa yang dirancang untuk dilakukan?


2
Bagaimana jika 'properti yang digunakan untuk menghitung peringkat' adalah arbitrer? Misalnya: Satu set entri keranjang belanja yang disusun ulang berdasarkan tindakan sewenang-wenang pengguna.
chitti

Ketika Anda mengatakan pangkat itu sewenang-wenang, apa maksud Anda? Harus ada algoritma yang Anda gunakan untuk menghitung peringkat apa yang seharusnya. Misalnya: "berdasarkan entri keranjang belanja" - Berdasarkan bagaimana? Pasti ada sesuatu yang tersimpan di database yang merupakan driver untuk perhitungan peringkat. Ini mungkin kombinasi dari beberapa hal, tetapi hal-hal ini entah bagaimana harus disimpan dalam tabel pelanggan atau dalam tabel yang terkait dengan pelanggan. Jika ada dalam data maka Anda dapat membuat fungsi yang menghitungnya. Jika Anda dapat menghitungnya, Anda dapat menyimpannya dan mengindeksnya.
Joel Brown

Katakanlah kita perlu menjaga urutan barang dalam keranjang belanja dan pesanan dapat 'diubah' oleh pengguna menggunakan web ui. Bagaimana Anda menyimpan daftar barang seperti itu dalam database dan bagaimana Anda mempertahankan urutan sortir?
chitti

Jika saya memahami Anda dengan benar, dengan "mengubah sewenang-wenang" urutan item dalam keranjang belanja, Anda berarti bahwa pengguna dapat menyeret item ke atas dan ke bawah dalam daftar dan menjatuhkannya ke tempat yang mereka inginkan. Saya kira itu menurut saya sedikit dibuat-buat. Mengapa pengguna melakukan itu? Jika mereka bisa melakukannya, apakah mereka akan sering melakukannya? Apakah menggunakan urutan item yang sederhana dalam keranjang benar-benar mementingkan kinerja? Sepertinya saya bahwa nomor urut dari satu ke jumlah item dalam kereta + FK ke pesanan akan memberi Anda indeks yang Anda butuhkan. Perbarui saja item-item ketika seseorang terseret.
Joel Brown

3
Keranjang belanja hanyalah sebuah contoh yang saya berikan untuk menunjukkan bahwa ada kasus di mana 'peringkat' dapat berubah-ubah. Mungkin itu bukan contoh yang bagus. Antrian dvd Netflix bisa menjadi contoh yang lebih baik. Demi argumen, bayangkan antrean netflix dengan item 100k yang dapat dipesan ulang secara sewenang-wenang oleh pengguna dan ia melakukannya setiap satu menit. Bagaimana Anda merancang basis data untuk menyimpan daftar film yang dipesan dalam aplikasi hipotetis ini?
chitti

1

Ini adalah keterbatasan non-RDBMS seperti simpleDB. Fitur yang Anda butuhkan tidak dapat diimplementasikan pada sisi DB di simpleDB, mereka harus diimplementasikan dari sisi pemrograman / aplikasi.

Untuk RDBMS seperti SQL server, fitur yang Anda butuhkan belum sempurna untuk indeks berkerumun.

  • Sisipkan (x) - Sisipkan catatan x ke tabel> Sisipkan sederhana.
  • Hapus (x) - Hapus catatan x dari tabel> Hapus sederhana.
  • Sebelum (x, n) - Mengembalikan catatan 'n' sebelum catatan x dalam daftar yang diurutkan. > Pilih n atas hasil di mana x kurang dari nilai dan urutan dengan klausa.

  • After (x, n) - Mengembalikan catatan 'n' yang menggantikan catatan x dalam daftar yang diurutkan. > Pilih n atas hasil di mana x lebih besar dari nilai dan urutan dengan klausa.

  • First (n) - Mengembalikan catatan 'n' pertama dari daftar yang diurutkan. > Pilih n hasil atas.

  • Terakhir (n) - Mengembalikan catatan 'n' terakhir dari daftar yang diurutkan. > Pilih n atas hasil setelah pesanan oleh desc.

  • Bandingkan (x, y) - Diberikan dua catatan x dan y dari tabel, cari apakah x> y. > Pernyataan TSQL IF.

SimpleDB memang menyediakan indeks otomatis, pengurutan dan bahasa permintaan dasar . Masalah saya akan tetap ada walaupun saya memilih RDBMS. Masalahnya adalah karena peringkat data dalam basis data saya berubah secara sewenang-wenang dan mereka tidak dapat ditangkap sebagai properti tunggal (kecuali saya menggunakan kolom peringkat kustom) yang dapat diindeks.
chitti

0

Inilah yang saya gunakan untuk memberi peringkat ulang tabel Postgres saya setelah setiap sisipan:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

Untuk kasus penggunaan saya, kinerja bukan masalah, tetapi keyakinan bahwa itu tidak akan pernah rusak atau bertindak aneh adalah penting.

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.