在互联网应用快速发展的今天,数据量的爆炸式增长和并发访问的急剧上升给传统数据库架构带来了巨大挑战。本文将带您了解数据库架构的演进之路,深入探讨读写分离和分库分表技术如何解决海量数据场景下的性能瓶颈。
在微服务时代出现之前,大部分项目都是一个单体应用架构,像一个 WAR 包包含所有功能的应用程序,从客户端操作传输到服务端,在写入单体数据库中

这个阶段是公司发展的早期阶段,系统架构如上图所示。在项目运行初期,各类功能的表都在同一个数据库中,每个表都包含了大量的字段。在用户量比较少,访问量也比较少的时候,单库单表不存在问题。
这个阶段一般是属于业务规模不是很大的公司使用,因为机器都是单台的话,随着我们业务规模的增长,慢慢的我们的网站就会出现一些瓶颈和隐患问题
随着访问量的继续不断增加,单台应用服务器从性能上和可用性上已经无法满足我们的需求。所以我们通过增加应用服务器的方式来将服务器集群化。

采用应用服务器高可用集群的架构之后,应用层性能也随之增加,但数据库的负载也在逐渐增大,随着访问量的提高,所有的压力都将集中在数据库这一层。

随着业务量的增长,数据库的负载也在逐渐增大,那如何去提高数据库层面的性能呢?既然应用层可以做集群化,那数据库是不是也可以多台呢?答案是当然可以,我们的数据库主要是通过 主从复制 + 读写分离 进行实现的,在这种架构中:
主库:负责所有写操作 + 部分读操作
从库:只能处理读操作,数据通过复制从主库同步

读写分离的核心思想是将数据库的读操作和写操作分离到不同的服务器上。通常,写操作集中在主库(Master),读操作分散到一个或多个从库(Slave)。这样做的原因主要有以下几点:
提升读性能:在大多数应用中,读操作的比例远高于写操作。通过增加多个从库来处理读请求,可以有效地分摊读负载,提升系统的整体读性能。
避免读写冲突:当读写都在同一个数据库实例上时,写操作可能会对读操作造成阻塞(例如,写锁会阻塞读锁)。将读写分离后,读操作可以在从库上执行,从而避免受到主库写操作的影响。
提高可用性:当主库出现故障时,可以快速切换到从库,保证系统的可用性。同时,从库也可以作为备份,防止数据丢失。
降低主库压力:通过将读请求分流到从库,主库可以更专注于处理写请求,从而提升写操作的性能。
然而,读写分离也存在一些挑战,比如主从同步延迟问题,这可能导致从库读取到旧数据。因此,需要根据业务场景选择是否采用读写分离,以及如何分离(例如,强一致性读仍然走主库)。
为什么不能所有节点都读写?
其实主要原因是 数据一致性问题:
sql-- 假设有两个节点都可读写
节点A: INSERT INTO users (id, name) VALUES (1, '张三');
-- 此时节点B还不知道这个数据
节点B: SELECT * FROM users WHERE id = 1;
-- 可能会返回"找不到记录"
节点B: INSERT INTO users (id, name) VALUES (1, '李四');
这就会导致主键冲突!数据不一致!
除了 主从复制 + 读写分离,那能开启多主节点吗?答案是可以的,MySQL 官方也支持此模式
sql-- MySQL Group Replication 多主模式
-- 所有节点都可读写,但有限制:
SET GLOBAL group_replication_single_primary_mode=OFF; -- 启用多主模式
但需要注意会存在以下几点问题:
使用 主从复制+读写分离 一定程度上可以解决问题,但是随着用户量的增加、访问量的增加、数据量的增加依然会带来大量的问题.
提示
MySQL读写分离后,只能有一台节点(主库)能写入,这是由MySQL的复制机制和数据一致性要求决定的,不能调整成多台节点同时写入。
部分分布式数据库或中间件(如InnoDB Cluster)支持多写模式,但需额外的数据冲突解决机制,不属于标准MySQL读写分离范畴。标准MySQL读写分离始终遵循“单主写入”。
随着访问量的持续不断增加,慢慢的我们的系统项目会出现许多用户访问同一内容的情况,比如秒杀活动,抢购活动等。
那么对于这些热点数据的访问,没必要每次都从数据库重读取,这时我们可以使用到缓存技术,比如 redis、memcache 来作为我们应用层的缓存。

虽然我们应用了缓存,性能得到了快速提升,但是这只能缓解读取压力,数据库的写入压力还是很大,且随着数据量的增长,性能还是会越来越慢。
我们的系统架构从单机演变到这个阶,所有的数据都还在同一个数据库中,尽管采取主从、读写分离和增加缓存的方式,但是随着数据库的压力持续增加,数据库的瓶颈仍然是个最大的问题。因此我们可以考虑对数据的垂直拆分和水平拆分。就是今天所讲的主题,分库分表。
分库分表是数据库架构演进的终极手段之一,它通过打破传统单一数据库的物理边界,将一个逻辑上的大数据库(表)拆分成多个小的、独立的物理单元进行存储和管理。
分库:将原本存储在一个数据库中的数据,按照特定规则分布到多个数据库中。
分表:将原本存储在一个数据表中的数据,按照特定规则分布到多个数据表中。
这两种技术可以独立使用,但更常见的是结合使用,即“分库又分表”,以实现最大程度的扩展。

