既然有MySQL了,為什麼還要有MongoDB?

2023-01-07 06:00:27

大家好,我是哪吒,最近專案在使用MongoDB作為圖片和檔案的儲存資料庫,為啥不直接存MySQL裡,還要搭個MongoDB叢集,麻不麻煩?

讓我們一起,一探究竟,瞭解一下MongoDB的特點和基本用法,實現快速入門,豐富個人簡歷,提高面試level,給自己增加一點談資,秒變面試小達人,BAT不是夢

三分鐘你將學會:

  1. MongoDB主要特徵
  2. MongoDB優缺點,揚長避短
  3. 何時選擇MongoDB?為啥要用它?
  4. MongoDB與MySQL關鍵字對比
  5. 下載與安裝過程中一些常見的坑
  6. Java整合MongoDB,實現農民工增刪改查

一、基本概念走起

MongoDB是一款開源、跨平臺、分散式,具有巨量資料處理能力的檔案儲存資料庫。

檔案資料庫MongoDB用於記錄檔案結構的資料,比如JSON、XML結構的資料。

二、MongoDB的主要特徵

  1. 高效能。提供JSON、XML等可嵌入資料快速處理功能,提供檔案的索引功能,以提高查詢速度;
  2. 豐富的查詢語言。為資料聚合、結構檔案、地理空間提供豐富的查詢功能;
  3. 高可用性。提供自動故障轉移和資料冗餘處理功能;
  4. 水平擴充套件能力。提供基於多伺服器叢集的分散式資料處理能力,具體處理時分主從權衡(基於Hash自動推選)兩種處理模式;
  5. 支援多種儲存引擎。MongoDB提供多種儲存引擎,WiredTiger引擎MMAPv1引擎是基於硬碟讀寫的儲存引擎,In-Memory引擎是基於記憶體的儲存引擎;

三、MongoDB優缺點,揚長避短

1、優點

  1. Free-schema無模式檔案,適應非結構化資料儲存;
  2. 內建GridFS,支援大容量的儲存;
  3. 內建Sharding,分片簡單
  4. 弱一致性(最終一致),更能保證使用者的存取速度;
  5. 查詢效能優越,對於千萬級別的檔案物件,差不多10個G,對有索引的ID的查詢不會比MySQL慢,而對非索引欄位的查詢,則是完勝MySQL;
  6. 聚合框架,它支援典型幾種聚合操作 , 比如,Aggregate pipelien, Map-Reduce等;
  7. 支援自動故障恢復

2、缺點

  1. 太吃記憶體,快是有原因的,因為MongoDB把資料都放記憶體裡了;
  2. 不支援事務操作;
  3. 佔用空間過大;
  4. 不支援聯表查詢;
  5. 只有最終一致性,言外之意,就是可能造成資料的不一致,如果想要保持強一致性,必須在一個伺服器處理所有的讀寫操作,坑;
  6. 複雜聚合操作通過mapreduce建立,速度慢
  7. Mongodb全域性鎖機制也是個坑;
  8. 預分配模式會帶來的磁碟瓶頸;
  9. 刪除記錄時不會釋放空間,相當於邏輯刪除,這個真的坑;
  10. MongoDB到現在為止,好像還沒有太好用的使用者端工具;

四、何時選擇MongoDB?為啥要用它?

1、MongoDB事務

MongoDB目前只支援單檔案事務,MongoDB暫時不適合需要複雜事務的場景。
靈活的檔案模型JSON格式儲存最接近真實物件模型,對開發者友好,方便快速開發迭代,可用複製集滿足資料高可靠、高可用的需求,運維較為簡單、故障自動切換可延伸分片叢集海量資料儲存。

2、多引擎支援各種強大的索引需求

  • 支援地理位置索引
  • 可用於構建各種O2O應用
  • 文字索引解決搜尋的需求
  • TTL索引解決歷史資料過期的需求
  • Gridfs解決檔案儲存的需求
  • aggregation & mapreduce解決資料分析場景需求,可以自己寫查詢語句或指令碼,將請求分發到 MongoDB 上完成。

