数据库读写分离与分库分表实战稿:从单库瓶颈到高并发架构
为什么大厂面试爱问“为什么要做读写分离/分库分表?”
面试官问这个问题,不是想听“读写分离能提升性能”“分库分表能解决单表数据量大的问题”这些概念,而是想判断:你在高并发场景下,真的遇到过数据库性能瓶颈吗?你能说清楚读写分离和分库分表解决了什么具体问题,带来了什么实际价值,以及如何保证数据一致性吗?
接下来,我会通过四个真实项目场景,展示我们是如何从“单库扛不住高并发”到“读写分离提升吞吐量”,从“单表数据量爆炸”到“分库分表平滑扩容”的完整过程。每个场景都会包含:当时遇到了什么具体问题、我们是怎么解决的、最终取得了什么效果。
场景 1:读多写少场景 —— 从“主库 CPU 95%”到“读写分离后 QPS 提升 3 倍”
业务背景:内容社区平台,用户每天产生 10 万条新内容,但阅读量达到 500 万次。典型的读多写少场景,读请求占比 98%,写请求占比 2%。
遇到的问题:
- 所有读写请求都打在主库上,高峰期主库 CPU 飙到 95%,连接数达到 800(最大 1000),经常出现连接超时。
- 用户反馈“刷不出内容”“页面加载慢”,投诉量每天 200+ 条。
- 数据库慢查询日志显示,大量 SELECT 查询占用资源,导致写操作(发布内容、点赞)也变慢,用户体验极差。
解决方案:
- 引入 MySQL 主从复制 + 读写分离,1 主 3 从架构。
- 写操作(INSERT、UPDATE、DELETE)走主库,读操作(SELECT)走从库。
- 使用 ShardingSphere-JDBC 做读写分离中间件,应用层无感知,自动路由。
- 从库延迟监控:设置告警,主从延迟 > 3 秒时告警,避免读到旧数据。
实际效果:
- 主库 CPU 从 95% 降到 45%,连接数从 800 降到 200,写操作响应时间从 200ms 降到 80ms。
- 读请求分散到 3 个从库,整体读 QPS 从 3000 提升到 9000(3 倍),用户投诉下降 90%。
- 系统吞吐量提升 2.5 倍,高峰期不再出现连接超时,用户体验显著改善。
场景 2:单表数据量爆炸 —— 从“查询 5 秒”到“分表后 50 毫秒”
业务背景:电商订单系统,订单表 order 存储了 3 年历史数据,单表数据量达到 8000 万条,表大小 120GB。用户查询“我的订单”时,即使加了索引,查询也要 3-5 秒。
遇到的问题:
- 单表数据量过大,即使有索引,B+ 树层级深,查询性能急剧下降。
- 高峰期用户查询“我的订单”,SQL 执行时间 5 秒,用户投诉“订单页面卡死”。
- 数据库维护困难:备份耗时 6 小时,索引重建需要 4 小时,影响业务。
- 新功能开发受限:不敢在订单表加新字段,担心影响性能。
解决方案:
- 采用 水平分表,按用户 ID 取模分 16 张表:order_0 到 order_15。
- 分表规则:table_name = order_{user_id % 16},保证同一用户的数据在同一张表。
- 使用 ShardingSphere-JDBC 做分表路由,应用层代码无需改动,自动路由到对应分表。
- 历史数据归档:3 年前的数据迁移到归档库,只保留最近 2 年的热数据。
实际效果:
- 单表数据量从 8000 万降到 500 万,查询时间从 5 秒降到 50 毫秒,性能提升 100 倍。
- 用户查询“我的订单”响应时间 < 100ms,用户体验显著提升,投诉量下降 95%。
- 数据库维护效率提升:备份时间从 6 小时降到 30 分钟,索引重建从 4 小时降到 15 分钟。
- 系统可扩展性增强:后续可以继续分表,支持更大数据量。
场景 3:跨库查询难题 —— 从“JOIN 查询 10 秒”到“应用层聚合 200 毫秒”
业务背景:社交平台,用户表和用户关系表都做了分库分表。用户表按用户 ID 分 8 个库,用户关系表按关注者 ID 分 8 个库。需要查询“我关注的用户列表及其详细信息”。
遇到的问题:
- 分库分表后,无法跨库 JOIN,原来的 SELECT u.* FROM user u JOIN user_relation r ON u.id = r.followed_id WHERE r.follower_id = ? 无法执行。
- 如果先查用户关系表,再循环查询用户表,需要执行 9 次 SQL(1 次查关系 + 8 次查用户),总耗时 10 秒。
- 用户反馈“关注列表加载慢”,页面超时,体验极差。
解决方案:
- 应用层聚合:先查用户关系表获取关注用户 ID 列表,再批量查询用户表。
- 批量查询优化:使用 IN 查询,一次查询多个用户,减少 SQL 执行次数。
- 缓存优化:用户关系数据缓存到 Redis,减少数据库查询。
- 异步加载:关注列表分页加载,首次只加载前 20 个,后续异步加载。
实际效果:
- 查询时间从 10 秒降到 200 毫秒,性能提升 50 倍。
- 用户关系数据缓存命中率 90%,数据库查询压力下降 80%。
- 用户体验显著提升,页面加载时间 < 500ms,投诉量下降 90%。
场景 4:主从延迟导致的数据不一致 —— 从“用户投诉余额不对”到“最终一致性保障”
业务背景:支付系统,用户充值后立即查询余额,但有时显示余额还是旧的,用户投诉“充了钱但余额没更新”。
遇到的问题:
- 读写分离后,写操作走主库,读操作走从库。
- 主从复制有延迟(通常 100-500ms),用户充值后立即查询余额,可能读到从库的旧数据。
- 用户投诉“充了 100 元,但余额还是旧的”,每天 10-20 个投诉,影响用户体验。
解决方案:
- 强制读主库:关键业务(余额查询、订单查询)强制读主库,保证强一致性。
- 最终一致性:非关键业务(内容列表、推荐列表)允许读从库,接受短暂延迟。
- 延迟监控:实时监控主从延迟,延迟 > 1 秒时告警,自动切换读主库。
- 业务优化:充值成功后,前端延迟 500ms 再查询余额,或直接使用返回结果,不立即查询。
实际效果:
- 关键业务数据一致性 100%,用户投诉“余额不对”的问题彻底解决。
- 非关键业务仍享受读写分离带来的性能提升,整体 QPS 提升 2.5 倍。
- 主从延迟监控覆盖率 100%,问题发现从“用户投诉”变为“主动预警”。
四类场景总结
| 场景 | 核心问题 | 解决方案价值 | 改善数据 |
|---|---|---|---|
| 读多写少 | 主库 CPU 高、连接数满 | 读写分离分散读压力 | QPS 提升 3 倍 |
| 单表数据量大 | 查询慢、维护困难 | 水平分表降低单表数据量 | 查询时间 5s → 50ms |
| 跨库查询 | 无法 JOIN、查询慢 | 应用层聚合 + 缓存优化 | 查询时间 10s → 200ms |
| 主从延迟 | 数据不一致、用户投诉 | 强制读主 + 最终一致性策略 | 一致性 100% |
常见追问点
1. 读写分离和分库分表有什么区别?什么时候用哪个?
实战答案:
- 读写分离:解决读多写少场景的性能问题,通过主从复制分散读压力。适合读请求占比 > 80% 的场景。
- 分库分表:解决单表数据量大、单库性能瓶颈的问题,通过水平拆分降低单表数据量。适合单表数据量 > 5000 万、单库 QPS > 5000 的场景。
- 组合使用:高并发场景下,通常先做读写分离,如果还不够,再做分库分表。我们项目就是先做读写分离(1 主 3 从),再做分库分表(8 库 16 表),最终支撑 10 万 QPS。
2. 分库分表后,如何保证分布式事务的一致性?
实战答案:
- 本地事务:同一分片内的操作,使用数据库本地事务保证 ACID。
- 分布式事务:跨分片的操作,使用 Seata(AT 模式) 或 TCC 模式 保证一致性。
- 最终一致性:非关键业务(如日志、统计)可以接受最终一致性,使用消息队列异步处理。
- 业务补偿:关键业务(如订单、支付)如果分布式事务失败,通过补偿机制回滚,保证数据一致性。
- 我们项目订单系统跨库操作使用 Seata,成功率 99.9%,失败场景通过补偿机制保证数据一致性。
3. 分库分表后,如何做数据迁移和扩容?
实战答案:
- 数据迁移:使用 Canal 监听 binlog,实时同步数据到新分片,迁移过程中双写,迁移完成后切换读新分片。
- 平滑扩容:从 8 库扩展到 16 库,需要重新分片。我们采用“双写 + 逐步切读”策略:新数据双写新旧分片,旧数据逐步迁移,迁移完成后切读新分片,最后停写旧分片。
- 数据校验:迁移过程中,定期校验新旧分片数据一致性,确保迁移正确。
- 我们项目从 8 库扩展到 16 库,迁移耗时 3 天,期间业务零中断,数据一致性 100%。
4. 分库分表后,如何做全局唯一 ID?
实战答案:
- 雪花算法(Snowflake):生成 64 位 ID,包含时间戳、机器 ID、序列号,保证全局唯一。我们项目使用雪花算法,QPS 支持 100 万+。
- 数据库自增 ID + 步长:每个分片设置不同的起始值和步长,如分片 1 起始 1 步长 8,分片 2 起始 2 步长 8,保证全局唯一。适合分片数固定的场景。
- Redis 自增:使用 Redis 的 INCR 命令生成全局唯一 ID,性能高但需要保证 Redis 高可用。
- UUID:简单但 ID 太长(36 字符),不适合做数据库主键,影响索引性能。
- 我们项目使用雪花算法,ID 长度 19 位,性能好,全局唯一,推荐使用。
面试时怎么回答这个问题?
如果面试官问“为什么要做读写分离/分库分表”,你可以这样组织答案:
第一步:先讲背景和问题 “我在做 XX 项目(比如内容社区/电商订单系统)时,遇到了数据库性能瓶颈。具体表现是:XX(比如主库 CPU 飙到 95%/单表数据量 8000 万,查询要 5 秒/高峰期连接数满,经常超时)。用户投诉 XX(比如页面加载慢/订单查询卡死),业务受到很大影响。”
第二步:说明解决方案 “为了解决这个问题,我们做了 XX(比如读写分离 1 主 3 从/按用户 ID 分 16 张表)。具体实现是:XX(比如使用 ShardingSphere-JDBC 做路由/主从延迟监控/应用层聚合跨库查询)。同时考虑了 XX(比如数据一致性/分布式事务/全局唯一 ID)这些技术难点。”
第三步:展示实际效果 “上线后效果很明显:XX(比如主库 CPU 从 95% 降到 45%/查询时间从 5 秒降到 50 毫秒/QPS 提升 3 倍)。更重要的是,系统具备了 XX(比如水平扩展能力/支撑更大数据量/高并发场景下的稳定性),为后续业务增长打下了基础。”
核心要点:面试官要的不是“你知道读写分离能提升性能”,而是“你能在高并发场景下,用读写分离和分库分表解决实际性能问题,还懂数据一致性、分布式事务这些技术难点”。
可视化插图
提示:本文讲的是单机数据库的读写分离和分库分表。如果你的项目是云原生架构(Kubernetes),就需要考虑数据库容器化、服务发现、自动扩缩容等问题,这是面试另一高频考点,后面单独拆解。
你项目里用读写分离/分库分表解决过什么性能瓶颈?评论区聊聊,下次面试直接用!