1. 程式人生 > 其它 >MongoDB入門實戰教程(7)

MongoDB入門實戰教程(7)

本系列教程目錄:

MongoDB入門實戰教程(1)

MongoDB入門實戰教程(2)

MongoDB入門實戰教程(3)

MongoDB入門實戰教程(4)

MongoDB入門實戰教程(5)

MongoDB入門實戰教程(6)

前面我們學習了聚合查詢,本篇我們來看看在模型設計中如何應用引用模式來提高查詢效率。

1內嵌模式

在進行MongoDB的模型設計中,基於JSON文件模型,我們很容易就可以設計出一個內嵌模式的文件模型出來。

可以不誇張地說,80%~90%的場景下,我們優先都會使用內嵌物件 或 內嵌陣列 的方式來設計文件模型的所謂的1-1、1-N、N-N的關係

例如下面這個Contacts的文件模型,它描述了一個聯絡人的關係建模:

Contacts

{
  name: "Edison Zhou",
  company: "CSCEC YZW",
  title: ".NET Engineer",
  portraits: {
    mimetype: xxx,
    data: xxxx
  },
  addresses: [
    { type: home, … },
    { type: work, … }
  ],
  groups: [
    {name: "YZW Football Assocation" },
    {name: "YZW .NET Assocation" }
  ]
}

可以看到,所謂的內嵌類 其實 類似於預先聚合(關聯)

這樣的操作(引用+冗餘)其實對讀操作更有效能優勢

但是,內嵌設計有一個大前提限制:即內嵌後文檔大小不能超過16MB。

此外,如果內嵌的陣列(通常是陣列)的長度太大,比如數萬或更多的時候,也是不適合採用內嵌模式的。

那麼,此時我們應該怎麼設計呢?

2 引用模式

萬級長度的內嵌陣列

這裡我們仍然適用上面提到的Contacts模型,假設其中的groups是一個內嵌陣列,這個groups的資料可能有百萬級的長度,且每個Contacts文件都需要冗餘這麼一份資料,而且groups資料還面臨著頻繁修改的需求。

Contacts

{
  name: "Edison Zhou",
  company: 
"CSCEC YZW", title: ".NET Engineer", ...... // 假設下面groups有百萬級,且一個group的資訊改動會引發百萬級的DB操作 groups: [ {name: "YZW Football Assocation" }, {name: "YZW .NET Assocation" } ] }

適當使用引用模式解決

解決方案很簡單,就是針對groups使用單獨的collection來儲存,在Contancts模型中新增對group id的集合的引用。

Collection 1 - Contacts:

Contacts

{
  name: "Edison Zhou",
  company: "CSCEC YZW",
  title: ".NET Engineer",
  ......
  // 假設下面groups有百萬級,且一個group的資訊改動會引發百萬級的DB操作
  group_ids: [1,2,3,4,5...]
}

Collection 2 -Groups:

Groups

{
  groups_id,
  name
}

這樣的設計其實類似於關係型資料庫模型的設計,用Id來關聯,我們再熟悉不過了。

但是,在MQL中,我們就需要額外使用$lookup來實現類似SQL中的關聯查詢了,嚴格來說,應該算是LEFT OUTER JOIN查詢。

嗯,這又是一種聚合操作:

db.Contacts.aggregate([
{
  $lookup:
  {
    from: "groups",
    localField: "group_ids",
    foreignField: "group_id",
    as: "groups"
  }
}]);

這個查詢會得到如下圖所示的結果:

.NET中的Lookup操作:

上面講解了如何通過MQL進行操作,那麼,在.NET中如何實現$lookup的效果呢?

好在MongoDB Driver已經幫我們提供了這樣的一個LookUp,且看下面的程式碼示例:

假設我們的實體定義如下:

public class Contact
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Company { get; set; }

    public string Title { get; set; }

    public int[] GroupIds { get; set; }

    public IList<Group> Groups { get; set; }
}

public class Group
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    public int GroupId { get; set; }

    public string Name { get; set; }
}

那麼,可以通過Driver實現以下操作:

public async Task<IList<Contact>> GetAsync()
{
    return await _contacts
        .Aggregate()
        .Lookup<Contact, Group, Contact>(
            _groups,
            local => local.GroupIds,
            from => from.GroupId,
            result => result.Groups)
        .ToListAsync();
}

完整示例github地址:https://github.com/EdisonChou/EDT.Mongo.Sample

執行結果如下所示:

什麼時候使用引用模式

綜上所述,當滿足以下條件之一時,你可以開始考慮引用模式設計文件模型:

(1)當內嵌後的文件太大,有可能超過16MB限制的時候;

(2)內嵌的文件 或 陣列元素 有可能會頻繁修改的時候;

(3)內嵌陣列元素 有可能會持續增長且沒有封頂的時候;

引用模式設計的限制

引用模式也並非銀彈,它存在以下一些限制:

(1)MongoDB對於使用引用的集合之間沒有所謂的外來鍵檢查;

(2)MongoDB使用聚合框架的$lookup來模仿關聯查詢;

(3)$lookup只支援LEFT OUTER JOIN,且關聯目標(from)不能是分片表;

db.Contacts.aggregate([
{
  $lookup:
  {
    from: "groups", // 這裡的from不能是分片表
    ......
  }
}]);

總結

本文簡單介紹了MongoDB的模型設計中的內嵌模式和引用模式,探討了引用模式的使用、何時使用 及 使用限制。

下一篇,我們會學習MongoDB的模式設計中的一些設計模式並套用這些設計模式簡化設計難度。

參考資料

唐建法,《MongoDB高手課》(極客時間)

郭遠威,《MongoDB實戰指南》(圖書)

△推薦訂閱學習

作者:周旭龍

出處:https://edisonchou.cnblogs.com

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。