ความสัมพันธ์ MongoDB: ฝังหรืออ้างอิง?


524

ฉันใหม่กับ MongoDB - มาจากพื้นหลังฐานข้อมูลเชิงสัมพันธ์ ฉันต้องการออกแบบโครงสร้างคำถามด้วยความคิดเห็น แต่ไม่ทราบว่าความสัมพันธ์ใดที่จะใช้สำหรับความคิดเห็น: embedหรือreference?

คำถามที่มีความคิดเห็นเช่นstackoverflowจะมีโครงสร้างดังนี้:

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

ตอนแรกฉันต้องการใช้ความคิดเห็นที่ฝัง (ฉันคิดว่าembedแนะนำใน MongoDB) เช่นนี้:

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

ชัดเจน แต่ฉันกังวลเกี่ยวกับกรณีนี้: หากฉันต้องการแก้ไขความคิดเห็นที่ระบุฉันจะได้รับเนื้อหาและคำถามได้อย่างไร ไม่มีที่_idจะให้ฉันค้นหาหรือquestion_refให้ฉันพบคำถาม (ผมมือใหม่ที่ผมไม่ทราบว่ามีวิธีการทำเช่นนี้โดยไม่ต้องใด ๆ_idและquestion_ref.)

ฉันต้องใช้งานหรือrefไม่embed? ถ้าอย่างนั้นฉันต้องสร้างคอลเลคชั่นใหม่สำหรับแสดงความคิดเห็น?


วัตถุ Mongo ทั้งหมดถูกสร้างขึ้นด้วย _ID ไม่ว่าคุณจะสร้างเขตข้อมูลหรือไม่ ดังนั้นในทางเทคนิคความคิดเห็นแต่ละรายการจะยังคงมี ID
Robbie Guilfoyle

25
@ RobbieGuilfoyle ไม่เป็นความจริง
pennstatephil

13
ฉันยืนแก้ไขขอบคุณ @pennstatephil :)
ร็อบบี้ Guilfoyle

4
สิ่งที่เขาอาจหมายถึงคือวัตถุพังพอนทั้งหมดถูกสร้างขึ้นด้วย _id สำหรับผู้ที่ใช้กรอบงานนี้ - ดูเอกสารย่อยพังพอน
Luca

1
หนังสือที่ดีมากสำหรับการเรียนรู้ความสัมพันธ์ของ mongo db คือ "MongoDB Applied Design Patterns - O'Reilly" บทที่หนึ่งพูดคุยเกี่ยวกับการตัดสินใจครั้งนี้เพื่อฝังหรืออ้างอิง?
Felipe Toledo

คำตอบ:


769