3、具體的應用場景

傳統的關係型資料庫在解決三高問題上的力不從心。
何為三高?

  • High performance - 對資料庫高並行讀寫的需求。
  • Huge Storage - 對海量資料的高效率儲存和存取的需求。
  • High Scalability && High Availability- 對資料庫的高可延伸性和高可用性的需求。

MongoDB可以完美解決三高問題。

4、以下是幾個實際的應用案例:

(1)遊戲場景

使用MongoDB儲存遊戲使用者資訊、裝備、積分等,直接以內嵌檔案的形式儲存,方便查詢、更新。

(2)物流場景

使用MongoDB儲存訂單資訊、訂單狀態、物流資訊,訂單狀態在運送過程中飛速迭代、以MongoDB內嵌陣列的形式來儲存,一次查詢就能將訂單所有的變更查出來,牛逼plus。

(3)社交場景

使用MongoDB儲存使用者資訊,朋友圈資訊,通過地理位置索引實現附近的人、定位功能。

(4)物聯網場景

使用MongoDB儲存裝置資訊、裝置彙報的紀錄檔資訊、並對這些資訊進行多維度分析。

(5)視訊直播

使用MongoDB儲存使用者資訊、點贊互動資訊。

5、選擇MongoDB的場景總結:

  • 資料量大
  • 讀寫操作頻繁
  • 資料價值較低,對事務要求不高

五、MongoDB與MySQL關鍵字對比

1、關鍵字對比

MySQL MongoDB 解釋說明
database database 資料庫
table collection 表/集合
row document 行/檔案
column field 欄位/域
index index 索引
join 嵌入檔案 表關聯/MongoDB不支援join,MongoDB通過嵌入式檔案來替代多表連線
primary key primary key 主鍵/MongoDB自動將_id欄位設定為主鍵

2、集合相當於MySQL中的表

集合就是一組檔案。可以看作是具有動態模式的表。

集合具有動態模式的特性。這意味著一個集合中的檔案可以具有任意數量的不同形態。

但是,將不同型別的檔案存放在一個集合中會出現很多問題:

  1. 檔案中可以存放任意型別的變數,但是,這裡不建議將不同型別的檔案儲存在同一個集合中,開發人員需要確保每個查詢只返回特定模式的檔案,或者確保執行查詢的應用程式程式碼可以處理不同型別的檔案;
  2. 獲取集合列表比提取集合中的檔案型別列表要快得多,減少磁碟查詢次數;
  3. 相同型別的檔案存放在同一個集合中可以實現資料的區域性性,對於集合,讓使用者見文知意;
  4. 集合中只存放單一型別的檔案,可以更高效地對集合進行索引;

3、集合的命名

  1. 集合名稱中不能是空字串;
  2. 集合名稱不能包含\0(空字元),因為這個字元用於表示一個集合名稱的結束;
  3. 集合名稱不能以system.開頭,該字首是為內部集合保留的。
  4. 集合名稱不能有$,只能在某些特定情況下使用。通常情況下,可以認為這兩個字元是MongoDB的保留字元,如果使用不當,那麼驅動程式將無法正常工作。

4、檔案相當於MySQL中的行

檔案是MongoDB中的基本資料單元,相當於傳統關係型資料庫中的行,它是一組有序鍵值的集合。每個檔案都有一個特殊的鍵「_id」,其在所屬的集合中是唯一的。

檔案中的鍵是字串型別。

鍵中不能含有\0(空字元)。這個字元用於表示一個鍵的結束。
.和$是特殊字元,只能在某些特定情況下使用。通常情況下,可以認為這兩個字元是MongoDB的保留字元,如果使用不當,那麼驅動程式將無法正常工作。

5、遊標

資料庫會使用遊標返回find的執行結果。遊標的使用者端實現通常能夠在很大程度上對查詢的最終輸出進行控制。你可以限制結果的數量,跳過一些結果,按任意方向的任意鍵組合對結果進行排序,以及執行許多其他功能強大的操作。

