Hubungan MongoDB: embed atau referensi?

524

Saya baru mengenal MongoDB - berasal dari latar belakang basis data relasional. Saya ingin merancang struktur pertanyaan dengan beberapa komentar, tetapi saya tidak tahu hubungan mana yang digunakan untuk komentar: embedatau reference?

Sebuah pertanyaan dengan beberapa komentar, seperti stackoverflow , akan memiliki struktur seperti ini:

Question
    title = 'aaa'
    content = bbb'
    comments = ???

Pada awalnya, saya ingin menggunakan komentar yang diembed (saya pikir embeddirekomendasikan di MongoDB), seperti ini:

Question
    title = 'aaa'
    content = 'bbb'
    comments = [ { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'} ]

Jelas, tetapi saya khawatir tentang kasus ini: Jika saya ingin mengedit komentar yang ditentukan, bagaimana cara saya mendapatkan konten dan pertanyaannya? Tidak ada _iduntuk membiarkan saya menemukan satu, atau question_refmembiarkan saya menemukan pertanyaannya. (Saya sangat pemula, bahwa saya tidak tahu apakah ada cara untuk melakukan ini tanpa _iddan question_ref.)

Apakah saya harus menggunakan refbukan embed? Lalu saya harus membuat koleksi baru untuk komentar?

Freewind
sumber
Semua objek Mongo dibuat dengan _ID, apakah Anda membuat bidang atau tidak. Jadi secara teknis setiap komentar masih akan memiliki ID.
Robbie Guilfoyle
25
@RobbieGuilfoyle tidak benar - lihat stackoverflow.com/a/11263912/347455
pennstatephil
13
Saya berdiri dikoreksi, terima kasih @pennstatephil :)
Robbie Guilfoyle
4
Maksudnya adalah bahwa semua objek luwak dibuat dengan _id untuk mereka yang menggunakan framework ini - lihat subdocs luwak
Luca Steeb
1
Buku yang sangat bagus untuk mempelajari hubungan mongo db adalah "Pola Desain Terapan MongoDB - O'Reilly". Bab satu, bicara tentang keputusan ini, untuk menanamkan atau referensi?
Felipe Toledo

Jawaban:

769

Ini lebih merupakan seni daripada sains. The Mongo Dokumentasi Skema adalah referensi yang baik, tapi di sini ada beberapa hal yang perlu dipertimbangkan:

  • Masukkan sebanyak mungkin

    Kegembiraan dari basis data dokumen adalah ia menghilangkan banyak Joins. Insting pertama Anda adalah menempatkan sebanyak mungkin dalam satu dokumen. Karena dokumen MongoDB memiliki struktur, dan karena Anda dapat secara efisien meminta dalam struktur itu (ini berarti Anda dapat mengambil bagian dari dokumen yang Anda butuhkan, sehingga ukuran dokumen tidak perlu terlalu mengkhawatirkan Anda), tidak perlu segera menormalkan data seperti Anda lakukan dalam SQL. Khususnya setiap data yang tidak berguna selain dari dokumen induknya harus menjadi bagian dari dokumen yang sama.

  • Pisahkan data yang dapat dirujuk dari beberapa tempat ke dalam koleksinya sendiri.

    Ini bukan masalah "ruang penyimpanan" karena ini adalah masalah "konsistensi data". Jika banyak catatan akan merujuk ke data yang sama, itu lebih efisien dan lebih sedikit kesalahan untuk memperbarui satu catatan dan menyimpan referensi di tempat lain.

  • Pertimbangan ukuran dokumen

    MongoDB memaksakan batas ukuran 4MB (16MB dengan 1,8) pada satu dokumen. Dalam dunia GB data ini terdengar kecil, tetapi juga 30 ribu tweets atau 250 jawaban Stack Overflow atau 20 foto yang berkedip. Di sisi lain, ini jauh lebih banyak informasi daripada yang mungkin ingin disajikan pada satu waktu di halaman web biasa. Pertama pertimbangkan apa yang akan membuat pertanyaan Anda lebih mudah. Dalam banyak kasus kekhawatiran tentang ukuran dokumen akan menjadi optimasi prematur.

  • Struktur data yang kompleks:

    MongoDB dapat menyimpan struktur data bersarang dalam yang sewenang-wenang, tetapi tidak dapat mencarinya secara efisien. Jika data Anda membentuk pohon, hutan atau grafik, Anda secara efektif perlu menyimpan setiap node dan ujung-ujungnya dalam dokumen terpisah. (Perhatikan bahwa ada penyimpanan data yang dirancang khusus untuk jenis data yang harus dipertimbangkan juga)

    Itu juga telah ditunjukkan daripada tidak mungkin untuk mengembalikan subset elemen dalam dokumen. Jika Anda perlu mengambil dan memilih beberapa bit dari setiap dokumen, akan lebih mudah untuk memisahkannya.

  • Konsistensi Data

    MongoDB membuat trade off antara efisiensi dan konsistensi. Aturannya adalah perubahan pada satu dokumen selalu bersifat atomik, sementara pembaruan untuk banyak dokumen tidak boleh dianggap sebagai atom. Juga tidak ada cara untuk "mengunci" catatan di server (Anda dapat membangun ini ke dalam logika klien menggunakan misalnya bidang "kunci"). Saat Anda merancang skema Anda, pertimbangkan bagaimana Anda akan menjaga data Anda konsisten. Secara umum, semakin banyak yang Anda simpan dalam dokumen, semakin baik.

Untuk apa yang Anda gambarkan, saya akan menyematkan komentar, dan memberikan setiap kolom komentar id dengan ObjectID. ObjectID memiliki cap waktu yang tertanam di dalamnya sehingga Anda dapat menggunakannya alih-alih dibuat kapan saja.

John F. Miller
sumber
1
Saya ingin menambahkan ke pertanyaan OP: Model komentar saya berisi nama pengguna dan tautan ke avatar-nya. Apa yang akan menjadi pendekatan terbaik, mengingat pengguna dapat memodifikasi namanya / avatar?
user1102018
5
Mengenai 'Struktur data yang kompleks', tampaknya mungkin untuk mengembalikan subset elemen dalam dokumen menggunakan kerangka agregasi (coba $ bersantai).
Eyal Roth
4
Errr, Teknik ini tidak mungkin atau tidak banyak dikenal di MongoDB pada awal 2012. Mengingat popularitas pertanyaan ini, saya akan mendorong Anda untuk menulis jawaban Anda sendiri yang diperbarui. Saya khawatir saya telah menjauh dari pengembangan aktif pada MongoDB dan saya tidak dalam posisi yang baik untuk menanggapi komentar Anda dalam posting asli saya.
John F. Miller
54
16MB = 30 juta tweet? Apakah ini sekitar 0,5 byte per tweet ?!
Paolo
8
Ya, sepertinya saya tidak aktif dengan faktor 1000 dan beberapa orang menganggap ini penting. Saya akan mengedit posting. WRT 560bytes per tweet, ketika saya hafal ini di 2011 twitter masih terikat dengan pesan teks dan string Ruby 1.4; dengan kata lain masih ASCII karakter saja.
John F. Miller
39

Secara umum, embed baik jika Anda memiliki hubungan satu-ke-satu atau satu-ke-banyak, dan referensi baik jika Anda memiliki banyak-ke-banyak hubungan.

ywang1724
sumber
10
dapatkah Anda menambahkan tautan referensi? Terima kasih.
db80
Bagaimana Anda menemukan komentar spesifik dengan desain satu ke banyak ini?
Mauricio Pastorini
29

Jika saya ingin mengedit komentar tertentu, bagaimana cara mendapatkan konten dan pertanyaannya?

Anda dapat meminta berdasarkan sub-dokumen: db.question.find({'comments.content' : 'xxx'}) .

Ini akan mengembalikan seluruh dokumen Pertanyaan. Untuk mengedit komentar yang ditentukan, Anda kemudian harus menemukan komentar pada klien, melakukan edit dan menyimpannya kembali ke DB.

Secara umum, jika dokumen Anda berisi array objek, Anda akan menemukan bahwa sub-objek tersebut perlu dimodifikasi sisi klien.

Gates VP
sumber
4
ini tidak akan berfungsi jika dua komentar memiliki konten yang identik. orang mungkin berpendapat bahwa kita juga bisa menambahkan penulis ke permintaan pencarian, yang masih tidak akan berhasil jika penulis membuat dua komentar yang identik dengan konten yang sama
Steel Brain
@SteelBrain: jika dia menyimpan indeks komentar, notasi titik mungkin membantu. lihat stackoverflow.com/a/33284416/1587329
serv-inc
13
Saya tidak mengerti bagaimana jawaban ini memiliki 34 upvotes, banyak orang kedua berkomentar hal yang sama seluruh sistem akan rusak. Ini adalah desain yang benar-benar mengerikan dan tidak boleh digunakan. Cara @user melakukannya adalah cara untuk pergi
user2073973
21

Yah, saya agak terlambat tetapi masih ingin berbagi cara pembuatan skema saya.

Saya memiliki skema untuk semua yang dapat dijelaskan dengan sebuah kata, seperti yang Anda lakukan di OOP klasik.

MISALNYA

  • Komentar
  • Akun
  • Pengguna
  • Blogpost
  • ...

Setiap skema dapat disimpan sebagai Dokumen atau Subdokumen, jadi saya menyatakan ini untuk setiap skema.

Dokumen:

  • Dapat digunakan sebagai referensi. (Misalnya pengguna membuat komentar -> komentar memiliki referensi "dibuat oleh" kepada pengguna)
  • Adalah "Root" di aplikasi Anda. (Misalnya blogpost -> ada halaman tentang blogpost)

Dokumen:

  • Hanya bisa digunakan sekali / tidak pernah menjadi referensi. (Mis. Komentar disimpan di blogpost)
  • Tidak pernah menjadi "Rooting" di aplikasi Anda. (Komentar hanya muncul di halaman blogpost tetapi halaman tersebut masih tentang blogpost)
Silom
sumber
20

Saya menemukan presentasi kecil ini sambil meneliti pertanyaan ini sendiri. Saya terkejut melihat betapa baiknya itu ditata, baik info dan presentasi itu.

http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents

Itu dirangkum:

Sebagai aturan umum, jika Anda memiliki banyak [dokumen anak] atau jika jumlahnya besar, koleksi terpisah mungkin yang terbaik.

Dokumen yang lebih kecil dan / atau lebih sedikit cenderung cocok untuk ditanamkan.

Chris Bloom
sumber
11
Berapa harganya a lot? 3? 10? 100? Apa large? 1 KB? 1MB? 3 bidang? 20 bidang? Apa smaller/ fewer?
Traxo
1
Itu pertanyaan yang bagus, dan untuk yang saya tidak punya jawaban spesifik. Presentasi yang sama termasuk slide yang mengatakan "Dokumen, termasuk semua dokumen dan array yang disematkan, tidak boleh melebihi 16MB", sehingga bisa jadi cutoff Anda, atau hanya pergi dengan apa yang tampaknya masuk akal / nyaman untuk situasi spesifik Anda. Dalam proyek saya saat ini, mayoritas dokumen yang disematkan adalah untuk hubungan 1: 1, atau 1: banyak di mana dokumen yang disematkan sangat sederhana.
Chris Bloom
Lihat juga komentar teratas saat ini oleh @ john-f-miller, yang sementara juga tidak memberikan angka spesifik untuk ambang batas memang mengandung beberapa petunjuk tambahan yang seharusnya membantu memandu keputusan Anda.
Chris Bloom
16

Saya tahu ini cukup lama tetapi jika Anda mencari jawaban untuk pertanyaan OP tentang cara mengembalikan hanya komentar yang ditentukan, Anda dapat menggunakan operator $ (permintaan) seperti ini:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
finspin
sumber
4
ini tidak akan berfungsi jika dua komentar memiliki konten yang identik. orang mungkin berpendapat bahwa kita juga bisa menambahkan penulis ke permintaan pencarian, yang masih tidak akan berfungsi jika penulis membuat dua komentar yang identik dengan konten yang sama
Steel Brain
1
@ SteelBrain: Pak dimainkan dengan baik, dimainkan dengan baik.
JakeStrang
12

Ya, kita dapat menggunakan referensi dalam dokumen. Untuk mengisi dokumen lain seperti sql saya bergabung. Dalam mongo db mereka tidak harus bergabung untuk memetakan satu ke banyak dokumen hubungan. Sebaliknya, kita dapat menggunakan populate untuk memenuhi skenario kita ..

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
  fans     : [{ type: Number, ref: 'Person' }]
});