นี่เป็นศิลปะมากกว่าวิทยาศาสตร์ เอกสาร Mongo บน Schemasคือการอ้างอิงที่ดี แต่ที่นี่มีบางสิ่งบางอย่างที่จะต้องพิจารณา:

  • ใส่ให้มากที่สุด

    ความสุขของฐานข้อมูลเอกสารคือการกำจัดการเข้าร่วมจำนวนมาก สัญชาตญาณแรกของคุณควรวางไว้ในเอกสารเดียวให้มากที่สุด เนื่องจากเอกสาร MongoDB มีโครงสร้างและเนื่องจากคุณสามารถสืบค้นภายในโครงสร้างนั้นได้อย่างมีประสิทธิภาพ (ซึ่งหมายความว่าคุณสามารถเป็นส่วนหนึ่งของเอกสารที่คุณต้องการได้ดังนั้นขนาดของเอกสารไม่ต้องกังวลมากนัก) ไม่จำเป็นต้องทำให้ข้อมูลเป็นมาตรฐานเหมือนเดิม คุณต้องการใน SQL โดยเฉพาะข้อมูลใด ๆ ที่ไม่เป็นประโยชน์นอกเหนือจากเอกสารหลักควรเป็นส่วนหนึ่งของเอกสารเดียวกัน

  • แยกข้อมูลที่สามารถอ้างอิงได้จากหลาย ๆ ที่มาไว้ในคอลเลกชันของตัวเอง

    นี่ไม่ใช่ปัญหา "พื้นที่เก็บข้อมูล" มากนักเนื่องจากเป็นปัญหา "ความสอดคล้องของข้อมูล" หากมีหลายระเบียนที่อ้างถึงข้อมูลเดียวกันมันจะมีประสิทธิภาพมากขึ้นและมีข้อผิดพลาดน้อยลงที่จะอัปเดตระเบียนเดียวและเก็บการอ้างอิงไว้ในที่อื่น ๆ

  • ข้อควรพิจารณาเกี่ยวกับขนาดเอกสาร

    MongoDB กำหนดขนาด จำกัด 4MB (16MB พร้อม 1.8) ในเอกสารเดียว ในโลกของข้อมูล GB ฟังดูเล็ก แต่มันก็เป็น 30,000 ทวีตหรือ 250 คำตอบของ Stack Overflow หรือรูปถ่ายวูบวาบ 20 ภาพ ในทางตรงกันข้ามนี่เป็นข้อมูลที่มากกว่าหนึ่งอาจต้องการที่จะนำเสนอในครั้งเดียวบนหน้าเว็บทั่วไป ก่อนอื่นให้พิจารณาสิ่งที่จะทำให้การค้นหาของคุณง่ายขึ้น ในหลายกรณีความกังวลเกี่ยวกับขนาดเอกสารจะเป็นการปรับให้เหมาะสมก่อนเวลาอันควร

  • โครงสร้างข้อมูลที่ซับซ้อน:

    MongoDB สามารถจัดเก็บโครงสร้างข้อมูลที่ซ้อนกันโดยพลการ แต่ไม่สามารถค้นหาได้อย่างมีประสิทธิภาพ หากข้อมูลของคุณอยู่ในรูปแบบทรีฟอเรสต์หรือกราฟคุณจำเป็นต้องจัดเก็บแต่ละโหนดและขอบของมันในเอกสารแยกต่างหาก (โปรดทราบว่ามีแหล่งเก็บข้อมูลที่ออกแบบมาโดยเฉพาะสำหรับข้อมูลประเภทนี้ที่ควรพิจารณาด้วย)

    มันยังชี้ให้เห็นว่ามันเป็นไปไม่ได้ที่จะคืนองค์ประกอบย่อยในเอกสาร หากคุณต้องการเลือกและหยิบเอกสารสองสามบิตมันจะง่ายกว่าถ้าจะแยกพวกมันออก

  • ความสอดคล้องของข้อมูล

    MongoDB ทำให้การแลกเปลี่ยนระหว่างประสิทธิภาพและความสอดคล้อง กฎคือการเปลี่ยนแปลงในเอกสารเดียวเป็นอะตอมเสมอในขณะที่การปรับปรุงเอกสารหลายรายการไม่ควรถือว่าเป็นอะตอมมิก นอกจากนี้ยังไม่มีวิธี "ล็อค" บันทึกบนเซิร์ฟเวอร์ (คุณสามารถสร้างสิ่งนี้ลงในตรรกะของลูกค้าโดยใช้ตัวอย่างเช่น "ล็อค" ฟิลด์) เมื่อคุณออกแบบสคีมาของคุณให้พิจารณาว่าคุณจะรักษาข้อมูลของคุณให้สอดคล้องกันอย่างไร โดยทั่วไปยิ่งคุณเก็บเอกสารไว้มากเท่าไหร่ก็ยิ่งดีเท่านั้น

สำหรับสิ่งที่คุณกำลังอธิบายฉันจะฝังความคิดเห็นและให้แต่ละ ID ความคิดเห็นด้วย ObjectID ObjectID มีการประทับเวลาที่ฝังอยู่ภายในเพื่อให้คุณสามารถใช้มันแทนที่จะสร้างที่หากคุณต้องการ


1
ฉันต้องการเพิ่มคำถาม OP: โมเดลความคิดเห็นของฉันมีชื่อผู้ใช้และลิงก์ไปยังภาพแทนตัวของเขา อะไรจะเป็นวิธีที่ดีที่สุดเมื่อพิจารณาจากผู้ใช้สามารถแก้ไขชื่อ / อวาตาร์ของเขาได้?
user1102018