通過cursor.hasNext()檢查是否還有其它結果,通過cursor.next()用來對其進行獲取。

呼叫find()時,shell並不會立即查詢資料庫,而是等到真正開始請求結果時才傳送查詢,這樣可以在執行之前給查詢附加額外的選項。cursor物件的大多數方法會返回遊標本身,這樣就可以按照任意順序將選項連結起來了。

在使用db.users.find();查詢時,實際上查詢並沒有真正執行,只是在構造查詢,執行cursor.hasNext(),查詢才會發往伺服器端。shell會立刻獲取前100個結果或者前4MB的資料(兩者之中的較小者),這樣下次呼叫next或者hasNext時就不必再次連線伺服器去獲取結果了。在使用者端遍歷完第一組結果後,shell會再次連線資料庫,使用getMore請求更多的結果。getMore請求包含一個遊標的識別符號,它會向資料庫詢問是否還有更多的結果,如果有則返回下一批結果。這個過程會一直持續,直到遊標耗盡或者結果被全部返回。

6、遊標的生命週期

在伺服器端,遊標會佔用記憶體和資源。一旦遊標遍歷完結果之後,或者使用者端傳送一條訊息要求終止,資料庫就可以釋放它正在使用的資源。

何時銷燬遊標:

  1. 當遊標遍歷完匹配的結果時,它會消除自身;
  2. 當遊標超出使用者端的作用域時,驅動程式會向資料庫傳送一條特殊的訊息,讓資料庫終止該遊標;
  3. 如果10分鐘沒有被使用的話,資料庫遊標也將自動銷燬;

六、下載與安裝過程中一些常見的坑

1、下載地址:https://www.mongodb.com/try/download/community2

2、設定環境變數D:\Program Files\MongoDB\Server\5.0\bin

3、在bin目錄下,重新開啟一個視窗,D:\Program Files\MongoDB\Server\5.0\bin,開啟cmd,輸入MongoDB

4、如果msi方式失敗,可以下載zip檔案進行安裝。

下載zip檔案,解壓,在bin同級目錄下建data資料夾,在data下建一個db資料夾,儲存MongoDB資料。

在bin資料夾下執行cmd,執行mongod --dbpath D:\Program Files\mongodb\data\db命令;

再在data目錄下,建一個logs資料夾,存放MongoDB紀錄檔。

在mongodb/bin目錄下,建一個mongod.cfg檔案,寫入

systemLog:
    destination: file
    logAppend: true
    path: D:\Program Files\mongodb\data\logs\mongod.log
storage:
    dbPath: D:\Program Files\mongodb\data\db

執行mongod --config "D:\Program Files\mongodb\bin\mongod.cfg" --install 命令,安裝MongoDB。

通過mongod --version檢查MongoDB版本。

D:\Program Files\mongodb\bin>mongod --version
db version v5.0.14
Build Info: {
    "version": "5.0.14",
    "gitVersion": "1b3b0073a0b436a8a502b612f24fb2bd572772e5",
    "modules": [],
    "allocator": "tcmalloc",
    "environment": {
        "distmod": "windows",
        "distarch": "x86_64",
        "target_arch": "x86_64"
    }
}

5、mongodb由於目標計算機積極拒絕,無法連線

突然間,mongodb無法連線了?mongod.exe --dbpath "D:\Program Files\mongodb\data完美解決。

注意一點,在重新啟動時,執行mongod.exe --dbpath "D:\Program Files\mongodb\data的視窗不要關閉。

6、由於找不到vcruntime140_1.dll,無法繼續執行程式碼

1、下載vcruntime140_1.dll檔案
2、將vcruntime140_1.dll檔案拷貝到C:\Windows\System32即可

七、Java整合MongoDB,實現農民工增刪改查

1、加入POM

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.8.2</version>
</dependency>

2、MongoDBUtil工具類

package com.example.demo.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.bson.Document;
import org.bson.conversions.Bson;

import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;

public class MongoDBUtil {

    private static MongoClient mongoClient;

    private static MongoClient mongoClientIdentify;