1、性能瓶颈
单机资源上限:单台数据库服务器的CPU、内存、磁盘I/O和网络连接数存在物理极限。当数据量或并发量增长到一定规模,这些资源会成为瓶颈,导致查询变慢、事务超时。
集中式锁竞争:所有请求集中在一个数据库实例上,高并发写操作会引发激烈的锁竞争(如行锁、表锁),导致大量请求处于等待状态,系统吞吐量下降。
2、可用性与风险集中
单点故障:所有业务都依赖一个数据库,一旦该数据库发生硬件故障、机房网络问题或严重Bug,将导致整个系统不可用,风险高度集中。
维护困难:对单一大库进行备份、迁移、版本升级等运维操作,时间窗口长、风险高、失败回滚代价巨大。
3、 存储与运维的物理限制
单表数据膨胀:单表数据量过亿后,即使有索引,B+树的层级也会变深,查询性能显著下降。此外,数据文件过大,导致 ALTER TABLE 等DDL操作耗时极长,几乎无法在线进行。
成本与扩展性:垂直升级硬件(买更好的服务器)成本高昂,且很快就会遇到天花板。分库分表可以走向成本更低的水平扩展路线。
引入分库分表会极大地增加系统复杂度,因此不应作为首选方案。通常在经历读写分离、缓存优化、SQL与索引调优等手段后,若以下核心指标仍然告警,才需要考虑。
1、数据量维度
单表数据量:一般认为,MySQL单表数据量达到千万行级别是一个需要警惕的信号。当达到5000万至1亿行时,性能往往会明显下降,应考虑分表。
数据库总数据量:单个数据库实例的数据总量超过 2TB(考虑到备份、恢复和管理的便利性),应考虑分库。
2、性能与负载维度
关键业务查询延时:即使在优化后,核心业务的95%分位(P95)查询响应时间仍持续超过1秒,且瓶颈明确在数据库。
数据库服务器负载:数据库服务器的 CPU使用率或IO利用率长期超过70%,并且与业务增长趋势正相关。
并发连接数:数据库的活跃连接数经常接近或达到 max_connections 的上限。
3、业务发展维度
可预见的快速增长:业务处于爆发期(例如用户量、订单量预计年增长10倍以上),需要提前规划架构,避免到时被动。
高可用性要求:业务要求99.99%以上的可用性,不能接受单数据库实例故障带来的全局服务中断。
分库分表主要有四种方式: 垂直分库、垂直分表、水平分库、水平分表
1、垂直分库
数据库中不同的表对应着不同的业务,垂直切分是指按照业务的不同将表进行分类,分布到不同的数据库上面,同时将数据库部署在不同服务器上,从而达到多个服务器共同分摊压力的效果

我们的微服务架构中,其本质上就是垂直领域的分库应用
2、垂直分表
当一张表中的字段太多且包含大字段的时候,在查询时对数据库的IO、内存会受到影响,同时更新数据时,产生的binlog 文件会很大,MySQL在主从同步时也会有延迟的风险,我们可以将一个表按照业务拆分成多表,每个表存储其中一部分字段,这就是垂直分表
例如对职位表进行垂直拆分, 将职位基本信息放在一张表, 将职位描述信息存放在另一张表

垂直拆分带来的一些提升
但是垂直拆分没有彻底解决单表数据量过大的问题
3、水平分库
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同,这就是水平分库。简单讲就是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(服务节点)上面, 例如将订单表 按照id是奇数还是偶数, 分别存储在不同的库中。
水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

4、水平分表
针对数据量巨大的单张表(比如订单表),按照规则把一张表的数据切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。

总结一下
关系型数据库在单机单库的情况下,比较容易出现性能瓶颈问题,分库分表可以有效的解决这方面的问题的同时,也会产生一些比较棘手的问题.
1、分布式事务
当我们需要更新的内容同时分布在不同的库时, 不可避免的会产生分布式事务的问题。原来在一个数据库操作, 本地事务就可以进行控制, 分库之后 一个请求可能要访问多个数据库,如何保证事务的一致性,目前还没有简单的解决方案.
2、跨节点关联的问题
在分库之后, 原来在一个库中的一些表被分散到多个库,原本简单的 JOIN、ORDER BY ... LIMIT、SUM/COUNT 会变得异常困难,可能需要业务层多次查询后合并。
解决这种关联查询需要我们在代码层面进行控制,将关联查询拆开执行,然后再将获取到的结果在内存中进行拼装.
3、分页与排序查询的问题
分库并行查询时,如果用到了分页每个库返回的结果集本身是无序的, 只有将多个库中的数据先查出来,然后再根据排序字段在内存中进行排序,如果查询结果过大也是十分消耗资源的,同时还存在深度分页的考虑。
4、全局唯一ID
在水平分库分表的环境中,表中的数据存储在不同的数据库不同的表, 主键自增无法保证ID不重复, 所以不能依赖数据库自增ID,需设计分布式ID生成方案(如Snowflake、Leaf)。
5、公共表的问题
拆分后的数据库都需要从公共表中获取数据,某一个数据库更新看公共表其他数据库的公共表数据需要进行同步.
简单来说,分库分表是应对海量数据和高并发的“终极外科手术”,它通过水平扩展的方式解决了单机数据库在性能、可用性和存储上的根本性瓶颈,但其自身也引入了巨大的复杂性和技术挑战。
接下来带着这些问题,我们就来一起来学习一下分布式的数据库生态系统 Apache ShardingSphere
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!