5
เกี่ยวกับ 'โครงสร้างข้อมูลที่ซับซ้อน' ดูเหมือนว่าเป็นไปได้ที่จะส่งคืนชุดย่อยขององค์ประกอบในเอกสารโดยใช้กรอบการรวม (ลองใช้ $ คลาย)
Eyal Roth

4
ข้อผิดพลาดเทคนิคนี้ไม่ได้เป็นไปได้หรือไม่เป็นที่รู้จักอย่างกว้างขวางใน MongoDB เมื่อต้นปี 2012 ด้วยความนิยมของคำถามนี้ฉันขอแนะนำให้คุณเขียนคำตอบที่อัปเดตของคุณเอง ฉันเกรงว่าฉันจะได้หลีกเลี่ยงการพัฒนาอย่างต่อเนื่องใน MongoDB และฉันก็ไม่ได้อยู่ในตำแหน่งที่ดีที่จะแสดงความคิดเห็นภายในโพสต์ต้นฉบับของฉัน
John F. Miller

54
16MB = 30 ล้านทวีตใช่ไหม มีเมนูเกี่ยวกับ 0,5 ไบต์ต่อทวีต!
เปาโล

8
ใช่มันปรากฏว่าฉันถูกปิดโดยปัจจัย 1,000 และบางคนพบว่ามีความสำคัญ ฉันจะแก้ไขโพสต์ WRT 560bytes ต่อทวีตเมื่อฉันท่องไปในทวิตเตอร์ 2554 ยังเชื่อมโยงกับข้อความและสตริง Ruby 1.4; กล่าวอีกนัยหนึ่งก็คือ ASCII chars เท่านั้น
John F. Miller

39

โดยทั่วไปการฝังจะดีถ้าคุณมีความสัมพันธ์แบบหนึ่งต่อหนึ่งหรือหนึ่งต่อหลายกลุ่มและการอ้างอิงนั้นดีถ้าคุณมีความสัมพันธ์แบบกลุ่มต่อกลุ่ม


10
คุณช่วยเพิ่มลิงค์อ้างอิงได้ไหม? ขอบคุณ
db80

คุณจะพบความคิดเห็นที่เฉพาะเจาะจงกับการออกแบบของหนึ่งต่อหลายนี้ได้อย่างไร
Mauricio Pastorini


29

หากฉันต้องการแก้ไขความคิดเห็นที่ระบุวิธีรับเนื้อหาและคำถาม

db.question.find({'comments.content' : 'xxx'})คุณสามารถสอบถามโดยการย่อยเอกสาร:

สิ่งนี้จะส่งคืนเอกสารคำถามทั้งหมด หากต้องการแก้ไขความคิดเห็นที่ระบุคุณต้องค้นหาความคิดเห็นที่ลูกค้าทำการแก้ไขและบันทึกกลับไปที่ฐานข้อมูล

โดยทั่วไปหากเอกสารของคุณมีอาร์เรย์วัตถุคุณจะพบว่าวัตถุย่อยเหล่านั้นจะต้องได้รับการแก้ไขด้านไคลเอนต์


4
สิ่งนี้จะไม่ทำงานหากมีสองความคิดเห็นมีเนื้อหาเหมือนกัน อาจมีคนแย้งว่าเราสามารถเพิ่มผู้แต่งลงในข้อความค้นหาซึ่งยังคงใช้งานไม่ได้หากผู้เขียนแสดงความคิดเห็นเหมือนกันสองรายการพร้อมเนื้อหาเดียวกัน
Steel Brain

@SteelBrain: ถ้าเขาเก็บดัชนีความคิดเห็นไว้, เครื่องหมายจุดอาจช่วยได้ ดูstackoverflow.com/a/33284416/1587329
serv-inc

13
ฉันไม่เข้าใจว่าคำตอบนี้มี 34 upvotes คนที่สองหลายคนแสดงความคิดเห็นแบบเดียวกันที่ทั้งระบบจะแตก นี่เป็นการออกแบบที่แย่มากและไม่ควรใช้ วิธีการ @user เป็นวิธีที่จะไป
user2073973

21

