ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL、MongoDB、Redis选型实战:从业务场景到混合架构

MySQL、MongoDB、Redis选型实战:从业务场景到混合架构

说实话,每次有人问我“MongoDB、Redis、MySQL 到底怎么选”,我都想先反问一句:你的业务是哪种“痛”?这三个数据库被放在一起比较,本身就是个伪命题,它们更像是工具箱里的扳手、螺丝刀和电钻,各有各的适用面,不存在谁全面碾压谁。我在一线摸爬滚打这些年,见过把 Redis 当唯一存储用、最后丢数据丢到崩溃的项目,也见过 MongoDB 硬扛订单事务、对账对到想辞职的团队。这篇文章,我就从真实业务场景出发,把三类存储的边界、选型逻辑、以及混合架构里的配合方式,一次讲透。

1. 别让“哪个数据库更牛”带偏方向:先看清三类存储的天然分工

选型之前,先把底层原理理清楚。很多人之所以选错,不是因为不知道这三个名字,而是没搞明白它们各自为谁而生。

1.1 MySQL:强一致性与关系结构的守护者

MySQL 属于传统关系型数据库,核心特征是表结构固定、支持 ACID 事务、强一致性。它的数据组织方式决定了它天然适合“关系明确、字段稳定、必须精确”的业务。

举个例子你就懂了:订单表和用户表、商品表之间有关联,订单金额需要计算,库存扣减和订单状态更新必须同时成功或同时失败。这种场景,MySQL 的行锁、事务提交、回滚机制,是经历过几十年验证的。我不止一次跟人强调:只要业务里出现“转账”“下单”“余额变动”这类词,别犹豫,MySQL 或者其他关系型数据库就是你的地基。

B+Tree 索引结构让它在范围查询、排序、关联查询上表现稳定。更重要的是,MySQL 的运维体系太成熟了,主从复制、半同步、备份恢复、监控告警,你能想到的问题,社区都踩过无数次坑。团队招人也好招,新人上手也快。选型不是炫技,稳定和可控有时候比性能更值钱。

1.2 MongoDB:为“字段多变、写入量大、要横向扩展”而生

MongoDB 是文档型数据库,数据以 BSON 形式存储,通俗点说就是“JSON 的二进制版本”。它最大的特点是 Schema-less,也就是不需要预先定义表结构。今天你存 10 个字段,明天加第 11 个字段,后天某个文档少了两个字段,完全没问题。

这种灵活性在哪些场景价值巨大?我给你列几个:埋点日志、IoT 设备上报、用户行为轨迹、内容管理系统、商品中心。这些场景有一个共同点——数据长得不一样、格式经常变、单个数据价值不高但总量巨大。早些年我做过一个 IoT 平台,设备上报的数据字段随着固件版本更新不断变化,如果用 MySQL,每次加字段都要评估锁表、迁移、створ旧数据分片,想想都头疼。后来换成 MongoDB,开发效率提升立竿见影。

MongoDB 还内置了 sharding 分片能力,手动把数据分布到多个节点上,写入吞吐可以水平扩展。配合复制集,高可用也不用自己搭轮子。

1.3 Redis:速度至上,但它是“加速器”不是“保险箱”

Redis 是纯内存数据库,读写在微秒级别。它的价值可以用一句话概括:把热数据放到离用户最近的位置,让绝大多数请求不用到磁盘上翻找。

但这里有一个被无数团队误读的关键点:Redis 是缓存或辅助存储,不是唯一的数据源。哪怕你开启了 AOF 和 RDB 持久化,它在极端情况下依然可能丢几秒数据,而且内存成本比磁盘高好几个数量级。你要是一个月几十亿条核心流水全塞 Redis,先不说容量问题,成本就能让你老板血压升高。

