分库分表详解

分库分表

读写分离主要应对的是数据库读并发,没有解决数据库存储问题。试想一下:如果 MySQL 一张表的数据量过大怎么办?

换言之,我们该如何解决 MySQL 的存储压力呢?

答案之一就是 分库分表

什么是分库?

分库 就是将数据库中的数据分散到不同的数据库上,可以垂直分库,也可以水平分库。

垂直分库 就是把单一数据库按照业务进行划分,不同的业务使用不同的数据库,进而将一个数据库的压力分担到多个数据库。

举个例子:说你将数据库中的用户表、订单表和商品表分别单独拆分为用户数据库、订单数据库和商品数据

 

水平分库 是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,这样就实现了水平扩展,解决了单表的存储和性能瓶颈的问题。

举个例子:订单表数据量太大,你对订单表进行了水平切分(水平分表),然后将切分后的 2 张订单表分别放在两个不同的数据库。

什么是分表?

分表 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。

垂直分表 是对数据表列的拆分,把一张列比较多的表拆分为多张表。

举个例子:我们可以将用户信息表中的一些列单独抽出来作为一个表。

水平分表 是对数据表行的拆分,把一张行比较多的表拆分为多张表,可以解决单一表数据量过大的问题。

举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。

水平拆分只能解决单表数据量大的问题,为了提升性能,我们通常会选择将拆分后的多张表放在不同的数据库中。也就是说,水平分表通常和水平分库同时出现。

 

什么情况下需要分库分表?

遇到下面几种场景可以考虑分库分表:

  • 单表的数据达到千万级别以上,数据库读写速度比较缓慢。
  • 数据库中的数据占用的空间越来越大,备份时间越来越长。
  • 应用的并发量太大(应该优先考虑其他性能优化方法,而非分库分表)。

不过,分库分表的成本太高,如非必要尽量不要采用。而且,并不一定是单表千万级数据量就要分表,毕竟每张表包含的字段不同,它们在不错的性能下能够存放的数据量也不同,还是要具体情况具体分析。

之前看过一篇文章分析 “InnoDB 中高度为 3 的 B+ 树最多可以存多少数据”,写的挺不错,感兴趣的可以看看。

常见的分片算法有哪些?

分片算法主要解决了数据被水平分片之后,数据究竟该存放在哪个表的问题。

常见的分片算法有:

  • 哈希分片:求指定分片键的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。哈希分片可以使每个表的数据分布相对均匀,但对动态伸缩(例如新增一个表或者库)不友好。
  • 范围分片:按照特定的范围区间(比如时间区间、ID 区间)来分配数据,比如 将 id1~299999 的记录分到第一个表, 300000~599999 的分到第二个表。范围分片适合需要经常进行范围查找且数据分布均匀的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
  • 映射表分片:使用一个单独的表(称为映射表)来存储分片键和分片位置的对应关系。映射表分片策略可以支持任何类型的分片算法,如哈希分片、范围分片等。映射表分片策略是可以灵活地调整分片规则,不需要修改应用程序代码或重新分布数据。不过,这种方式需要维护额外的表,还增加了查询的开销和复杂度。
  • 一致性哈希分片:将哈希空间组织成一个环形结构,将分片键和节点(数据库或表)都映射到这个环上,然后根据顺时针的规则确定数据或请求应该分配到哪个节点上,解决了传统哈希对动态伸缩不友好的问题。
  • 地理位置分片:很多 NewSQL 数据库都支持地理位置分片算法,也就是根据地理位置(如城市、地域)来分配数据。
  • 融合算法分片:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
  • ……

分片键如何选择?

分片键(Sharding Key)是数据分片的关键字段。分片键的选择非常重要,它关系着数据的分布和查询效率。一般来说,分片键应该具备以下特点:

  • 具有共性,即能够覆盖绝大多数的查询场景,尽量减少单次查询所涉及的分片数量,降低数据库压力;
  • 具有离散性,即能够将数据均匀地分散到各个分片上,避免数据倾斜和热点问题;
  • 具有稳定性,即分片键的值不会发生变化,避免数据迁移和一致性问题;
  • 具有扩展性,即能够支持分片的动态增加和减少,避免数据重新分片的开销。

实际项目中,分片键很难满足上面提到的所有特点,需要权衡一下。并且,分片键可以是表中多个字段的组合,例如取用户 ID 后四位作为订单 ID 后缀。

分库分表会带来什么问题

记住,你在公司做的任何技术决策,不光是要考虑这个技术能不能满足我们的要求,是否适合当前业务场景,还要重点考虑其带来的成本。

