MongoDBの関係:埋め込みまたは参照?


524

私はMongoDBを初めて使用しました-リレーショナルデータベースのバックグラウンドから来ました。私はいくつかのコメントで質問構造を設計したいが、私は、コメントに使用する関係がわかりません:embedreference

stackoverflowなどのコメント付きの質問は、次のような構造になります。

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

最初は、次のように埋め込みコメント(embedMongoDBでは推奨されていると思います)を使用します。

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

明らかですが、このケースが心配です:特定のコメントを編集したい場合、その内容と質問を取得するにはどうすればよいですか?何もありません_id、私はものを見つけるようにする、またquestion_ref私はその質問をご覧いただくこと。(私は初心者なので、_idand なしでこれを行う方法があるかどうかわかりませんquestion_ref。)

私は使用しなければなりrefませんembedか?次に、コメント用の新しいコレクションを作成する必要がありますか?


フィールドを作成するかどうかに関係なく、すべてのMongoオブジェクトは_IDを使用して作成されます。したがって、技術的には各コメントにはIDがまだあります。
ロビーギルフォイル2014年

25
見true--ない@RobbieGuilfoyle stackoverflow.com/a/11263912/347455
pennstatephil

13
@pennstatephilに感謝します:)
ロビー

4
彼がおそらく意味することは、すべてのマングースオブジェクトは、このフレームワークを使用する人のために_idを使用して作成されるということです
Luca Steeb

1
mongo dbの関係を学ぶのに非常に適した本は、「MongoDB Applied Design Patterns-O'Reilly」です。第一章、この決定について話し合う、埋め込むか参照するか?
フェリペトレド

回答:


769

これは科学というより芸術です。スキーマのMongoのドキュメントは良いの参照ですが、ここで考慮すべきいくつかのものがあります:

  • できるだけ入れて

    ドキュメントデータベースの喜びは、多くの結合を排除することです。最初の本能は、できる限り多くのことを1つのドキュメントに配置することです。MongoDBドキュメントには構造があり、その構造内で効率的にクエリを実行できるため(つまり、必要なドキュメントの一部を取得できるため、ドキュメントのサイズはそれほど気にしないはずです)、次のようなデータをすぐに正規化する必要はありません。あなたはSQLでそうするでしょう。特に、親ドキュメントを除いて役に立たないデータは、同じドキュメントの一部である必要があります。

  • 複数の場所から参照できるデータを独自のコレクションに分離します。

    これは「データの整合性」の問題であるため、「ストレージ容量」の問題ではありません。多くのレコードが同じデータを参照する場合、単一のレコードを更新して他の場所でそのレコードへの参照を保持する方が効率的でエラーが発生しにくくなります。

  • ドキュメントサイズの考慮事項

    MongoDBは、単一のドキュメントに4MB(1.8で16MB)のサイズ制限を課しています。GBのデータの世界ではこれは小さく聞こえますが、これは3万のツイート、または250の典型的なスタックオーバーフローの回答、または20のちらつき写真でもあります。一方、これは、一般的なWebページで一度に表示したい情報よりもはるかに多くの情報です。まず、クエリを簡単にする方法を検討します。多くの場合、ドキュメントサイズに関する懸念は時期尚早の最適化になります。

  • 複雑なデータ構造:

    MongoDBは任意の深くネストされたデータ構造を格納できますが、それらを効率的に検索することはできません。データがツリー、フォレスト、またはグラフを形成している場合、各ノードとそのエッジを個別のドキュメントに効果的に格納する必要があります。(このタイプのデータ用に特別に設計されたデータストアもあることに注意してください)

    また、ドキュメント内の要素のサブセットを返すことが不可能であることも指摘されています。各ドキュメントの数ビットを選択する必要がある場合は、それらを分離する方が簡単です。

  • データの整合性

    MongoDBは、効率と一貫性の間でトレードオフを行います。ルールは、単一のドキュメントへの変更は常にアトミックであることですが、複数のドキュメントへの更新アトミックであると決して考えるべきではありません。サーバー上のレコードを「ロック」する方法もありません(たとえば、「ロック」フィールドを使用して、これをクライアントのロジックに組み込むことができます)。スキーマを設計するときは、データの一貫性を保つ方法を検討してください。一般的に、ドキュメントに多くの情報を保存する方がよいでしょう。

あなたが説明していることについては、コメントを埋め込み、各コメントにObjectIDのidフィールドを与えます。ObjectIDにはタイムスタンプが埋め込まれているので、必要に応じてatで作成する代わりに使用できます。


1
OPの質問に追加したいと思います。私のコメントモデルには、ユーザー名と彼のアバターへのリンクが含まれています。ユーザーが自分の名前/アバターを変更できることを考えると、最善のアプローチは何ですか?
user1102018

5
「複雑なデータ構造」に関して、集約フレームワークを使用してドキュメント内の要素のサブセットを返すことが可能であるようです($ unwindを試してください)。
Eyal Roth

4
エラー、この手法は2012年の初めにはMongoDBで不可能であるか、広く知られていませんでした。この質問の人気を考えると、独自の更新された回答を書くことをお勧めします。MongoDBでの積極的な開発から一歩離れたと思いますが、元の投稿内でコメントを投稿するのに適した立場にはありません。
John F. Miller

54
16MB = 3000万ツイート?ツイートあたりのメナスは約0.5バイトですか?
Paolo

8
はい、私は1000倍遅れていたようで、一部の人々はこれを重要だと考えています。投稿を編集します。ツイートごとにWRT 560バイト。2011年にこれをロートしたとき、TwitterはまだテキストメッセージとRuby 1.4文字列に関連付けられていました。つまり、ASCII文字のみです。
John F. Miller

39