Populasi adalah proses untuk secara otomatis mengganti jalur yang ditentukan dalam dokumen dengan dokumen dari koleksi lain. Kami dapat mengisi satu dokumen, beberapa dokumen, objek polos, beberapa objek polos, atau semua objek yang dikembalikan dari kueri. Mari kita lihat beberapa contoh.

Lebih baik Anda bisa mendapatkan informasi lebih lanjut silakan kunjungi: http://mongoosejs.com/docs/populate.html

Narendran
sumber
5
Luwak akan mengeluarkan permintaan terpisah untuk setiap bidang yang diisi. Ini berbeda dengan SQL GABUNGAN karena dilakukan di server. Ini termasuk lalu lintas tambahan antara server aplikasi dan server mongodb. Sekali lagi, Anda mungkin mempertimbangkan ini ketika Anda mengoptimalkan. Namun demikian, server Anda masih benar.
Maks.
6

Sebenarnya, saya cukup ingin tahu mengapa tidak ada yang berbicara tentang spesifikasi UML. Aturan praktisnya adalah bahwa jika Anda memiliki agregasi, maka Anda harus menggunakan referensi. Tetapi jika itu adalah komposisi, maka kopling lebih kuat, dan Anda harus menggunakan dokumen yang disematkan.

Dan Anda akan segera mengerti mengapa itu logis. Jika suatu objek dapat ada secara independen dari induknya, maka Anda akan ingin mengaksesnya meskipun induknya tidak ada. Karena Anda tidak dapat menanamkannya di induk yang tidak ada, Anda harus membuatnya langsung dalam struktur data itu sendiri. Dan jika orangtua ada, cukup tautkan bersama-sama dengan menambahkan referensi objek pada induk.

Tidak benar-benar tahu apa perbedaan antara kedua hubungan itu? Berikut ini tautan yang menjelaskannya: Agregasi vs Komposisi dalam UML

Bonjour123
sumber
Kenapa -1? Tolong beri penjelasan yang akan menjelaskan alasannya
Bonjour123
1

Jika saya ingin mengedit komentar tertentu, bagaimana cara saya mendapatkan konten dan pertanyaannya?

Jika Anda melacak jumlah komentar dan indeks komentar yang ingin Anda ubah, Anda dapat menggunakan operator titik ( contoh SO ).

Anda bisa melakukan f.ex.

db.questions.update(
    {
        "title": "aaa"       
    }, 
    { 
        "comments.0.contents": "new text"
    }
)

(sebagai cara lain untuk mengedit komentar di dalam pertanyaan)

serv-inc
sumber