【解決方案】專案重構之如何使用 MySQL 替換原來的 MongoDB

CodeBlogMan發表於2024-09-02

目錄
  • 前言
  • 一、痛點所在
  • 二、選型分析
    • 2.1特點對比
    • 2.2場景對比
  • 三、核心思路
  • 四、demo 示例
    • 4.1實體對映
      • 4.1.1MongoDB 實體
      • 4.1.2MySQL 實體
    • 4.2查詢程式碼
      • 4.2.1MongoDB 查詢
      • 4.2.2MySQL 查詢
  • 五、文章小結

前言

在筆者 Java 後端開發的專案經歷中,MySQL 和 MongoDB 都有使用過作為後端的資料庫來對業務資料進行持久化,兩者沒有孰優孰劣之分,都可以在合適的場景下發揮出它們的優勢。

今天要分享的是一個專案重構過程中如何將資料庫選型由原來的 MongoDB 改為 MySQL 的思考,涉及到業務當前的痛點、選型分析、解決的核心思路,最後會給出簡單的 demo。

本篇文章側重在於兩者在表設計思維上的轉換,而業務資料遷移同步的方案,下一篇文章將給出。


一、痛點所在

該專案是一個【PC端管理後臺】+【移動端h5頁面】為主業務框架的系統,原來的預期是:在後臺配置好活動所需的引數,h5 既可以放在 app 客戶端開啟,也可以作為url 連結的形式直接在瀏覽器開啟。專案一期的時候,業務方認為這樣的運營活動會帶來不少的流量和使用者。但是到後來業務重心有所調整,引流的方式發生變化,最終導致了專案的一個重構。

主要的原因有以下幾點:

  1. 總體的資料量沒有預想的那麼大

    活動參與人數前期預估為30w+,經歷過2個線上活動後的實際總參與人數為5w+,客戶端註冊使用者數為3w+,佔全部參與人數的65%左右,遠不及預期規模;

  2. 核心介面的併發也沒有預想的高

    h5 端的大約 5-8 個的核心介面在實際線上活動進行的最高 QPS 只達到 200-300 左右,CPU 與 記憶體佔用率也未達到設定的告警線(60%);

  3. MySQL 在硬體資源成本上價效比更高

    以阿里雲的 RDS for MySQL 與 雲資料庫 MongoDB 做對比,都是叢集部署 + 8核16GB + 100GB 儲存 + 1年時長的規格下,前者會比後者便宜7w+RMB;

  4. MySQL 的動態資料來源切換方案更成熟

    當時後端的專案已經被全部要求接入多租戶改造,市面上開源的、成熟的動態資料來源切換方案並不多,而完全專門支援 MongoDB 的是少之又少。

綜合以上幾點原因,完全放棄該專案是沒必要的,但也需要適應當前業務的變化和成本控制,預計花費30人/天,即 2 個後端開發在 2-3 周內完成對該系統的重構,介面和前端頁面基本無需調整。


二、選型分析

這裡就正式進入技術部分了,首要對比的是兩者各自的特點以及適用的場景,這對於把握整個專案的走向是至為關鍵的。

2.1特點對比

表2-1
對比項 MySQL MongoDB
資料模型 關係型資料庫,採用表格(table)的形式儲存資料,每一行是一條記錄 非關係型(NoSQL)、文件型資料庫,資料以文件(document)的非結構化形式儲存
查詢方式 使用標準的 SQL 進行查詢,提供了豐富的查詢條件、連線(join)、排序、分頁等功能 使用基於 JSON 結構特點的的查詢語句,支援大量資料的聚合、統計、分析
事務支援 支援 ACID 事務,確保在多條操作組成的事務中資料的一致性和可靠性。特別是在InnoDB引擎中,提供了完整的事務支援 4.0 版本開始引入了多文件事務支援,可以保證在一定範圍內的讀寫操作具備ACID特性。但對於需要嚴格事務特性的複雜業務場景不及 MySQL 成熟
資料處理 在處理複雜查詢和高併發寫入時,需要依賴索引來最佳化效能,或者透過分割槽、分片等手段進行水平擴充套件 在水平擴充套件和實時資料處理方面優勢很大,透過分片(sharding)技術可以輕鬆應對海量資料儲存和高併發讀寫
空間佔用 由於資料結構緊湊,對資料的儲存通常更為節省空間,特別是對於簡單資料結構和關係清晰的資料集 由於文件儲存的靈活性和包含後設資料等因素,通常佔用空間較大
專案整合 已經有成熟的第三方 ORM 框架支援,如:Mybatis、Mybatis Plus、io.mybatis、tk.mybatis等 目前整合在 Spring Boot 專案裡的增刪改查都是基於 MongoRepository 和 MongoTemplate 來實現的