一般に、埋め込みは、エンティティ間に1対1または1対多の関係がある場合に適しています。参照は、多対多の関係がある場合に適しています。


10
参照リンクを追加していただけますか?ありがとう。
db80 2015年

この1対多のデザインで特定のコメントをどのように見つけますか?
マウリシオパス


29

特定のコメントを編集したい場合、その内容と質問を取得するにはどうすればよいですか?

サブドキュメントでクエリできます:db.question.find({'comments.content' : 'xxx'})

これにより、質問ドキュメント全体が返されます。指定されたコメントを編集するには、クライアントでコメントを見つけて編集し、DBに保存し直す必要があります。

一般に、ドキュメントにオブジェクトの配列が含まれている場合、それらのサブオブジェクトをクライアント側で変更する必要があることがわかります。


4
2つのコメントの内容が同じ場合、これは機能しません。著者を検索クエリに追加することもできますが、著者が同じ内容の2つの同一のコメントを作成した場合は機能しません
Steel Brain

@SteelBrain:コメントインデックスを保持している場合は、ドット表記が役立つ場合があります。stackoverflow.com/a/33284416/1587329を
serv-inc

13
この回答に34の賛成票があるかどうかはわかりません。2番目の複数の人が、システム全体が壊れると同じことについてコメントします。これは絶対にひどい設計であり、使用すべきではありません。方法は@userはそれが進むべき道であるん
user2073973

21

ええと、少し遅れましたが、スキーマ作成の方法を共有したいと思います。

私は、古典的なOOPで行うように、単語で説明できるすべてのスキーマを持っています。

例えば

  • コメント
  • アカウント
  • ユーザー
  • ブログ投稿
  • ...

すべてのスキーマはドキュメントまたはサブドキュメントとして保存できるため、スキーマごとにこれを宣言します。

資料:

  • 参考にしてください。(たとえば、ユーザーがコメントを作成した->コメントにはユーザーへの「作成者」参照があります)
  • アプリケーションの「ルート」です。(例:ブログ投稿->ブログ投稿に関するページがあります)

サブドキュメント:

  • 一度だけ使用できます/決して参照ではありません。(例:コメントはブログ投稿に保存されます)
  • アプリケーションの「ルート」になることはありません。(コメントはブログ投稿ページに表示されるだけですが、ページはまだブログ投稿に関するものです)

20

この質問を自分で調査しているときに、この小さなプレゼンテーションに出くわしました。情報と情報の提示の両方がうまくレイアウトされていることに驚きました。

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

要約すると:

原則として、多数の[子ドキュメント]がある場合、またはそれらが大きい場合は、個別のコレクションが最適です。

小さいドキュメントや少ないドキュメントは、埋め込みに自然に適合する傾向があります。


11
いくらa lotですか?3?10?100?なにlarge?1kb?1MB?3つのフィールド?20フィールド?何ですかsmaller/ fewer
Traxo、2017年

1
それは良い質問で、具体的な答えはありません。同じプレゼンテーションに「すべての埋め込みドキュメントと配列を含むドキュメントは16MBを超えることはできない」というスライドが含まれていたので、これはカットオフになる場合もあれば、特定の状況で合理的/快適であると思われるもので済む場合もあります。私の現在のプロジェクトでは、埋め込まれたドキュメントの大部分は1:1の関係、または埋め込まれたドキュメントが本当に単純な1:manyの関係です。
Chris Bloom

@ john-f-millerによる現在のトップコメントも参照してください。これには、しきい値に特定の数値を提供していませんが、決定の指針となる追加のポインターが含まれています。
Chris Bloom

16

私はこれがかなり古いことを知っていますが、指定されたコメントのみを返す方法に関するOPの質問への回答を探している場合は、次のように$(クエリ)演算子を使用できます。

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

4
2つのコメントの内容が同じ場合、これは機能しません。著者を検索クエリに追加することもできると主張する人もいるかもしれませんが、著者が同じ内容の2つの同一のコメントを作成した場合は機能しません
Steel Brain

1
@SteelBrain:よく演奏されました。
JakeStrang 2018

12

はい、ドキュメントで参照を使用できます.sql iのように別のドキュメントにデータを追加するには、joinを使用します.mongo dbでは、1つから多くのリレーションシップドキュメントへのマッピングへの結合がありません。代わりに、populateを使用してシナリオを実行できます。

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
Mongooseは、入力されたフィールドごとに個別のリクエストを発行します。これらはサーバーで実行されるため、SQL JOINSとは異なります。これには、アプリサーバーとmongodbサーバー間の追加のトラフィックが含まれます。繰り返しますが、最適化するときにこれを検討するかもしれません。それにもかかわらず、あなたのanwserはまだ正しいです。
最大の

6

実は、なぜUML仕様について誰も話さなかったのはかなり興味深いことです。経験則として、集計がある場合は参照を使用する必要があります。しかし、それがコンポジションである場合、カップリングはより強く、埋め込まれたドキュメントを使用する必要があります。

そして、それが論理的である理由をすぐに理解できます。オブジェクトが親から独立して存在できる場合は、親が存在しなくてもアクセスできます。存在しない親にそれを埋め込むことはできないので、それをそれ自身のデータ構造でライブにする必要があります。また、親が存在する場合は、親のオブジェクトの参照を追加することで、それらをリンクします。

2つの関係の違いは何ですか?これを説明するリンクは次のとおりです 。UMLでの集約と構成


なぜ-1なのか?理由を明確にする説明をしてください
Bonjour123


1

特定のコメントを編集したい場合、その内容と質問を取得するにはどうすればよいですか?

コメントの数と変更したいコメントのインデックスを追跡していた場合は、ドット演算子SOの例)を使用できます。

f.exを実行できます。

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

(質問内のコメントを編集する別の方法として)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.