    /**
     * 不通過認證獲取連線資料庫物件
     */
    public static MongoDatabase getNoIdentifyConnect(String host, int port, String dbaseName) {
        // 連線mongodb服務
        MongoDBUtil.mongoClient = new MongoClient(host, port);

        // 連線資料庫
        MongoDatabase mongoDatabase = MongoDBUtil.mongoClient.getDatabase(dbaseName);

        // 返回連線資料庫物件
        return mongoDatabase;
    }

    /**
     * 通過連線認證獲取MongoDB連線
     */
    public static MongoDatabase getIdentifyConnect(String host, int port, String dbaseName, String userName, String password) {

        List<ServerAddress> adds = new ArrayList<ServerAddress>();

        ServerAddress serverAddress = new ServerAddress(host, port);
        adds.add(serverAddress);

        List<MongoCredential> credentials = new ArrayList<>();

        MongoCredential mongoCredential = MongoCredential.createScramSha1Credential(userName, dbaseName, password.toCharArray());
        credentials.add(mongoCredential);

        // 通過連線認證獲取MongoDB連線
        MongoDBUtil.mongoClientIdentify = new MongoClient(adds, credentials);

        MongoDatabase mongoDatabase = MongoDBUtil.mongoClientIdentify.getDatabase(dbaseName);

        return mongoDatabase;
    }

    /**
     * 關閉連線
     */
    public static void closeNoIdentifyConnect () {
        MongoDBUtil.mongoClient.close();
    }

    /**
     * 關閉連線
     */
    public static void closeIdentifyConnect () {
        MongoDBUtil.mongoClientIdentify.close();
    }

    /**
     * 插入一個檔案
     */
    public static void insertOne (Map<String, Object> data, MongoDatabase mongoDatabase, String col) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);

        //建立檔案
        Document document = new Document();

        for (Map.Entry<String, Object> m : data.entrySet()) {
            document.append(m.getKey(), m.getValue()).append(m.getKey(), m.getValue());
        }

        //插入一個檔案
        collection.insertOne(document);
    }

    /**
     * 插入多個檔案
     */
    public static void insertMany (List<Map<String, Object>> listData, MongoDatabase mongoDatabase, String col) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);

        //要插入的資料
        List<Document> list = new ArrayList<>();
        for (Map<String, Object> data : listData) {
            //建立檔案
            Document document = new Document();

            for (Map.Entry<String, Object> m : data.entrySet()) {
                document.append(m.getKey(), m.getValue());
            }
            list.add(document);
        }

        //插入多個檔案
        collection.insertMany(list);
    }

    /**
     * 刪除匹配到的第一個檔案
     */
    public static void delectOne (String col, String key, Object value, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //申明刪除條件
        Bson filter = Filters.eq(key, value);
        //刪除與篩選器匹配的單個檔案
        collection.deleteOne(filter);
    }

    /**
     * 刪除匹配的所有檔案
     */
    public static void deleteMany (String col, String key, Object value, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //申明刪除條件
        Bson filter = Filters.eq(key, value);
        //刪除與篩選器匹配的所有檔案
        collection.deleteMany(filter);
    }

    /**
     * 刪除集合中所有檔案
     */
    public static void deleteAllDocument(String col, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        collection.deleteMany(new Document());
    }

    /**
     * 刪除檔案和集合。
     */
    public static void deleteAllCollection(String col, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        collection.drop();
    }

    /**
     * 修改單個檔案,修改過濾器篩選出的第一個檔案
     *
     * @param col 修改的集合
     * @param key 修改條件的鍵
     * @param value 修改條件的值
     * @param eqKey 要修改的鍵,如果eqKey不存在,則新增記錄
     * @param eqValue 要修改的值
     * @param mongoDatabase 連線資料庫物件
     */
    public static void updateOne (String col, String key, Object value,String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //修改過濾器
        Bson filter = Filters.eq(key, value);
        //指定修改的更新檔案
        Document document = new Document("$set", new Document(eqKey, eqValue));
        //修改單個檔案
        collection.updateOne(filter, document);
    }

    /**
     * 修改多個檔案
     *
     * @param col 修改的集合
     * @param key 修改條件的鍵
     * @param value 修改條件的值
     * @param eqKey 要修改的鍵,如果eqKey不存在,則新增記錄
     * @param eqValue 要修改的值
     * @param mongoDatabase 連線資料庫物件
     */
    public static void updateMany (String col, String key, Object value, String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //修改過濾器
        Bson filter = Filters.eq(key, value);
        //指定修改的更新檔案
        Document document = new Document("$set", new Document(eqKey, eqValue));
        //修改多個檔案
        collection.updateMany(filter, document);
    }

    /**
     * 查詢集合中的所有檔案
     */
    public static MongoCursor<Document> find (String col, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //查詢集合中的所有檔案
        FindIterable<Document> findIterable = collection.find();
        MongoCursor<Document> cursorIterator = findIterable.iterator();
        return cursorIterator;
    }

    /**
     * 按條件查詢集合中檔案
     */
    public static MongoCursor<Document> Filterfind (String col,String key, Object value, MongoDatabase mongoDatabase) {
        //獲取集合
        MongoCollection<Document> collection = mongoDatabase.getCollection(col);
        //指定查詢過濾器
        Bson filter = Filters.eq(key, value);

        //指定查詢過濾器查詢
        FindIterable<Document> findIterable = collection.find(filter);
        MongoCursor<Document> cursorIterator = findIterable.iterator();
        return cursorIterator;
    }
}

