【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB
前言
在笔者 Java 后端开发的项目经历中,MySQL 和 MongoDB 都有使用过作为后端的数据库来对业务数据进行持久化,两者没有孰优孰劣之分,都可以在合适的场景下发挥出它们的优势。
今天要分享的是一个项目重构过程中如何将数据库选型由原来的 MongoDB 改为 MySQL 的思考,涉及到业务当前的痛点、选型分析、解决的核心思路,最后会给出简单的 demo。
本篇文章侧重在于两者在表设计思维上的转换,而业务数据迁移同步的方案,下一篇文章将给出。
一、痛点所在
该项目是一个【PC端管理后台】+【移动端h5页面】为主业务框架的系统,原来的预期是:在后台配置好活动所需的参数,h5 既可以放在 app 客户端打开,也可以作为url 链接的形式直接在浏览器打开。项目一期的时候,业务方认为这样的运营活动会带来不少的流量和用户。但是到后来业务重心有所调整,引流的方式发生变化,最终导致了项目的一个重构。
主要的原因有以下几点:
总体的数据量没有预想的那么大
活动参与人数前期预估为30w+,经历过2个线上活动后的实际总参与人数为5w+,客户端注册用户数为3w+,占全部参与人数的65%左右,远不及预期规模;
核心接口的并发也没有预想的高
h5 端的大约 5-8 个的核心接口在实际线上活动进行的最高 QPS 只达到 200-300 左右,CPU 与 内存占用率也未达到设置的告警线(60%);
MySQL 在硬件资源成本上性价比更高
以阿里云的 RDS for MySQL 与 云数据库 MongoDB 做对比,都是集群部署 + 8核16GB + 100GB 存储 + 1年时长的规格下,前者会比后者便宜7w+RMB;
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
- Web 应用程序:如常见的 xx 管理后台、xx 管理系统,电商 web 网站,包括一些移动端 h5 的页面等;
- 企业级应用:如常见的客户关系管理系统(CRM)、人力资源管理系统(HRM)和供应链管理系统(SCM)等,MySQL 提供了强大的事务支持;
- 嵌入式开发:需要轻量级数据库的软件、硬件和设备,MySQL 可以作为一个嵌入式数据库引擎集成到各种应用程序中,提高应用程序的可移植性;
- 云计算和大数据:MySQL 在云数据库服务中被广泛使用,支持云原生应用程序和分布式数据处理框架,如 Hadoop 和 Spark 等。
- MongoDB
- 处理实时数据:非常适合处理移动互联网应用常见的大部分场景,如用户活动、社交互动、在线购物等;
- 内容管理系统(CMS):用于处理文章、稿件、评论、图片、视频等富媒体内容的存储和增删改查,支持全文搜索和实时更新;
- 数据聚合仓库:存储原始或半处理的业务数据,利用聚合框架进行实时数据聚合、统计分析和数据可视化;
- 游戏数据管理:存储玩家账户信息、游戏进度、成就、虚拟物品、社交关系等,快速计算和更新游戏排行榜数据,支持实时查询等。
三、核心思路
我们知道,在 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 : N 的范式关系,用主键-外键的方式做关联
最后,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!
【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB的更多相关文章
- 【转】vue项目重构技术要点和总结
vue数据更新, 视图未更新 这个问题我们经常会遇到,一般是vue数据赋值的时候,vue数据变化了,但是视图没有更新.这个不算是项目重构的技术要点,也和大家分享一下vue2.0通常的解决方案吧! 解决 ...
- freeswitch 使用mysql替换默认的sqlite
转自 80000hz.com freeswitch 使用mysql替换默认的sqlite No Reply , Posted in 默认分类 on January 14, 2014 目标使用mysql ...
- iOS基于MVC的项目重构总结
关于MVC的争论 关于MVC的争论已经有很多,对此我的观点是:对于iOS开发中的绝大部分场景来说,MVC本身是没有问题的,你认为的MVC的问题,一定是你自己理解的问题(资深架构师请自动忽略本文). 行 ...
- 转:iOS基于MVC的项目重构总结
转:http://www.cocoachina.com/ios/20160519/16346.html 关于MVC的争论 关于MVC的争论已经有很多,对此我的观点是:对于iOS开发中的绝大部分场景来说 ...
- Mysql笔记之 -- replace()实现mysql 替换字符串
mysql 替换函数replace()实现mysql 替换字符串 mysql 替换字符串的实现方法: mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数 ...
- mysql替换字段里数据内容部分字符串(亦可用于增加字段中的内容)
mysql替换表的字段里面内容,如例子: mysql> select host,user from user where user='testuser'; +----------------- ...
- mysql 替换函数replace()实现mysql 替换字符串
mysql 替换字符串的实现方法:mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数去替换,用起来非常的方便,mysql 替换函数replace()Upd ...
- (转)Android项目重构之路:实现篇
前两篇文章Android项目重构之路:架构篇和Android项目重构之路:界面篇已经讲了我的项目开始搭建时的架构设计和界面设计,这篇就讲讲具体怎么实现的,以实现最小化可用产品(MVP)的目标,用最简单 ...
- (转)Android项目重构之路:界面篇
在前一篇文章<Android项目重构之路:架构篇>中已经简单说明了项目的架构,将项目分为了四个层级:模型层.接口层.核心层.界面层.其中,最上层的界面,是变化最频繁的一个层面,也是最复杂最 ...
- Hive(2)-Hive的安装,使用Mysql替换derby,以及一丢丢基本的HQL
一. Hive下载 1. Hive官网地址 http://hive.apache.org/ 2. 文档查看地址 https://cwiki.apache.org/confluence/display/ ...
随机推荐
- 如何在 Windows 使用 Podman Desktop 取代 Docker Desktop
Podman Desktop 是 Docker Desktop 的免费替代品,是本地开发使用的另一个绝佳选择.它提供了类似的功能集,同时保持完全开源,让您避免使用 Docker 产品的许可问题.在本文 ...
- Java 表达式执行引擎 jexl
介绍 JEXL的全称是Java表达式语言(Java Expression Language),简单的说,它可以配合我们的Java程序运算一些简单的表达式. 具体可以识别哪些表达式? 包含最基本的加减乘 ...
- 如何应对红帽不再维护 CentOS
CentOS(Community Enterprise Operating System,社区企业操作系统)是一种开源的.免费的操作系统.由 Lance Davis 发起,通过社区驱动,目标是创建一个 ...
- Claude是否超过Chatgpt,成为生成式AI的一哥?
Anthropic 周一推出了 Claude 3 ,据这家初创公司称,该系列中最有能力的 Claude 3 Opus 在各种基准测试中都优于 Openai 的竞争对手 GPT-4 和谷歌的 Gemin ...
- 如何支持同一台电脑上使用不同版本的Node.js版本
在我们实际项目开发过程中,经常不同项目使用的node.js版本会也有所不同,为了方便维护不同版本的项目.可以使用nvm来解决. 1.下载nvm https://github.com/coreybutl ...
- [oeasy]python0035_ 整合shell编程_循环_延迟_清屏
整合shell编程 回忆上次内容 用\r 可以让输出位置回到行首 原位刷新时间 如果想要的是大字符效果 需要使用 figlet 但同时还希望能刷新 编辑 这可能吗? 建立脚本 我们得熟悉一下s ...
- 《HelloGitHub》第 100 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...
- 【工具】SpringBoot项目如何查看某个maven依赖是否存在以及依赖链路
当我在SpringBoot项目中想加个依赖,但是不确定现有依赖的依赖的依赖.....有没有添加过这个依赖,怎么办呢?如果添加过了但是不知道我需要的这个依赖属于哪个依赖的下面,怎么查呢? IDEA中提供 ...
- app备案证明需要提供md5值和公钥的解决方案
现在app上架华为市场.小米市场.苹果市场等大型的应用商店,都需要提供国内的app备案证明.无论是安卓还是ios,都需要备案了. 但是问题是备案的时候需要填写app的bundle ID.公钥和MD5值 ...
- 【CI/CD】Jenkins 部署前后端项目Demo
前置环境准备: 参考尚硅谷最新发布的Jenkins教程 同样准备了三台服务器: 192.168.124.34 Centos7 8G内存 用于安装GitLab 192.168.124.35 Centos ...