Redis 真正擅长的是什么?热点数据缓存、分布式锁、计数器、排行榜、会话 Session、接口幂等、限流。它的数据结构丰富,String、Hash、List、Set、ZSet,每个都能对应一类经典场景。记住一句话:Redis 负责让系统“快”,MySQL/MongoDB 负责让数据“稳”。

我把三者核心差异整理成一张对比表,选型时直接对着看:

对比维度MySQLMongoDBRedis
数据模型关系表,强结构文档,动态结构键值对,多种数据结构
事务能力完整 ACID单文档事务,多文档受限事务性较弱,Lua 脚本模拟
典型延迟毫秒~几十毫秒毫秒级微秒级
扩展方式分库分表为主,运维成本高原生分片/复制集,方便主从/集群,但容量受内存限制
磁盘存储是是主要是内存,磁盘仅持久化
擅长场景核心交易、强一致、结构化数据海量写入、灵活字段、聚合分析缓存、热数据、计数器、锁

2. 真实业务场景拆解:我踩过的选型翻车与救火现场

说再多理论,都不如具体案例来得扎心。我把这些年遇到的几个典型场景拆开讲,每个场景都附上当时的取舍过程和踩坑复盘。

2.1 场景一:订单与结算系统——为什么我只能选 MySQL

之前参与过一个电商类项目,核心模块是下单、支付回调、订单状态流转、对账。技术评审会上,有人提出“用 MongoDB 性能高、扩展好”,还有人说“把订单放 Redis 里速度更快”——这两种意见我都当面否了。

原因很简单。MongoDB 虽然支持事务,但多文档事务在分片集群下性能和复杂度都是问题。订单系统的核心不是“写入快”,而是“绝对不出错”。订单金额算错一分钱、库存和订单不同步、支付状态错乱,哪一个都足以引发资损。MySQL 的 ACID 事务在这里是地狱底线,要么全部成功,要么全部回滚,没有中间态。

Redis 就更不用提了,它的持久化机制决定它不是做数据主权的地方。你想想,一个订单正要落库,主节点宕机,几秒内数据还没来得及刷到磁盘,重启之后 Redis 恢复的数据是缺失的。这个订单用户已经付了钱,系统却说没收到——这怎么跟用户交代?

最终方案就是 MySQL 承载订单主数据,Redis 只用来缓存商品页和用户购物车这类可丢失数据。这个场景没什么花哨技术,就是用关系型数据库把底盘稳住。

2.2 场景二:IoT 埋点与设备数据——MongoDB 的甜点区

同样是我参与的一个工业设备监测平台,设备每隔几秒上报一次运行状态。状态数据长什么样?电压、电流、温度、湿度、运行时长……听起来挺固定。但实际上,不同型号的设备上报字段差异很大,新固件上线后还会加新字段,而且一秒内可能有上万台设备同时上报。

这种场景用 MySQL 就是灾难。每来一种新字段就要 ALTER TABLE,几千台设备种类一多,表结构会膨胀到没法看。更别说高并发写入,MySQL 单机写入上限很快就撞到。

MongoDB 在这里几乎是为需求定制的:

  • 动态 schema 让我不用管字段变化,来什么存什么;
  • 写入性能极高,分片后可以水平扛住高并发;
  • 聚合管道做统计报表,比如按设备分组算平均温度、最大负载,写起来比 SQL 的复杂 JOIN 还直观;
  • 最关键的是 TTL 索引,可以自动删除 30 天前的原始数据,运维成本几乎为零。

如果你还在用 MySQL 硬扛海量日志类数据,我的建议是尽早评估迁移到 MongoDB。哪个场景适合它,真的一试便知。

2.3 场景三:热点商品与登录态——Redis 的正确打开方式

Redis 在这个组合里的定位很清晰:承担高并发的读操作和原子操作。拿一个我经历过的秒杀场景举例——某次促销活动,商品详情页和库存查询接口的 QPS 瞬间冲上几十万。如果全部请求都打到 MySQL,数据库直接就跪了。