引入分库分表之后,会给系统带来什么挑战呢?

  • join 操作:同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作。这样就导致我们需要手动进行数据的封装,比如你在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。不过,很多大厂的资深 DBA 都是建议尽量不要使用 join 操作。因为 join 的效率低,并且会对分库分表造成影响。对于需要用到 join 操作的地方,可以采用多次查询业务层进行数据组装的方法。不过,这种方法需要考虑业务上多次查询的事务性的容忍度。
  • 事务问题:同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足我们的要求了。这个时候,我们就需要引入分布式事务了。
  • 分布式 ID:分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 ID 了。
  • 跨库聚合查询问题:分库分表会导致常规聚合查询操作,如 group by,order by 等变得异常复杂。这是因为这些操作需要在多个分片上进行数据汇总和排序,而不是在单个数据库上进行。为了实现这些操作,需要编写复杂的业务代码,或者使用中间件来协调分片间的通信和数据传输。这样会增加开发和维护的成本,以及影响查询的性能和可扩展性。
  • ……

另外,引入分库分表之后,一般需要 DBA 的参与,同时还需要更多的数据库服务器,这些都属于成本。

分库分表有没有什么比较推荐的方案?

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。

ShardingSphere 项目(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar)是当当捐入 Apache 的,目前主要由京东数科的一些巨佬维护。

ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理、影子库、数据加密和脱敏等功能。

ShardingSphere 提供的功能如下:

ShardingSphere 提供的功能

ShardingSphere 的优势如下(摘自 ShardingSphere 官方文档:概览 :: ShardingSphere):

  • 极致性能:驱动程序端历经长年打磨,效率接近原生 JDBC,性能极致。
  • 生态兼容:代理端支持任何通过 MySQL/PostgreSQL 协议的应用访问,驱动程序端可对接任意实现 JDBC 规范的数据库。
  • 业务零侵入:面对数据库替换场景,ShardingSphere 可满足业务无需改造,实现平滑业务迁移。
  • 运维低成本:在保留原技术栈不变前提下,对 DBA 学习、管理成本低,交互友好。
  • 安全稳定:基于成熟数据库底座之上提供增量能力,兼顾安全性及稳定性。
  • 弹性扩展:具备计算、存储平滑在线扩展能力,可满足业务多变的需求。
  • 开放生态:通过多层次(内核、功能、生态)插件化能力,为用户提供可定制满足自身特殊需求的独有系统。

另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。

不过,还是要多提一句:现在很多公司都是用的类似于 TiDB 这种分布式关系型数据库,不需要我们手动进行分库分表(数据库层面已经帮我们做了),也不需要解决手动分库分表引入的各种问题,直接一步到位,内置很多实用的功能(如无感扩容和缩容、冷热存储分离)!如果公司条件允许的话,个人也是比较推荐这种方式!

分库分表后,数据怎么迁移呢?

分库分表之后,我们如何将老库(单库单表)的数据迁移到新库(分库分表后的数据库系统)呢?

比较简单同时也是非常常用的方案就是停机迁移,写个脚本老库的数据写到新库中。比如你在凌晨 2 点,系统使用的人数非常少的时候,挂一个公告说系统要维护升级预计 1 小时。然后,你写一个脚本将老库的数据都同步到新库中。

如果你不想停机迁移数据的话,也可以考虑双写方案。双写方案是针对那种不能停机迁移的场景,实现起来要稍微麻烦一些。具体原理是这样的:

  • 我们对老库的更新操作(增删改),同时也要写入新库(双写)。如果操作的数据不存在于新库的话,需要插入到新库中。 这样就能保证,咱们新库里的数据是最新的。
  • 在迁移过程,双写只会让被更新操作过的老库中的数据同步到新库,我们还需要自己写脚本将老库中的数据和新库的数据做比对。如果新库中没有,那咱们就把数据插入到新库。如果新库有,旧库没有,就把新库对应的数据删除(冗余数据清理)。
  • 重复上一步的操作,直到老库和新库的数据一致为止。

想要在项目中实施双写还是比较麻烦的,很容易会出现问题。我们可以借助上面提到的数据库同步工具 Canal 做增量数据迁移(还是依赖 binlog,开发和维护成本较低)。

如何使用路由组件实现分库分表 

要实现分库分表(Sharding)功能,使用路由组件可以有效地将数据按照特定规则分散到多个数据库和表中。路由组件的主要作用是根据某些规则(如字段值、哈希值、范围等)来决定数据应该存储在哪个库和表。以下是一个基本的实现步骤,利用路由组件实现分库分表的过程。