3、測試類

<dependency>
   <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
package com.example.demo.utils;

import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MongoDBTest {

    // 獲取資料庫連線物件
    MongoDatabase mongoDatabase = MongoDBUtil.getNoIdentifyConnect("127.0.0.1", 27017, "test");

    @Test
    public void insertOne() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("姓名", "哪吒程式設計");
        map.put("性別", "男");
        map.put("年齡", 18);
        MongoDBUtil.insertOne(map, mongoDatabase, "worker");
        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void insertMany() {
        Map<String, Object> map1 = new HashMap<String, Object>();
        map1.put("姓名", "哪吒程式設計2");
        map1.put("性別", "男");
        map1.put("年齡", 18);
        Map<String, Object> map2 = new HashMap<String, Object>();
        map2.put("姓名", "妲己");
        map2.put("性別", "女");
        map2.put("年齡", 18);
        List<Map<String, Object>> listData = new ArrayList<>();
        listData.add(map1);
        listData.add(map2);
        MongoDBUtil.insertMany(listData, mongoDatabase, "worker");
        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void delectOne() {
        MongoDBUtil.delectOne("worker", "姓名", "妲己", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void deleteMany() {

        MongoDBUtil.deleteMany("worker", "姓名", "哪吒程式設計", mongoDatabase);
        MongoDBUtil.deleteMany("worker", "姓名", "妲己", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void deleteAllDocument() {
        MongoDBUtil.deleteAllDocument("worker", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void deleteAllCollection() {

        MongoDBUtil.deleteAllCollection("worker", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void updateOne() {

        MongoDBUtil.updateOne("worker", "姓名", "哪吒程式設計2","姓名", "哪吒程式設計", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void updateMany() {

        MongoDBUtil.updateMany("worker", "姓名", "哪吒程式設計2","姓名", "哪吒程式設計", mongoDatabase);

        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void find() {
        MongoCursor<Document> mongoCursor = MongoDBUtil.find("worker", mongoDatabase);

        while (mongoCursor.hasNext()) {
            Document document = mongoCursor.next();
            System.out.println(document + "  size: " + document.size());
        }
        MongoDBUtil.closeNoIdentifyConnect();
    }

    @Test
    public void filterfind() {
        MongoCursor<Document> mongoCursor = MongoDBUtil.Filterfind("worker", "姓名", "哪吒程式設計", mongoDatabase);

        while (mongoCursor.hasNext()) {
            Document document = mongoCursor.next();
            System.out.println(document + "  size: " + document.size());
        }
        MongoDBUtil.closeNoIdentifyConnect();
    }
}