数据库扛不住了?读写分离与分库分表实战指南

去年一个做在线教育的客户找到我,愁眉苦脸:“我们的数据库快扛不住了。每天晚高峰,CPU跑到90%,响应时间从50ms飙到3秒。用户投诉页面打不开,老板天天追着问怎么办。”
我问他:“现在怎么处理的?”
“加CPU、加内存,都试过了,撑几个月又不行。MySQL已经配到顶配了,再往上就是天价。”
这是很多业务增长期公司的共同困境:数据库成了瓶颈,垂直扩容(加配置)已经到天花板,水平扩展(加机器)又不知道从哪下手。
今天聊聊数据库扩展的两种主流方案:读写分离和分库分表。不是那种“理论很重要”的废话,而是帮你理清楚:什么时候该上读写分离,什么时候得上分库分表,以及怎么平稳地过去,不踩坑。
01 先问自己:瓶颈在读还是在写?
扩展数据库的第一步,不是选技术,是诊断。
查看数据库的监控指标:
慢查询日志里,读查询(SELECT)多还是写查询(INSERT/UPDATE/DELETE)多?
CPU高的时候,是在执行读还是写?
磁盘IOPS,读和写的占比?
80%的场景,瓶颈在读,不在写。 大部分业务是读多写少:看文章、刷商品、查订单。写操作相对少。
那家教育公司就是典型的读多写少。用户晚上集中刷课,读请求暴增,主库CPU被打满。但写请求不多,主库大部分资源都在处理读。
反常识观点:数据库扛不住了,第一反应不应该是分库分表,而是读写分离。 读写分离能解决80%的瓶颈,且简单得多。
02 读写分离:一招解决读多写少
读写分离的核心思想:主库负责写,从库负责读。
主库:处理INSERT、UPDATE、DELETE,保持数据一致性。
从库:通过主从复制同步数据,处理SELECT查询。
云上怎么做?
RDS、Aurora都支持一键添加只读实例。创建几个从库,把读流量引过去,主库压力立刻降下来。
流量怎么分配?
代码层:配置多个数据源,根据注解或规则判断走主库还是从库。
中间件层:ShardingSphere、MyCat等,对应用透明。
云原生:RDS的读写分离地址,自动分发读请求。
要避免的坑:主从延迟。
从库同步主库有延迟,通常几百毫秒,高负载时可能几秒。刚写完数据马上读,可能读到旧数据。
解决方案:
核心场景强制走主库:比如下单后立即查订单详情。
业务上容忍短暂延迟:比如用户看课程列表,晚几秒看到新课程没关系。
监控主从延迟,超过阈值告警。
那家教育公司加了两个只读实例,读流量切过去,主库CPU从90%降到30%。撑了一年多,业务又翻倍了,才考虑下一步。
03 读写分离扛不住了,怎么办?
当写操作也成为瓶颈,或者数据量太大(单表几亿行),读写分离也不够用了。这时候需要分库分表。
分库分表分两种:
垂直分片:按业务模块拆,订单库、用户库、商品库分开。简单,但跨库join麻烦。
水平分片:同一个表,按某个字段(如用户ID)拆到多个库/表里。复杂,但能线性扩展。
什么时候该上水平分片?
单表数据超过500万行,查询开始变慢
单表超过1000万行,索引效果明显下降
单库的写入TPS达到上限,无法通过加从库解决
核心难题:分片键怎么选?
分片键决定了数据怎么分布,也决定了查询怎么路由。
按用户ID分片:用户相关的订单、购物车都在一起。适合用户维度的查询。
按订单ID分片:适合订单维度的查询,但用户维度的查询需要跨分片。
按时间分片:适合日志、流水类数据,方便归档。
黄金法则:80%的查询要能带上分片键。 如果大部分查询都需要跨分片,分片就失去了意义。
04 分库分表的代价:你准备好了吗?
读写分离简单,加几个只读实例就行。分库分表是手术,动完就回不去了。
代价一:跨分片查询
原来一条SQL搞定,现在要查多个分片,结果聚合。分页、排序、count(*)都变得复杂。
代价二:分布式事务
原来数据库ACID事务保证一致性,现在跨库了,得用分布式事务。XA性能差,TCC代码复杂,最终一致性业务上要能接受。
代价三:数据重分布
数据量大了,想再加分片,需要重新分布数据。这个过程可能要停服,或者搞双写迁移,非常痛苦。
代价四:全局唯一ID
自增ID不能用了,得用雪花算法、Leaf等生成全局ID。
反常识观点:分库分表不是优化,是妥协。 是在单库实在扛不住的情况下,用复杂度换容量。能不分,尽量不分。
05 一条平滑的演进路径
不要一上来就分库分表。正确的路径是:
第一阶段:单库 + 读写分离
先加只读实例,把读流量分走。大部分公司止步于此,够用好几年。
第二阶段:垂直分片
按业务模块拆库。订单、用户、商品分开,各自独立扩展。这一步相对简单,不影响核心业务逻辑。
第三阶段:水平分片
单表实在扛不住了,再按分片键拆。优先拆数据量最大、写入压力最高的表。
第四阶段:分布式数据库
如果水平分片也扛不住了,考虑云原生分布式数据库(Aurora、TiDB、DRDS)。它们把分片、分布式事务、弹性扩展封装好了,省去自己造轮子的痛苦。
那家教育公司,用了两年读写分离,然后做了垂直分片(把课程库和用户库分开),到现在还没到水平分片的阶段。技术负责人说:“当年差点直接上分库分表,幸亏没冲动。”
06 一个真实案例:从单库到分片的三步走
一个电商客户,订单表三年涨到2亿行。查询越来越慢,写入也扛不住了。我们帮他们走了一条渐进的路:
第一步:读写分离。 加了两个只读实例,读查询走从库。撑了半年。
第二步:垂直分片。 把订单表和订单商品详情表分开(后者数据量更大)。又撑了半年。
第三步:水平分片。 订单表按用户ID哈希分成8个库,订单商品表跟着走。分片键是用户ID,用户查自己的订单只查一个分片。
迁移过程:双写新旧库 + 历史数据同步 + 切流量。花了两周,业务几乎无感知。
现在订单表已经涨到5亿行,8个分片每个约6000万行,查询性能良好。他们计划明年再扩到16个分片。
写在最后
数据库扛不住了,别慌。先诊断瓶颈在读还是在写,大概率在读。上读写分离,加几个只读实例,主库压力立马降下来。等真的扛不住了,再考虑分库分表。
分库分表是最后的手段,不是第一选择。能不分就不分,能晚分就晚分。复杂度上去了,运维成本翻倍,调试难度翻倍,业务代码也要改。
那家电商的运维负责人后来说:“数据库扩展就像修路,车多了先拓宽车道(加只读实例),车道不够了再架高架(分库分表)。一上来就架高架,路没通,钱先花完了。”
你的数据库,现在在哪个阶段?