前言

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

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

【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB的更多相关文章

  1. 【转】vue项目重构技术要点和总结

    vue数据更新, 视图未更新 这个问题我们经常会遇到,一般是vue数据赋值的时候,vue数据变化了,但是视图没有更新.这个不算是项目重构的技术要点,也和大家分享一下vue2.0通常的解决方案吧! 解决 ...

  2. freeswitch 使用mysql替换默认的sqlite

    转自 80000hz.com freeswitch 使用mysql替换默认的sqlite No Reply , Posted in 默认分类 on January 14, 2014 目标使用mysql ...

  3. iOS基于MVC的项目重构总结

    关于MVC的争论 关于MVC的争论已经有很多,对此我的观点是:对于iOS开发中的绝大部分场景来说,MVC本身是没有问题的,你认为的MVC的问题,一定是你自己理解的问题(资深架构师请自动忽略本文). 行 ...

  4. 转:iOS基于MVC的项目重构总结

    转:http://www.cocoachina.com/ios/20160519/16346.html 关于MVC的争论 关于MVC的争论已经有很多,对此我的观点是:对于iOS开发中的绝大部分场景来说 ...

  5. Mysql笔记之 -- replace()实现mysql 替换字符串

    mysql 替换函数replace()实现mysql 替换字符串 mysql 替换字符串的实现方法:  mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数 ...

  6. mysql替换字段里数据内容部分字符串(亦可用于增加字段中的内容)

    mysql替换表的字段里面内容,如例子: mysql> select host,user from user  where user='testuser'; +----------------- ...

  7. mysql 替换函数replace()实现mysql 替换字符串

    mysql 替换字符串的实现方法:mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数去替换,用起来非常的方便,mysql 替换函数replace()Upd ...

  8. (转)Android项目重构之路:实现篇

    前两篇文章Android项目重构之路:架构篇和Android项目重构之路:界面篇已经讲了我的项目开始搭建时的架构设计和界面设计,这篇就讲讲具体怎么实现的,以实现最小化可用产品(MVP)的目标,用最简单 ...

  9. (转)Android项目重构之路:界面篇

    在前一篇文章<Android项目重构之路:架构篇>中已经简单说明了项目的架构,将项目分为了四个层级:模型层.接口层.核心层.界面层.其中,最上层的界面,是变化最频繁的一个层面,也是最复杂最 ...

  10. Hive(2)-Hive的安装,使用Mysql替换derby,以及一丢丢基本的HQL

    一. Hive下载 1. Hive官网地址 http://hive.apache.org/ 2. 文档查看地址 https://cwiki.apache.org/confluence/display/ ...

随机推荐

  1. 如何在 Windows 使用 Podman Desktop 取代 Docker Desktop

    Podman Desktop 是 Docker Desktop 的免费替代品,是本地开发使用的另一个绝佳选择.它提供了类似的功能集,同时保持完全开源,让您避免使用 Docker 产品的许可问题.在本文 ...

  2. Java 表达式执行引擎 jexl

    介绍 JEXL的全称是Java表达式语言(Java Expression Language),简单的说,它可以配合我们的Java程序运算一些简单的表达式. 具体可以识别哪些表达式? 包含最基本的加减乘 ...

  3. 如何应对红帽不再维护 CentOS

    CentOS(Community Enterprise Operating System,社区企业操作系统)是一种开源的.免费的操作系统.由 Lance Davis 发起,通过社区驱动,目标是创建一个 ...

  4. Claude是否超过Chatgpt,成为生成式AI的一哥?

    Anthropic 周一推出了 Claude 3 ,据这家初创公司称,该系列中最有能力的 Claude 3 Opus 在各种基准测试中都优于 Openai 的竞争对手 GPT-4 和谷歌的 Gemin ...

  5. 如何支持同一台电脑上使用不同版本的Node.js版本

    在我们实际项目开发过程中,经常不同项目使用的node.js版本会也有所不同,为了方便维护不同版本的项目.可以使用nvm来解决. 1.下载nvm https://github.com/coreybutl ...

  6. [oeasy]python0035_ 整合shell编程_循环_延迟_清屏

    ​ 整合shell编程 回忆上次内容 用\r 可以让输出位置回到行首 原位刷新时间 如果想要的是大字符效果 需要使用 figlet 但同时还希望能刷新 ​ 编辑 这可能吗? 建立脚本 我们得熟悉一下s ...

  7. 《HelloGitHub》第 100 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...

  8. 【工具】SpringBoot项目如何查看某个maven依赖是否存在以及依赖链路

    当我在SpringBoot项目中想加个依赖,但是不确定现有依赖的依赖的依赖.....有没有添加过这个依赖,怎么办呢?如果添加过了但是不知道我需要的这个依赖属于哪个依赖的下面,怎么查呢? IDEA中提供 ...

  9. app备案证明需要提供md5值和公钥的解决方案

    现在app上架华为市场.小米市场.苹果市场等大型的应用商店,都需要提供国内的app备案证明.无论是安卓还是ios,都需要备案了. 但是问题是备案的时候需要填写app的bundle ID.公钥和MD5值 ...

  10. 【CI/CD】Jenkins 部署前后端项目Demo

    前置环境准备: 参考尚硅谷最新发布的Jenkins教程 同样准备了三台服务器: 192.168.124.34 Centos7 8G内存 用于安装GitLab 192.168.124.35 Centos ...