那我是怎么设计的?商品基础信息和库存数先预热到 Redis,接口优先读缓存。库存扣减使用 Redis 的原子操作,伪代码如下:

# 扣减库存的 Lua 脚本,保证原子性 local stock = redis.call('GET', KEYS[1]) if tonumber(stock) <= 0 then return 0 end redis.call('DECRBY', KEYS[1], ARGV[1]) return 1

这一套下来,热点接口的响应时间稳定在 5 毫秒以内,MySQL 只接收异步落库的订单数据,压力小了一个数量级。

Redis 还经常用来存用户登录态,替代传统的 Session。同一套用户系统,原来查一次用户信息要走一次 MySQL,自从把 Session 放到 Redis,再用 Hash 存用户基本资料,查询路径从“查库”变成了“查内存”,响应速度完全是两个世界。

我要提醒的是:对热点数据做缓存,务必想清楚“缓存击穿”“缓存穿透”“缓存雪崩”三件事。击穿可以用互斥锁重建缓存,穿透可以用布隆过滤器拦截,雪崩可以用过期时间加随机抖动错峰。这些是 Redis 使用的基本功,基本功不牢,再好的工具也会被你玩出事。

2.4 三个场景复盘:数据源决定你会踩什么坑

我把这三个场景收进一张复盘表,方便你对照自己的业务:

业务场景最终选型核心选择理由踩过的坑 / 注意点
订单结算、账户交易MySQL事务强一致、关系明确分库分表后跨库事务复杂度剧增
IoT 上报、行为埋点MongoDB动态 schema、海量写入别用它跑强一致性核心交易
热点商品、登录态Redis(配合 MySQL)微秒读、原子操作必须开持久化、必须做缓存兜底策略

3. 混合架构才是常态:一个项目里三类存储怎么各司其职

很多人以为选型就是“三选一”,但真实的工业级项目里,这三者常常是共存的。我做一个系统时,最舒服的状态是:MySQL 做主权数据库,MongoDB 收纳高频、灵活、可容忍最终一致的数据,Redis 把所有热点访问拦截在最前端。

3.1 三个存储并存的分层设计

我在一个模拟项目里搭过一套三层数据链路,结构不复杂,但足够应付大多数业务:

最底层是 MySQL,存储最核心的实体数据,比如用户账户、订单、结算记录。这些数据要求 100% 不丢、强一致、可追溯。中间层是 MongoDB,存放行为日志、操作流水、设备上报、商品快照等。这些数据格式化程度低、量很大、对一致性要求稍微放宽,允许短时间内的延迟。最上层是 Redis,放商品热点、用户 Session、库存计数器、排行榜。它的目标只有一个——用内存换速度,让 90% 的读请求根本不需要碰 MySQL 和 MongoDB。

数据流大致是这样:用户在页面浏览商品,Redis 扛热点读;用户点击下单,MySQL 开启事务落核心单据;与此同时,系统的行为轨迹异步写入 MongoDB,供后续的个性化推荐和运营分析使用;订单创建成功后,Redis 里的库存数和购物车同步更新。

写到这里我想强调一点:这套链路不是一开始就设计出来的,而是被线上事故“教”出来的。最初我试图把所有数据一股脑都放 MySQL,结果日志表膨胀到主库磁盘告警;后来又把核心数据挪到 Redis,差点在宕机演练时把用户账单弄丢。每一次翻车,都逼着我重新审视“什么数据该住在什么存储里”。

3.2 跨存储的一致性怎么做:最终一致的取舍

混合架构里最让人头疼的,不是我上面说的读写链路,而是“同一个数据在多个存储中都存在”的一致性维护。

最常见的例子:订单在 MySQL 落库后,要同步一份汇总数据到 MongoDB 给报表系统用,还要更新 Redis 的缓存。这三者无法做到强一致,怎么办?