2.2場景對比

  • MySQL
    1. Web 應用程式:如常見的 xx 管理後臺、xx 管理系統,電商 web 網站,包括一些移動端 h5 的頁面等;
    2. 企業級應用:如常見的客戶關係管理系統(CRM)、人力資源管理系統(HRM)和供應鏈管理系統(SCM)等,MySQL 提供了強大的事務支援;
    3. 嵌入式開發:需要輕量級資料庫的軟體、硬體和裝置,MySQL 可以作為一個嵌入式資料庫引擎整合到各種應用程式中,提高應用程式的可移植性;
    4. 雲端計算和大資料:MySQL 在雲資料庫服務中被廣泛使用,支援雲原生應用程式和分散式資料處理框架,如 Hadoop 和 Spark 等。
  • MongoDB
    1. 處理實時資料:非常適合處理移動網際網路應用常見的大部分場景,如使用者活動、社互動動、線上購物等;
    2. 內容管理系統(CMS):用於處理文章、稿件、評論、圖片、影片等富媒體內容的儲存和增刪改查,支援全文搜尋和實時更新;
    3. 資料聚合倉庫:儲存原始或半處理的業務資料,利用聚合框架進行實時資料聚合、統計分析和資料視覺化;
    4. 遊戲資料管理:儲存玩家賬戶資訊、遊戲進度、成就、虛擬物品、社交關係等,快速計算和更新遊戲排行榜資料,支援實時查詢等。

三、核心思路

我們知道,在 MongoDB 中,一條資料的記錄(文件)格式是 json 的 格式,即強調 key-value 的關係。

表2-2
【解決方案】專案重構之如何使用 MySQL 替換原來的 MongoDB

對於一個 MongoDB 的文件來說,裡面可以包含很多這個集合的屬性,就像一篇文章裡面有很多章節一樣。

以下面這個圖2-1為例子,activity 是一個完整的集合,裡面包含了很多屬性,id、name、status等基本屬性,還有 button 和 share 等額外屬性,這些屬性共同構成了這個集合。

但這樣的結構在 MySQL 裡是不能實現的,理由很簡單,MySQL 強調關係,1:1 和 1:N 是十分常見的關係。可以看到,下面將基本屬性放在 activity 作為主表,而其它額外屬性分別放在了 button 表和 share 表裡,同時將主表的主鍵 id 作為了關聯表的 ac_id 外來鍵。

【解決方案】專案重構之如何使用 MySQL 替換原來的 MongoDB
圖2-1

那要怎麼替換才能實現呢?MongoDB 改成 MySQL 的核心在於:原有的集合關係以及巢狀關係,需要拆表成1 : N 的正規化關係,用主鍵-外來鍵的方式做關聯查詢,同時避免 join 連線查詢。


四、demo 示例

下面首先分別給出實際的表設計與實體對映,包括 MongoDB 和 MySQL 的,然後再透過簡單的查詢程式碼來體現兩者的區別。

4.1實體對映

4.1.1MongoDB 實體
@EqualsAndHashCode(callSuper = true)
@Data
public class Activity extends BaseEntity {
    @Id
    private String id;
    private String name;
    private ActivityStatusEnum status;
    private ReviewStatusEnum review;
    private ActivityTypeEnum type;
    private ActivityButton button;
    private ActivityShare share;
}
4.1.2MySQL 實體
@Data
public class Activity extends BaseEntity {
    @Id
    private Integer id;
    private String name;
    private Integer status;
    private Integer review;
    private Integer type;
}
@Data
public class ActivityButton extends BaseEntity {
    @Id
    private Integer id;
    private Integer acId;
    private String signUp;
    private Integer status;
    private String desc;
}
@Data
public class ActivityShare extends BaseEntity {
    @Id
    private String id;
    private Integer acId;
    private String title;
    private String iconUrl;
}

4.2查詢程式碼

下面就根據主鍵 id 和狀態這兩個條件進行活動詳情的查詢。

4.2.1MongoDB 查詢
    /**
     * @apiNote 透過主鍵id和活動狀態查詢活動
     * @param id 主鍵id
     * @return 實體
     */
    @Override
    public Avtivity getDetailById(String id) {
        return this.repository.findById(id)
                .filter(val -> ActivityStatusEnum.ON.equals(val.getStatus()))
                .orElseThrow(() -> new RuntimeException("該活動不存在!"));
    }
4.2.2MySQL 查詢
    @Resource
    private ActivityShareService activityShareService;
    @Resource
    private ActivityButtonService activityButtonService;
    @Override
    public ActivityVO detail(Integer id) {
        ExampleWrapper<Activity, Serializable> wrapper = this.wrapper();
        wrapper.eq(Activity::getid, id)
                .eq(Activity::getStatus(), DataStatusEnum.NORMAL.getCode());
        Activity activity = Optional.ofNullable(this.findOne(wrapper.example()))
            .orElseThrow(() -> new RuntimeException("該活動不存在!"));
        ActivityVO vo = new ActivityVO();
        vo.setName(Optional.ofNullable(activity.getName()).orElse(StringUtils.EMPTY));
        //查兩個關聯表
        vo.setShare(this.activityShareService.getShare(activity.getId()));
        vo.setButton(this.activityButtonService.getButton(activity.getId()));
        return vo;
    }

五、文章小結

使用 MySQL 替換 MongoDB 的小結如下:

  1. 做技術選型時要充分考慮對比兩者的特點以及應用場景,選擇最合適的
  2. 如非必要,那麼還是繼續沿用原來的設計;一旦選擇重構,那麼就要考慮成本
  3. 原有的集合關係以及巢狀關係,需要拆表成1 : N 的正規化關係,用主鍵-外來鍵的方式做關聯

最後,如有不足和錯誤,還請大家指正。或者你有其它想說的,也歡迎大家在評論區交流!

相關文章