ฉันมาสายนิดหน่อย แต่ก็ยังอยากจะแบ่งปันวิธีการสร้างสคีมาของฉัน

ฉันมี schemas สำหรับทุกสิ่งที่สามารถอธิบายได้ด้วยคำเช่นคุณจะทำมันใน OOP คลาสสิก

เช่น

  • คิดเห็น
  • บัญชีผู้ใช้
  • ผู้ใช้งาน
  • โพสต์บล็อก
  • ...

ทุกสคีมาสามารถบันทึกเป็นเอกสารหรือเอกสารย่อยดังนั้นฉันจึงประกาศสิ่งนี้สำหรับสคีมาแต่ละรายการ

เอกสาร:

  • สามารถใช้เป็นข้อมูลอ้างอิง (เช่นผู้ใช้แสดงความคิดเห็น -> ความคิดเห็นมีการอ้างอิง "โดย" กับผู้ใช้)
  • เป็น "รูท" ในแอปพลิเคชันของคุณ (เช่น blogpost -> มีหน้าเกี่ยวกับ blogpost)

เอกสารย่อย:

  • สามารถใช้ได้เพียงครั้งเดียว / ไม่เคยมีการอ้างอิง (เช่นความคิดเห็นถูกบันทึกไว้ใน blogpost)
  • ไม่มี "รูท" ในแอปพลิเคชันของคุณ (ความคิดเห็นเพิ่งปรากฏในหน้าบล็อกโพสต์ แต่หน้านั้นยังคงเป็นเรื่องของโพสต์บล็อก)

20

ฉันเจองานนำเสนอเล็ก ๆ นี้ในขณะที่ค้นคว้าคำถามนี้ด้วยตัวเอง ฉันประหลาดใจมากที่มีการจัดวางที่ดีทั้งข้อมูลและการนำเสนอของมัน

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

มันสรุป:

ตามกฎทั่วไปหากคุณมี [เอกสารย่อย] จำนวนมากหรือถ้าเอกสารมีขนาดใหญ่ชุดสะสมแยกต่างหากอาจดีที่สุด

เอกสารที่มีขนาดเล็กและ / หรือน้อยกว่านั้นมักจะเหมาะสำหรับการฝัง


11
เท่าไหร่a lot? 3 หรือไม่? 10? 100? อะไรนะlarge? 1kb? 1MB? 3 ช่อง? 20 สาขา? smaller/ คือfewerอะไร
Traxo

1
นั่นเป็นคำถามที่ดีและฉันไม่มีคำตอบเฉพาะสำหรับ งานนำเสนอเดียวกันรวมถึงสไลด์ที่ระบุว่า "เอกสารรวมถึงเอกสารและอาร์เรย์ทั้งหมดที่ฝังตัวต้องมีขนาดไม่เกิน 16MB" ซึ่งอาจเป็นทางลัดของคุณหรือไปกับสิ่งที่สมเหตุสมผล / สะดวกสบายสำหรับสถานการณ์เฉพาะของคุณ ในโครงการปัจจุบันของฉันเอกสารฝังตัวส่วนใหญ่ใช้สำหรับความสัมพันธ์แบบ 1: 1 หรือ 1: เอกสารส่วนใหญ่ที่เรียบง่ายจริงๆ
Chris Bloom

ดูความคิดเห็นยอดนิยมในปัจจุบันโดย @ john-f-miller ซึ่งในขณะที่ยังไม่ให้ตัวเลขที่เฉพาะเจาะจงสำหรับเกณฑ์นั้นจะมีตัวชี้เพิ่มเติมที่ควรช่วยชี้แนะการตัดสินใจของคุณ
Chris Bloom

16

ฉันรู้ว่ามันค่อนข้างเก่า แต่ถ้าคุณกำลังมองหาคำตอบสำหรับคำถามของ OP เกี่ยวกับวิธีส่งกลับเฉพาะความคิดเห็นที่ระบุคุณสามารถใช้ตัวดำเนินการ$ (แบบสอบถาม)ดังนี้:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})