我的做法是“最终一致”。通过 MySQL 的 Binlog 监听,把变更事件投递到消息中间件,再由消费端写入 MongoDB 和刷新 Redis 缓存。这套机制的核心是:MySQL 是唯一事实来源,其他存储是它的投影。只要 MySQL 的数据不丢,其余存储可以晚几秒同步,但不能丢失最终租金。

这里必须做的配套工作是幂等处理。消息可能重发,消费段必须保证“同一条订单变更,处理两次和一次结果一样”。我常用的手段是在 MongoDB 里给业务主键建唯一索引,插入时用 updateOne + upsert 语义,重复消费时只是覆盖写,不会产生重复数据。

3.3 双写同步最容易翻车的时序问题

我再多说一点实战里最容易踩的坑:同步时序。

有些人图省事,在业务逻辑里写双写——先写 MySQL,再写 MongoDB,再写 Redis。问题是这里任何一个环节失败,数据就分裂了。MySQL 写入成功,MongoDB 写入失败,报表系统查不到这条数据,等到排查的时候已经过去了半天。

正确的做法是剥离开主链路与旁路同步:主链路只写 MySQL,写成功后发送一条消息到中间件;同步脚本监听消息,再更新 MongoDB 和 Redis。MySQL 永远第一,其他存储永远异步。

我经历过的另一个坑是:先更新 Redis,再更新 MySQL,结果 Redis 更新成功而 MySQL 更新失败,用户看到的数据和数据库就不一致了。正确的顺序永远是“先更数据库,再删缓存”,而不是先更缓存。删缓存而不是更新缓存,原因在于避免并发环境下旧数据被回写覆盖,这个细节值得默写三遍。

4. 选型决策清单:从数据模型、访问模式、扩展预期倒推落地方案

前面讲了这么多案例,你可能会问:那我现在接过一个新项目,到底该按什么顺序思考?我根据自己的经验,整理了一套偏实操的决策方法,核心思路只有一句话:选型不是选工具,而是选数据的管理方式。

4.1 五个关键判断维度:问完就出答案

第一,数据形态是否固定?如果字段列表清晰、关系明确、几乎不变,MySQL 优先;如果字段随业务演进频繁变化、文档之间结构不一致,MongoDB 值得考虑。

第二,一致性要求有多高?涉及钱、库存、订单、权限,必须强一致,那基本就和 MongoDB/Redis 说再见了(至少它们不能做唯一数据源)。反过来说,允许最终一致的分析、报表、日志类数据,完全可以用 MongoDB。

第三,读写流量模型是什么样?高并发热点读,必须引入 Redis 做缓存;海量写入且增长快,MongoDB 的扩展性远比 MySQL 分库分表省心。

第四,数据量级和增长速度。千万行级别以内,MySQL 完全没问题;亿级以上而且还在涨,就要考虑 MongoDB 分片或 MySQL 分库分表了。这里忠告一句:分库分表的运维复杂性会超出你想象,如果业务刚起步,别预支这份复杂度。

第五,团队能力和运维成本。一个团队全员只会 SQL,别硬上 MongoDB;搞不定 Redis 持久化和主从同步,就别把核心流程压在上面。选型最终是要人去维护的,工具再好,没人能 hold 住,就是定时炸弹。

4.2 一份可以直接“抄作业”的选型判断表

考虑到很多朋友没时间把上面五个维度全部推演一遍,我整理了一份“条件判断 + 推荐结论”的清单,每条都是我从实际项目里总结出来的,边界很清晰:

判断条件结论
我必须保证不丢数据、不出错,字段关系明确首选 MySQL
我的数据接口和字段经常变,未来也有大量新字段考虑 MongoDB
我要存海量日志/埋点,写入并发极高,字段结构不重要MongoDB,开启分片和 TTL
我的接口要求毫秒级响应,峰值流量巨大引入 Redis 做缓存层
我要做排行榜、计数器、分布式锁、限流Redis 的 ZSet、INCR、SETNX
我只是想“快”,不在乎丢了数据可不可恢复赶紧醒醒,Redis 不能当保险箱
我正在做一套复杂的多实体关系报表,重关联查询MySQL 比 MongoDB 的 $lookup 顺手

