首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB

编程知识
2024年09月02日 07:18

目录

前言

在笔者 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

对于一个 MongoDB 的文档来说,里面可以包含很多这个集合的属性,就像一篇文章里面有很多章节一样。

以下面这个图2-1为例子,activity 是一个完整的集合,里面包含了很多属性,id、name、status等基本属性,还有 button 和 share 等额外属性,这些属性共同构成了这个集合。

但这样的结构在 MySQL 里是不能实现的,理由很简单,MySQL 强调关系,1:1 和 1:N 是十分常见的关系。可以看到,下面将基本属性放在 activity 作为主表,而其它额外属性分别放在了 button 表和 share 表里,同时将主表的主键 id 作为了关联表的 ac_id 外键。

图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 的范式关系,用主键-外键的方式做关联

最后,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

From:https://www.cnblogs.com/CodeBlogMan/p/18257793
本文地址: http://www.shuzixingkong.net/article/1648
0评论
提交 加载更多评论
其他文章 博客园20年纪念T恤上架:艰难的时光,燃烧的希望
2024年9月开始了,救园的最后一个月到了,园子的20年纪念T恤终于赶在天凉好个秋之前上架了。用这件设计简约清新给人希望的T恤,纪念过去二十年充满艰难而满怀希望的时光。园子的二十年,是困难重重的二十年,是走了很多弯路摔了很多跟头的二十年,但也是心中一直燃烧着希望的二十年
博客园20年纪念T恤上架:艰难的时光,燃烧的希望 博客园20年纪念T恤上架:艰难的时光,燃烧的希望 博客园20年纪念T恤上架:艰难的时光,燃烧的希望
【工程应用十二】Bayer图像格式中Hamilton-Adams及Zhang Wu 基于LMMSS算法的Demosaic过程优化。
Demosaic,中文直接发翻译为去马赛克, 但是其本质上并不是去除马赛克,这让很多第一次接触这个名词或者概念的人总会想的更多。本文提供了经典的HA算法代码,并针对HA的缺点,分析了Zhang Wu基于LMMSE算法改进后的优化过程。
【工程应用十二】Bayer图像格式中Hamilton-Adams及Zhang Wu 基于LMMSS算法的Demosaic过程优化。 【工程应用十二】Bayer图像格式中Hamilton-Adams及Zhang Wu 基于LMMSS算法的Demosaic过程优化。 【工程应用十二】Bayer图像格式中Hamilton-Adams及Zhang Wu 基于LMMSS算法的Demosaic过程优化。
Go plan9 汇编:说透函数栈
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 函数是 Go 的一级公民,本文从汇编角度出发看看我们常用的一些函数在干什么。 1. 函数 1.1 main 函数 在 main 函数中计算两数之和如下: package main func main() { x, y := 1, 2 z :=
Go plan9 汇编:说透函数栈 Go plan9 汇编:说透函数栈 Go plan9 汇编:说透函数栈
失败的十年,回顾反思
开局 依稀记得那是2014年11月大四上学期,学校已经没有课了。看着同寝室的其他室友都出去实习了,而我一个人还坐在电脑前发呆。因为的不敢出去面试。由于小学时牙齿有一颗龅牙,从小就产生了自卑的心理,也让自己有了严重的社交恐惧,我开始不敢一个人买车票,甚至不敢自己去食堂吃饭。从小学到大学,求学的过程中,
以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题?
开心一刻 清明节那天,看到一小孩在路边烧纸时不时地偷偷往火堆里扔几张考试卷子边烧边念叨:爷爷呀,你岁数大了,在那边多做做题吧,对脑子好,要是有不懂的地方,就把我老师带走,让他教您! 前提说明 假设 MySQL 5.7.36 的库 qsl_datax 有表 qsl_datax_source 和 数据
以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题? 以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题? 以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题?
金九银十来了,你的简历写好了么?
大家好,我是晓凡。 写在前面 时间过得真快,转眼间就来到了九月份。都说金九银十,又到了一年一度的求职跳槽黄金季。 这两年就业环境应该不用我多说了吧,只能用一个惨字来形容。 即使就业环境再怎么不堪,但毕业了工作总要找的,总不可能啃老摆烂吧。 在这竞争激烈的时期,一份优质的简历,无疑是你求职路上的敲门砖
金九银十来了,你的简历写好了么? 金九银十来了,你的简历写好了么? 金九银十来了,你的简历写好了么?
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
我们在某宝或某多多上抢购商品时,如果只是下了订单但没有进行实际的支付,那在订单页面会有一个支付倒计时,要是过了这个时间点那么订单便会自动取消。在这样的业务场景中,一般情况下就会使用到延时队列。
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践 PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践 PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【ETL工具】DataX + DataXWeb 初使用过程记录
ETL:将大量的原始数据,经过抽取(Extract)和清洗转换(Transform)后,加载(Load)到目的端的过程,称为ETL,实现这种过程的工具,也就是ETL工具 版本:DataX v202309 DataXWeb 2.1.3-alpha-release DataX:阿里云开源的一个异构数据源
【ETL工具】DataX + DataXWeb  初使用过程记录 【ETL工具】DataX + DataXWeb  初使用过程记录 【ETL工具】DataX + DataXWeb  初使用过程记录