← 返回因果链

🔗 存储选型因果链

因为…所以… — 为什么不是一种数据库解决所有问题?

MySQLMongoDBElasticsearch Redis对象存储多存储协作

① 因为:需要结构化数据+强一致性 → 所以MySQL

❓ 问题:银行转账、订单记录,数据必须准确、不能丢
💡 所以:MySQL——关系型数据库,ACID事务保证

类比:Excel——行列分明,有公式计算,有数据校验

MySQL最适合的场景(因为…所以):
因为数据结构固定 → 所以用表结构约束
因为需要事务保证 → 所以用InnoDB ACID
因为需要复杂查询 → 所以用SQL JOIN
因为需要数据一致性 → 所以用外键约束
⚡ 但是:MySQL不擅长什么?

1. schema不灵活:加字段要ALTER TABLE,大表锁很久
2. 全文搜索差:LIKE '%关键词%' 全表扫描,无法分词
3. 热点数据扛不住:每次都从磁盘读,10ms+
4. 大文件不合适:图片视频存BLOB,数据库暴增

② 因为:schema灵活+大数据量 → 所以MongoDB

❓ 问题:数据结构经常变,加字段不想ALTER TABLE

用户画像:每个用户的属性不一样(张三有"爱好",李四有"职业")→ MySQL要预留100列?

💡 所以:MongoDB——文档型数据库,schema自由

类比:微信朋友圈+Excel灵活表格——每条动态内容不同,但都能存

// MongoDB:每个文档可以不同结构 // 用户1 { "name": "张三", "hobbies": ["篮球", "编程"], "level": 5 } // 用户2——完全不同的字段! { "name": "李四", "job": "工程师", "certificates": ["AWS", "PMP"] } // 因为MongoDB不需要预定义schema → 所以随时加字段 // 因为嵌套文档 → 所以不需要JOIN // 因为水平扩展容易 → 所以大数据量友好
⚡ 但是:MongoDB不擅长全文搜索

MongoDB有text index,但分词能力弱,不支持中文分词,搜索相关性差 → 引出Elasticsearch

③ 因为:需要全文搜索 → 所以Elasticsearch

❓ 问题:淘宝搜索"手机",MySQL用LIKE?1000万商品要搜10秒
💡 所以:Elasticsearch——倒排索引+分词

类比:淘宝搜索+图书馆目录卡——先查目录卡找到页码,再翻到那页

"华为手机" 分词器 ["华为","手机"]
倒排索引
"华为" → [商品1, 商品5, 商品8]
"手机" → [商品1, 商品3, 商品5, 商品9]
交集 → [商品1, 商品5] ← 搜索结果!
⚡ 但是:ES不擅长事务和实时性

ES近实时(1秒延迟)+ 没有事务 → 所以ES不能替代MySQL,而是和MySQL配合:MySQL作为主存储,ES作为搜索索引

ES 搜索代码示例

// Elasticsearch DSL:搜索"华为手机",按相关性排序 { "query": { "bool": { "must": [ { "match": { "name": "华为手机" } } ], "filter": [ { "range": { "price": { "gte": 1000, "lte": 9999 } } }, { "term": { "status": "on_sale" } } ] } }, "sort": [ { "_score": "desc" } ], "from": 0, "size": 20 } // 因为倒排索引 → 所以10亿数据搜索只需几十毫秒 // 因为filter不参与评分 → 所以比must更快,适合精确过滤

④ 因为:热点数据需要极速读取 → 所以Redis

💡 Redis——因为磁盘太慢,所以用内存

类比:口袋里的钱(Redis,0.1ms)vs 当天的包(本地缓存,ns)vs 家里的保险箱(MySQL,10ms)vs 银行金库(归档存储,秒级)

// Redis不同数据结构解决不同问题(因为…所以) // 因为商品详情页高频读取 → 所以用String缓存 SET product:1001 '{"name":"iPhone","price":6999}' EX 3600 // 因为排行榜需要排序 → 所以用ZSet(有序集合) ZADD daily_rank 1500 "user:A" 1200 "user:B" 980 "user:C" ZREVRANGE daily_rank 0 9 WITHSCORES // Top10 // 因为秒杀库存需要原子扣减 → 所以用Lua脚本保证原子性 EVAL "if redis.call('get',KEYS[1])>'0' then return redis.call('decr',KEYS[1]) else return -1 end" 1 stock:1001

⑤ 因为:大文件不适合存DB → 所以对象存储(OSS)

💡 对象存储——因为图片/视频/文档不应该存在数据库
// ❌ 不推荐:图片存MySQL BLOB // 1. 数据库暴增(100万张图 × 2MB = 2TB) // 2. 备份恢复极慢 // 3. 无法CDN加速 // ✅ 推荐:图片存OSS,数据库只存URL @Service public class FileService { @Autowired private OSSClient ossClient; public String uploadImage(MultipartFile file) { String key = "images/" + UUID.randomUUID() + ".jpg"; ossClient.putObject("my-bucket", key, file.getInputStream()); return "https://my-bucket.oss-cn-hangzhou.aliyuncs.com/" + key; // 数据库存这个URL,不存图片! } }

⑥ 因为:每种存储各有长短 → 所以需要多存储协作

MySQL
主存储+事务
写入→同步 Elasticsearch
搜索索引
MySQL
主存储
热点→缓存 Redis
缓存层
MySQL
主存储
日志→写入 MongoDB
日志/画像
前端 图片/视频 OSS
文件存储
一句话因果:因为没有一种数据库能解决所有问题 → 所以根据问题选择合适的工具 → 所以大型系统都是多存储协作

🎯 面试点映射

面试题因为…所以…
MySQL vs MongoDB因为需要事务/JOIN → MySQL;因为schema灵活 → MongoDB
为什么用ES因为MySQL LIKE搜索太慢 → ES倒排索引+分词
MySQL+ES数据同步因为MySQL是主存储ES是索引 → 所以需要Canal/双写同步
Redis为什么快因为内存比磁盘快100倍+单线程无锁竞争
大文件怎么存因为BLOB让数据库暴增 → 所以存OSS,数据库只存URL
为什么不是一种DB搞定因为每种DB解决一类核心问题 → 没有银弹

❓ 常见疑问

MySQL和ES数据不一致怎么办?
因为MySQL是主存储ES是索引 → 所以必须保证最终一致性。方案:①Canal监听binlog同步(推荐,解耦)②双写(代码侵入强,不推荐)③消息队列异步同步(折中)。不管哪种,都要加对账机制定时校验。
Redis缓存和MySQL数据不一致怎么办?
因为先更新DB再删缓存→极端情况会短暂不一致。方案:①延迟双删(更新DB→删缓存→延迟500ms→再删)②监听binlog删除(Canal)③设置合理TTL兜底。核心原则:缓存是提升性能的手段,不是数据源。
MongoDB能替代MySQL吗?
因为MongoDB不支持多文档事务(4.0前)→ 所以不能替代需要强事务的场景。4.0+支持多文档事务但性能差 → 结论:需要事务用MySQL,需要灵活schema用MongoDB,两者互补不替代。
全栈资料库 · 因果链板块 · 存储选型因果链