---
# System prepended metadata

title: MongoDB

---

# MongoDB

- [一、觀念](#一、-觀念)
    - [1. MongoDB Infar 概念](#1-MongoDB-Infar-概念)
    - [2. Compare with RDB](#2-Compare-with-RDB)
    - [3. Connect](#3-Connect)
    - [4. User and Role](#4-User-and-Role)
    - [5. Schema-less](#5-Schema-less)
    - [6. 基礎語法](#6-基礎語法)
    - [7. Aggregate(聚合)](#7-Aggregate(聚合))
- [二、操作](#二、-操作)
    - [1. MongoDB Atlas](#1-MongoDB-Atlas)
    - [2. MongoDB Compass](#2-MongoDB-Compass)

## 一、 觀念

幾個KeyPoint這邊先表列，要注意這幾點的原則。

1. 無需事先定義資料結構
2. 權限劃分依據DataBase
3. 一個Document最大16MB
4. 資料的儲存結構應優先選擇Embedding

### 1. MongoDB Infar 概念

基本上會採用的是SaaS的服務，因此在架構面目前應知道一些基本的知識就好了吧？（其實是想偷懶ＸＤ）就分別介紹在MongoDB是如何達兩個議題

1. 高可用性
2. 分散式

#### a. Replica Set 高可用性架構搭建

基本上MongoDB高可用性架構為我們常見的Primary/Secondary（主/從）架構，

 - Primary（主節點）: 這是主要的數據庫節點，負責處理寫入（Write）操作。通常，所有的數據變更（包括新增、更新、刪除）都會先發生在這個節點上。
 - Secondary（從節點）: 這些是備份節點，通常會同步主節點上的數據。可用於讀取（Read）操作分攤主節點(primary)壓力和作為故障轉移（Failover）的備份。



而官方有提供三種 Replica Set 架構 (Pattern)，如下：
* Three Member Replica Sets
* Replica Sets with Four or More Members
* Geographically Distributed Replica Sets

這邊將說明Three Member Replica Sets最基本的高可用性架構，在這個模式下我們建立MongoDB時需要三個節點(Node)，分別為1個Primary和2個Secondary。

在正常情況下Primary的資料會即時同步給兩個Secondary。

![](https://hackmd.io/_uploads/SyuwW59lT.png)


甚至我們想要減輕Primary的工作負擔，可以使其節點負責寫入，去指定我們要讀取的節點是Secondary。

最重要的事，當節點發生故障時會透過投票的方式來取得哪一個節點升級為Primary。

![](https://hackmd.io/_uploads/BkJzf5qxa.png)


#### b. Sharded Cluste 分片

MongoDB的分片集群是為了解決數據量龐大和高吞吐量的讀寫操作而設計的水平擴展方案。當一個單一的伺服器不足以存儲大量數據或處理大量的流量時，分片可以將數據分布到多個伺服器上。

![](https://hackmd.io/_uploads/S1fRVq9ep.png)

而其中每一個Shard都是一個Replica Set，因此從上圖可以發現Sharded機制不會進行備份，而是每一個Shard都個該去實作Replica Set使其有高可用性的備援機制存在。

而資料分片會將每一個Block存了哪些資料的索引去記錄在Config Server中

![](https://hackmd.io/_uploads/rJIfUccxp.png)

那我們使用的時候是對mongs進行連線，mongs就像一個Router他會幫我們指引一條明路去找到資料的所在位子

![](https://hackmd.io/_uploads/rJeuL95xa.png)


### 2. Compare with RDB

非關聯式資料庫與關聯式資料庫，在他的資料面存放面的名詞，可以用下列幾種作為一個區分，以便於第一次接觸非關聯式資料庫的理解。
![](https://hackmd.io/_uploads/BkKNsEcea.png)


#### a. Collection V.S Table
| 特性/概念 | MongoDB Collection | 關聯型數據庫 Table |
|------------|---------------------|-------------------|
| 定義 | 一組MongoDB文檔的集合。 | 由行和列組成的數據集。 |
| 數據結構 | 非結構化，支援BSON格式的文檔。 | 結構化，定義了固定的列和數據類型。 |
| 彈性 | 文檔之間可以有不同的結構。 | 所有的行都必須符合相同的結構。 |
| 主鍵 | `_id`字段，是每個文檔的唯一識別符。 | 通常由一或多個列組成的主鍵來確保唯一性。 |
| 查詢語言 | 使用JSON-like的查詢語言。 | 使用SQL為查詢語言。 |
| 索引 | 支援多種類型的索引，包括複合索引、地理空間索引等。 | 主要是B-tree類型的索引，還有其他類型。 |
| 存儲方式 | 文檔以BSON格式存儲。 | 通常存儲為表格形式，每個字段有固定的數據類型。 |
| 關聯 | 使用嵌入式文檔和引用來建立關聯。 | 使用外鍵和JOIN操作來建立和查詢關聯。 |


#### c. Document V.S Row
| 特性/概念 | MongoDB Document                     | 關聯型數據庫 Row |
|----------|--------------------------------------|-----------------|
| 定義     | 一個數據記錄或物件，存儲在Collection中。| 一組相關的數據值，存儲在Table中的一行裡。 |
| 數據格式 | JSON-like，使用BSON格式。            | 定義了固定的列和數據類型的值。 |
| 結構     | 非結構化或半結構化。不同文檔可以有不同的結構。 | 結構化。每一行都必須符合Table的結構定義。 |
| 主鍵     | `_id`字段，是每個文檔的唯一識別符。   | 由Table的主鍵列或列組成，確保每行的唯一性。 |
| 存儲     | 文檔可以包含嵌入式的子文檔和陣列，形成豐富的結構。 | 數據通常被存儲為平面結構，關聯性由外鍵和JOIN操作確定。 |
| 查詢     | 使用JSON-like的查詢語言。支援嵌入式文檔和陣列的查詢。 | 使用SQL進行查詢。JOIN操作用於查詢多個表之間的關聯。 |
| 最大存儲大小 | 16MB（單個BSON文檔的大小限制）     | 通常由具體的DBMS和硬件配置決定，但單行的大小通常小於文檔大小限制。 |


### 3. Connect

在RDB中有些資料庫基本上都必需要指定DataBase進行連線，而不能直接連線Instance，但有些資料庫連線時不指定DataBase是因為他會給予預設的DataBase進行連線，在透過DataBase Name進行存取的切換。

#### a. JDBC Oracle連線範例

其中SID為Instance，但基本上SID會對應一個DataBase，也就是他的資料庫存取需要跨Instance，就必須使用到DB Link的技術。

Oralce的世界是

>Instance 只能對應一個 DataBase
>DataBase可以對應多個Instance

```
jdbc:oracle:thin:@[HOSTNAME]:[PORT]:[SID]
```

#### b. JDBC PostgreSQL連線範例

PostgreSQL剛好反之，一個Instance可以擁有多個DataBase，但連線時必須指名DataBase並且不可跨DataBase存取，必須透過DB Link。
```
jdbc:postgresql://[HOSTNAME]:[PORT]/[DATABASE]
```

#### c. MongoDB連線範例
MongoDB特色跟PostgreSQL比較像，一個Instance可以擁有多個DataBase，且不需要額外設定DBLink，只需要設定DataBase的Role規則。

且依據跟大神取經，原廠建議使用原廠的連線方式，不要透過第三方的套件如JDBC，以下的連線字串皆會是原廠的Driver連線方式。

不指定DataBase連線會是以Admin角色，每一次存取都必須指定資料庫。
```
mongodb://username:password@localhost:27017/
```

但MongoDB基本上可以對Cluster直接連線，透過using [DataBase]來指定DataBase進行存取。

>Java的存取範例
>> 這邊要注意，範例是使用原廠建議的Driver來實作，而非透過JDBC。

```java
    public static void main(String[] args) {
        // 1. Connect to MongoDB
        MongoClientURI uri = new MongoClientURI("mongodb://username:password@localhost:27017/");
        MongoClient mongoClient = new MongoClient(uri);
        MongoDatabase database = mongoClient.getDatabase("myDatabase");  // Choose the database
        MongoCollection<Document> collection = database.getCollection("myCollection");  // Choose the collection

        // 2. Insert (Create)
        Document newDocument = new Document("name", "John")
                .append("age", 30)
                .append("city", "New York");
        collection.insertOne(newDocument);

        // 3. Read
        Document myDoc = collection.find(new Document("name", "John")).first();
        System.out.println(myDoc.toJson());

        // 4. Update
        Document updateDocument = new Document("$set", new Document("city", "Los Angeles"));
        collection.updateOne(new Document("name", "John"), updateDocument);

        // 5. Delete
        collection.deleteOne(new Document("name", "John"));

        mongoClient.close();
    }
}

```

#### e. Connection Pool
在原生的MongoDB中，就支援Connection Pool的功能，在其中跟以往的Hikari之類的Connction Pool類似，提供一個簡單的設定使用範例，其使用需要針對專案的情境斟酌考量，當然若無需要客製化設定，原本他就有預設的情況，資料庫最高可支援到65536條連線。

而在連線的Driver預設的連線池最小連線數為0，最大為100，可以依據專案的情況調整最大最小值。


```java
ConnectionPoolSettings connectionPoolSettings = ConnectionPoolSettings.builder()
    .maxSize(50)                // 最大連接數
    .minSize(10)                // 最小連接數
    .maxWaitTime(1000, TimeUnit.MILLISECONDS) // 最大等待時間
    .build();

MongoClientSettings settings = MongoClientSettings.builder()
    .applyConnectionString(new ConnectionString("mongodb://localhost:27017"))
    .applyToConnectionPoolSettings(builder -> builder.applySettings(connectionPoolSettings))
    .build();

MongoClient mongoClient = MongoClients.create(settings);

```

## 4. User and Role

資料庫需要依據每一個使用的專案進行權限的切割，那我們就需要針對這樣子的需求建立不同的規則和使用者帳號。

例如：

我有兩個資料庫分別為A-DB和B-DB。

然後分別有兩個使用者Yeh和Wang，並且要遵守以下規則。

1. Yeh - 只可讀取A-DB，但可讀寫B-DB。
2. Wang - 只可讀取B-DB，但可讀寫A-DB。

首先建立規則

```
use admin

// 為A-DB創建readWrite角色
db.createRole({
  role: "readWriteOnA-DB",
  privileges: [
    { resource: { db: "A-DB", collection: "" }, actions: ["find", "insert", "update", "delete"] }
  ],
  roles: []
})


// 為A-DB創建read角色
db.createRole({
  role: "readOnlyOnA-DB",
  privileges: [
    { resource: { db: "A-DB", collection: "" }, actions: ["find"] }
  ],
  roles: []
})


// 為B-DB創建read角色
db.createRole({
  role: "readOnlyOnB-DB",
  privileges: [
    { resource: { db: "B-DB", collection: "" }, actions: ["find"] }
  ],
  roles: []
})

// 為B-DB創建readWrite角色
db.createRole({
  role: "readWriteOnB-DB",
  privileges: [
    { resource: { db: "B-DB", collection: "" }, actions: ["find", "insert", "update", "delete"] }
  ],
  roles: []
})

```

再來創建使用者並賦予規則

```
db.createUser({
  user: "Yeh",
  pwd: "passwordForYeh",
  roles: ["readWriteOnB-DB", "readOnlyOnA-DB"]
})


// 創建Wnag的使用者
db.createUser({
  user: "Wang",
  pwd: "passwordForWang",
  roles: ["readWriteOnA-DB", "readOnlyOnB-DB"]
})
```

## 5. Schema-less

NoSQL最大的特色就是，不用預先定義資料結構，在傳統的RDB中我們需要預先定義Table的樣子，可以想像我畫圖要先畫草稿，但在MongoDB或其他NoSQL(ex Redis)中不需要先行定義，換句話說就是直接開工不畫草稿了。

在RDB的世界裡我們就要撰寫，DDL語法來建置我的資料會儲存樣態，必須事先定義。

透過DDL 先定義儲存結構
```sql=
CREATE TABLE schema_name.table_name (
    column_1 data_type column_constraint,
    column_2 data_type column_constraint,
    ...
    table_constraint
 );
```
透過DML再把資料儲存進入RDB中
```sql=
INSERT INTO table
(column1, column2, ... column_n )
VALUES
(expression1, expression2, ... expression_n );
```

MongoDB就不用像上面這樣，只要執行Insert 或者 CreateCollection就可以達到目的，不用事先預設資料結構，詳細方法參照[5.基礎語法](#5-基礎語法)

```shell
use sample_mflix //選擇要寫入的資料庫

//movies 為 Collection
db.movies.insertOne(
  {
    //資料的結構不需要事先定義好
    title: "The Favourite",
    genres: [ "Drama", "History" ],
    runtime: 121,
    rated: "R",
    year: 2018,
    directors: [ "Yorgos Lanthimos" ],
    cast: [ "Olivia Colman", "Emma Stone", "Rachel Weisz" ],
    type: "movie"
  }
)
```

但使用MongoDB有幾個特性要注意，資料結構的儲存有兩種方式Embedding 和 Reference兩種。

### a. Embedding (嵌入)
**描述：** 這是將一個文檔或文檔的陣列直接嵌入到另一個文檔中的方法。這表示所有相關的信息都存儲在同一個地方。
**儲存：** 數據被物理地存儲在一個文檔內。因此，查詢主文檔時，嵌入的數據也會立即可用，不需要進行額外的查詢。

**優點：**
提供更快的讀取操作，因為只需要一次數據庫查詢。
數據在應用層更直觀，因為相關的信息保持在一起。
**缺點：**
如果嵌入的文檔經常變更或成為很大，可能會導致數據管理上的問題。
過度的嵌入可能導致文檔大小超過MongoDB的16MB限制。

```json
{
  _id: 1,
  name: "John",
  address: {
    street: "123 Main St",
    city: "Springfield",
    zip: "12345"
  }
}
```

### b. Reference (引用) (非必要不建議這樣用)
**描述：** 文檔之間的關係可以使用引用來表示。這意味著一個文檔中包含另一個文檔的ID參考，而不是完整的內容。
**儲存：** 數據被分開存儲在不同的文檔或集合中。要查詢完整的數據，可能需要多個查詢或使用$lookup。

**優點：**
更靈活的數據結構，尤其是當數據關係較為複雜或多變時。
避免了文檔大小超過限制的問題。
**缺點：**
查詢時需要額外的查詢來解析參考，可能導致性能降低。

```json
// 在'users'集合中
{
  _id: 1,
  name: "John",
  addressId: 1001
}

// 在'addresses'集合中
{
  _id: 1001,
  street: "123 Main St",
  city: "Springfield",
  zip: "12345"
}

```

## 6. 基礎語法 

在MongoDB中，我們只要在應用程式或者Mongo Shell中下達指令，就可以馬上把資料漸入到其中，並不用事先定義我們的資料結構。

**creatCollection**
在資料庫中建立一個Collection
```
db.createCollection( <name>,
    {
      capped: <boolean>,
      timeseries: {                  // Added in MongoDB 5.0
         timeField: <string>,        // required for time series collections
         metaField: <string>,
         granularity: <string>,
         bucketMaxSpanSeconds: <number>,  // Added in MongoDB 6.3
         bucketRoundingSeconds: <number>  // Added in MongoDB 6.3
      },
      expireAfterSeconds: <number>,
      clusteredIndex: <document>,  // Added in MongoDB 5.3
      changeStreamPreAndPostImages: <document>,  // Added in MongoDB 6.0
      size: <number>,
      max: <number>,
      storageEngine: <document>,
      validator: <document>,
      validationLevel: <string>,
      validationAction: <string>,
      indexOptionDefaults: <document>,
      viewOn: <string>,
      pipeline: <pipeline>,
      collation: <document>,
      writeConcern: <document>
    }
  )
```

**SELECT**
把要查詢的條件，以Json格式呈現
```
db.movies.find( { title: "The Favourite" } )
```

**Insert**

直接將資料以Json方式增加一筆資料，若沒有先使用createCollection在這邊也不用擔心，當沒有Collection執行Insert指令時，他也會幫你建立出一個Collection。

```shell
use sample_mflix //選擇要寫入的資料庫

//movies 為 Collection
db.movies.insertOne(
  {
    title: "The Favourite",
    genres: [ "Drama", "History" ],
    runtime: 121,
    rated: "R",
    year: 2018,
    directors: [ "Yorgos Lanthimos" ],
    cast: [ "Olivia Colman", "Emma Stone", "Rachel Weisz" ],
    type: "movie"
  }
)
```

**UPDATE**

更新一筆資料條件符合Json內定義的資料

```
use sample_mflix

db.movies.updateOne( { title: "Twilight" },
{
  $set: {
    plot: "A teenage girl risks everything–including her life–when she falls in love with a vampire."
  },
  $currentDate: { lastUpdated: true }
})
```

更新多筆資料條件符合Json內定義的資料
```
use sample_airbnb

db.listingsAndReviews.updateMany(
  { security_deposit: { $lt: 100 } },
  {
    $set: { security_deposit: 100, minimum_nights: 1 }
  }
)
```

**DELETE**

刪除多筆條件符合Json內的定義資料
```
use sample_mflix

db.movies.deleteMany( { title: "Titanic" } )
```

刪除一筆條件符合Json內的定義資料
```
use sample_mflix

db.movies.deleteOne( { cast: "Brad Pitt" } )
```

## 7. Aggregate(聚合)

MongoDB提供了很多資料處理，簡單提供一個範例，更多的功能可以依據官方文檔說明進行使用。

https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/


在使用$lookup這個方法時可能要特別注意一下


**$lookup 說明**

作為一個NoSQL數據庫，最初是不支持傳統的JOIN操作的，因為它主要是基於文檔的設計。但在版本3.2之後，MongoDB引入了$lookup階段到聚合管道中，從而支持了類似JOIN的操作。這讓你可以將文檔從不同的集合結合在一起。

```shell
db.orders.aggregate([
   {
      $lookup:
         {
           from: "products",
           localField: "productId",
           foreignField: "_id",
           as: "productDetails"
         }
   }
])
```

上面這個範例就是相當於RDB的Join，這種情況就可能誤把NoSQL當作RDB的方式去設計和使用，這會大大捨棄掉了NoSQL的特性，也會造成效能的損失，不是特殊情況了話應避免使用。


## 二、 操作

IDE - Mongodb compass
IDE - studio 3T $$ 聽說超強大

### 1. [MongoDB Atlas](https://reurl.cc/l770zE)

MongoDB Atlas 是 MongoDB 的一個雲端服務。它讓你可以更容易地部署、管理和擴展 MongoDB 數據庫，而不需要自己去搞定所有底層的基礎設施和維護工作。簡單來說，他算是SAAS服務，基本上如果你涉獵公有雲和混合雲，使用 MongoDB Atlas 可能會更方便。可以輕易地與 AWS、GCP 和 Azure 等雲平台集成。使用上不難，就註冊直接使用就好...不過免費的只有512MB，但對於單純連線與資料操作就很夠用了。



### 2. MongoDB Compass

官方出的MongoDB管理GUI工具，除了可以做DB操作外，也可以將語法生成相對應程式碼(C#、Java、Python~)，[下載連結點我](https://www.mongodb.com/products/tools/compass)。

#### a. 連線

開始MongoDB Compass介面後，馬上就會看到關於連線機制操作，如下

![](https://hackmd.io/_uploads/rJT6ELclT.png)

如果適用Atlas，連線機制可在Project->Data Services->Database有個Connect按鈕，他會跳出不同的連線方式，請點選Compass

![](https://hackmd.io/_uploads/S1x2S89ga.png)

就會看到Url 

![](https://hackmd.io/_uploads/rJ7ABI5xp.png)

替換掉Password部分，輸入至New Connection URL處，按Connect即可連線~~~

#### b. 介面操作

主介面操作如下，黑色為資料面，紅色為操作面，紅色操作面主要為Query查詢操作及Generate Code操作。這兩者操作功能就猶如字面上的意思，也沒有需要注意的，就不多說了。其餘Tab功能如下

 - **Documents** : 查看、編輯、刪除和新增文檔（即數據）。直觀地查看和操作每個用戶的詳細資料。
 - **Aggregations** : 創建和測試資料聚合查詢。假如你想知道所有用戶的平均年齡，你可以在這個界面進行設置和測試，而不需要手動寫複雜的查詢代碼。
 - **Schema** : 查看集合（Collection）的結構和統計信息。助於你了解數據的分佈情況，例如，哪個欄位最常出現，數據類型是什麼等。
 - **Indexes** : 查看、新增或刪除索引。如果你發現查詢某個特定欄位（例如，email）很慢，你可以在這裡為該欄位添加索引，提高查詢速度。
 - **Validation** : 設置和查看 Schema 驗證規則。假如你的用戶資料必須包含 email 和電話號碼，你可以在這裡設置這些驗證規則，確保新增或修改的數據都符合這些標準。

![](https://hackmd.io/_uploads/ryxn1wclp.png)


其餘進階操作(Options)如下圖，

 - Filter : 跟SQL的Where條件很像，用於指定哪些Document（相當於 SQL 中的「行」）應該被選出來。
 - Project : 許你選擇只查找Document中的某些欄位。
 - Sort : 決定查詢結果如何排序。
 - Collation : 控制字符串（文字）比較的規則，比如大小寫敏感性、換字等。
 
 
 例如圖中所設定的參數為，
 
 查找name為Mercedes Tyler資料，只顯示date欄位，sort排序為遞減(-1)，Collation設定為查詢大小寫不敏感設置。
 
 - Primary（1）：僅比較基本的字符，忽略大小寫和重音符號。也就是說，"a" 和 "A"、"á" 會被視為相同。
- Secondary（2）：除了基本的字符比較外，還會考慮重音符號但忽略大小寫。例如，"a" 和 "A" 會被視為相同，但 "á" 會被視為不同。
- Tertiary（3）：會考慮基本字符、重音符號以及大小寫。所以，"a"、"A" 和 "á" 都會被視為不同。
- Quaternary（4）：在考慮所有上述因素的基礎上，還會考慮字符串中的標點符號和空白。
 
 ![](https://hackmd.io/_uploads/BytKh_qeT.png)



Reference
https://hevodata.com/learn/mongodb-sql/
https://blog.toright.com/posts/4483/mongodb-schema-設計指南.html
https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/
https://www.mongodb.com/docs/manual/reference/method/js-database/
https://www.mongodb.com/community/forums/t/frequently-asked-questions-atlas-serverless-instances/131992