这张表不是严格判定器,它更像一个启发式索引。真正落地时,把条件组合起来看,答案基本就浮出水面。

4.3 “不折腾”原则:能简单就别复杂

最后想说一个被大多数人忽略的选型原则——不折腾。技术选型会议上,总有“技术乔布斯”喜欢把所有新潮组件往架构图里堆。我的经验是:能用 MySQL 解决的,绝对不因为“想试试 MongoDB”而替换;能用缓存解决的,绝对不搞一整套双写同步。数据库的复杂度是呈指数级上升的,每多加一个存储,你就要多应对一致性、运维、成本、团队学习曲线等一系列问题。

5. 从选型到迁移:切换存储引擎时最容易忽视的三个坑

假设你已经决定要动刀,把部分数据从 MySQL 迁到 MongoDB,或者给系统加上 Redis 层。这里有三类坑我几乎每次都会遇到,写在这里,希望帮你避开。

5.1 坑一:一次性全量导入,上线前一天才发现“影子差异”

很多人迁移数据时,想着一夜之间导出、导入、切换,第二天完事。我只能说理想很丰满。我的建议是采用双跑灰度迁移:

  • 旧库新库并行运行至少一两个业务周期,先对账;
  • 新库只写不改、全量同步旧库变更,影子模式校验数据一致性;
  • 线上流量逐步切分,先切 5% 的请求,观察,再切 20%,再切全量。

你需要一个对账脚本把所有数据按月抽检,比较 MySQL 和 MongoDB 里的结果。等校验通过再切换流量,就可以把风险控制在可控范围。

5.2 坑二:用 MongoDB 硬套 SQL 思维

还在用 MySQL 时,我们习惯用 JOIN 把多张表关联起来。切到 MongoDB 后,最忌讳的是一上来就用 $lookup。$lookup 确实能做关联,但性能消耗巨大,尤其在数据量大的集合上,一跑全表扫描,磁盘 IO 就爆了。

正确的做法是先“反向建模”:把高频一起读的数据直接嵌入到同一个文档里,或者做冗余字段。比如订单文档里直接带上“用户昵称”,而不是查询时再去关联用户表。冗余带来的问题是数据更新时要同步更新多个文档,但 MongoDB 在多写场景下这点成本远比每次查询都做关联要划算。别让习惯决定架构,要让访问路径决定数据模型。

5.3 坑三:Redis 持久化配置不当,重启后数据“消失”

有人把 Redis 当缓存用,根本不开持久化,觉得“反正缓存丢了就从 MySQL 拉”。理是这么个理,但如果你的 Redis 里存了分布式锁、计数器、或者购物车结构,丢失后带来的连锁反应可不小。

我的经验是至少开启 AOF,并设置 appendfsync everysec,这样即使极端宕机,最多也只丢一秒的数据。如果对恢复速度有要求,可以同时开 RDB 做快照,配合 AOF 做增量恢复命令重放。更重要的是,定期做重启演练,别等真出事才发现自己连 AOF 文件路径在哪都不知道。

这里还要提醒一点:Redis 的内存淘汰策略要提前测。默认的 noeviction 和 allkeys-lru 行为完全不同,测试环境里跑一遍,否则线上内存满了,接口突然开始报错,你才去看配置就晚了。


这些坑和配置细节,都是我一次次从线上事故里换来的。数据存储选型这件事,没有所谓的“黄金组合”,只有“当前业务阶段最合适的组合”。我见过单 MySQL 撑了几年的小项目,也见过三存储并存的大平台,谁也别瞧不起谁。你在设计架构时,只需要记住:核心数据给 MySQL,灵活数据给 MongoDB,热数据请交给 Redis,剩下的复杂度控制,靠的是你对业务的理解深度。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进