4
สิ่งนี้จะไม่ทำงานหากมีสองความคิดเห็นมีเนื้อหาเหมือนกัน อาจมีคนแย้งว่าเราสามารถเพิ่มผู้แต่งลงในข้อความค้นหาซึ่งยังคงใช้งานไม่ได้หากผู้เขียนแสดงความคิดเห็นเหมือนกันสองรายการพร้อมเนื้อหาเดียวกัน
Steel Brain

1
@SteelBrain: คุณเล่นได้ดีเล่นได้ดี
JakeStrang

12

ใช่เราสามารถใช้การอ้างอิงในเอกสารหากต้องการเติมเอกสารอื่นเช่น sql i joins ใน mongo db พวกเขาไม่ได้เข้าร่วมในการแมปเอกสารความสัมพันธ์แบบหนึ่งถึงหลายอันแทนเราสามารถใช้ประชากรเพื่อเติมเต็มสถานการณ์ของเรา ..

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' }]
});

ประชากรคือกระบวนการแทนที่พา ธ ที่ระบุในเอกสารโดยอัตโนมัติด้วยเอกสารจากคอลเล็กชันอื่น เราอาจเติมเอกสารเดี่ยวเอกสารหลายฉบับวัตถุธรรมดาหลายวัตถุล้วนหรือวัตถุทั้งหมดที่ส่งคืนจากแบบสอบถาม ลองดูตัวอย่าง

ดีกว่าคุณจะได้รับข้อมูลเพิ่มเติมกรุณาเยี่ยมชม: http://mongoosejs.com/docs/populate.html


5
พังพอนจะออกคำขอแยกต่างหากสำหรับแต่ละเขตข้อมูลที่มีประชากร สิ่งนี้แตกต่างจาก SQL JOINS เนื่องจากดำเนินการบนเซิร์ฟเวอร์ ซึ่งรวมถึงทราฟฟิกเพิ่มเติมระหว่างเซิร์ฟเวอร์แอปและเซิร์ฟเวอร์ mongodb อีกครั้งคุณอาจพิจารณาสิ่งนี้เมื่อคุณเพิ่มประสิทธิภาพ อย่างไรก็ตาม anwser ของคุณยังคงถูกต้อง
สูงสุด

6

จริงๆแล้วฉันค่อนข้างสงสัยว่าทำไมไม่มีใครพูดถึงข้อกำหนด UML กฎง่ายๆคือถ้าคุณมีการรวมแล้วคุณควรใช้การอ้างอิง แต่ถ้าเป็นองค์ประกอบแล้วข้อต่อก็จะแข็งและคุณควรใช้เอกสารที่ฝังไว้

และคุณจะเข้าใจได้อย่างรวดเร็วว่าทำไมมันถึงมีเหตุผล หากวัตถุสามารถมีอยู่เป็นอิสระจากผู้ปกครองแล้วคุณจะต้องการเข้าถึงแม้ว่าผู้ปกครองไม่ได้อยู่ เนื่องจากคุณไม่สามารถฝังไว้ในพาเรนต์ที่ไม่มีอยู่ได้คุณต้องทำให้มันอยู่ในโครงสร้างข้อมูลของตัวเอง และหากผู้ปกครองมีอยู่เพียงแค่เชื่อมโยงพวกเขาเข้าด้วยกันโดยการเพิ่มการอ้างอิงของวัตถุในผู้ปกครอง

ไม่รู้จริง ๆ ว่าความแตกต่างระหว่างสองความสัมพันธ์คืออะไร นี่คือลิงค์อธิบายพวกเขา: การ รวมตัวกับองค์ประกอบใน UML


ทำไม -1 โปรดให้คำอธิบายที่จะอธิบายเหตุผล
Bonjour123


1

หากฉันต้องการแก้ไขความคิดเห็นที่ระบุฉันจะได้รับเนื้อหาและคำถามได้อย่างไร

หากคุณติดตามจำนวนความคิดเห็นและดัชนีของความคิดเห็นที่คุณต้องการแก้ไขคุณสามารถใช้ตัวดำเนินการ dot ( ตัวอย่างเช่น )

คุณสามารถทำ f.ex

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

(เป็นอีกวิธีในการแก้ไขความคิดเห็นภายในคำถาม)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.