实现步骤

1. 定义分库分表策略

首先,你需要设计一个分库分表的策略。常见的策略有:

  • 哈希路由:根据某个字段(例如 user_id)的哈希值进行路由。
  • 范围路由:根据字段的范围进行路由,比如按时间范围(按年、按月)或某些数字区间来划分数据。
  • 复合路由:结合多个字段进行路由,比如按照 user_idregion_id 一起进行路由。

例子:

  • user_id 的哈希值将用户数据分到不同的表中(例如 user_0, user_1)。
  • 按照时间分表,order 数据按年份分表(例如 order_2020, order_2021)。
2. 选择合适的路由组件

选择一个合适的路由中间件或框架。常见的分库分表路由组件有:

  • ShardingSphere:一个轻量级的开源数据库分库分表中间件,支持多种路由策略。
  • Mycat:支持较为复杂的分库分表规则,适用于大规模的分布式系统。
  • TDDL(阿里巴巴的分库分表中间件):阿里巴巴的解决方案,特别适用于需要高可用性的系统。
  • Cobar:阿里巴巴开发的数据库路由中间件,主要应用于大数据量的场景。

这里我们以 ShardingSphere 为例

3. 配置 ShardingSphere 实现路由

假设你要使用 ShardingSphere 来实现分库分表,以下是基本的配置步骤:

1.1 引入 ShardingSphere 依赖

如果你使用的是 Spring Boot,可以通过以下方式引入 ShardingSphere 的依赖:

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>5.x.x</version>
</dependency>

1.2 配置数据源和分库分表规则

application.ymlapplication.properties 中配置分库分表规则。假设我们使用 user_id 进行哈希路由,并将数据分到 2 个数据库和 2 张表。

spring:
  shardingsphere:
    datasource:
      names: ds0, ds1
      ds0:
        url: jdbc:mysql://localhost:3306/db0
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver
      ds1:
        url: jdbc:mysql://localhost:3306/db1
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver
    sharding:
      tables:
        user:
          actual-data-nodes: ds${0..1}.user_${0..1}  # 2 个库,每个库 2 张表
          table-strategy:
            inline:
              sharding-column: user_id  # 路由字段
              sharding-rule: "user_id % 2"  # 哈希路由规则:user_id % 2
      binding-tables: 
        - user  # 将 user 表绑定在一起

说明

  • ds0, ds1 是两个数据源,分别对应 db0db1
  • user 表被分为 user_0user_1,并且数据根据 user_id % 2 进行路由。
  • binding-tables 用来确保这些表作为同一个逻辑表进行操作。
1.3 配置分库分表的路由算法

ShardingSphere 支持不同的路由策略,inline 是其中一种。它基于字段值进行路由。你还可以实现自定义路由算法。

table-strategy:
  inline:
    sharding-column: user_id  # 选择用于分片的列
    sharding-rule: "user_id % 2"  # 路由规则,哈希分片

这个配置将数据根据 user_id 的哈希值分配到 2 个表中。

1.4 支持的路由策略
  • InlineStrategy(内联策略):适用于简单的基于列值计算的路由(如模运算)。
  • RangeStrategy(范围策略):可以基于范围(如时间、ID 范围)进行路由。
  • ComplexStrategy(复杂策略):可以结合多种字段进行路由。
4. 处理分库分表后的 SQL 操作

在应用层,ShardingSphere 会自动处理 SQL 操作。你不需要显式指定要访问哪个数据库或表。比如,你执行如下 SQL 查询:

SELECT * FROM user WHERE user_id = 12345;

ShardingSphere 会根据配置的路由规则,自动选择存储该数据的具体数据库和表(例如 ds0.user_1)。

5. 路由组件处理结果

当你执行 SQL 查询时,ShardingSphere 会根据规则自动决定:

  • 查询是否涉及多张表。
  • 是否跨库查询(例如 JOIN 查询)。
  • 如何返回查询结果。

对于 多数据源多表查询等,ShardingSphere 通过智能的路由和查询合并机制,保证查询结果的正确性和性能。

6. 扩展与高可用性
  • 动态扩容:ShardingSphere 允许你在运行时增加更多的数据源,自动适应数据的增长。
  • 读写分离:ShardingSphere 可以配置多个数据源,实现主从复制和读写分离,提高性能。
  • 事务支持:提供分布式事务的支持,保证跨库操作的一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值