【数据库】分库分表分区

目录

  • 前言
    • 拆分方式
      • 水平拆分(Sharding)
      • 垂直拆分
    • 分区
  • 项目中分库分表涉及的问题
    • 1. 为什么要分库分表
    • 2. 技术选型
    • 3. 分片策略以及如何扩缩容/迁移
      • (1)根据范围range分表
      • (2)根据hash取模
      • 参考
      • 分库分表如何迁移数据(感觉不太好,待完善)
    • 4. 带来的业务问题
      • 主键ID怎么生成
      • 带来的分布式事务问题
      • 业务查询时没有分表键(待补充)
      • join问题
      • group by聚合问题
      • order by + limit offset分页问题
    • 参考
  • 四、业务场景
    • 冷热分离怎么做
  • 其它-数据库N范式

前言

先回顾一些概念

拆分方式

水平拆分(Sharding)

解释: 表结构不变,数据分散到多个其他相同结构的表中
优点: 缓解单数据库表的压力,流量按数据分布进行分流。

垂直拆分

解释: 表结构发生改变,一张表的列拆成多张表,每张表的数据行数一样,且保持不变。
优点: 常常按业务拆分,缓解表压力,流量按业务分流。

分区

以MySQL为例。在5.1版本之后增加了对分区的支持,常见的存储引擎都支持分区,如InnoDB、MYISAM、NDB等

分区的特点:

  • 分区是在物理上将一张表分割成多张物理分区,但是逻辑上对外还是一张表
  • 支持水平分区,但不支持垂直分区
  • 采用的是局部分区索引的方式,即一个分区对象存放一部分索引,而非全局分区索引方式(所有分区数据对应的索引存放在一个对象中)。因此若是查询未能定位数据在哪个分区,即使走索引也会在每个分区挨个走索引,成本翻多倍。
  • 分区字段可以存在NULL

如下场景分区表性能好:

  • 删除某分区下的数据时,相比非分区表,不需要查出所有数据再删除,只需要删除分区即可。
  • 查询的数据只存在一个分区内且数据量较大时,性能高。
    • 查询分区表时,根据查询键找到所在分区,全表扫描。
    • 查询非分区表时,要么因为查询数据量大发生全表扫描,要么走索引找到所有符合条件的数据,再回表,整体性能不如分区表的全表扫描

如下场景分区表性能差:

  • 走索引且查询数据量小,查询条件无法判断数据所在分区
    • 查询分区表时,会在所有的分区表进行索引扫描。代价是分区数量*单表IO次数
    • 查询非分区表时,直接查询索引。代价是单表IO次数

其它优缺点:

  • 程序对分区表无感,不需要修改代码
  • 数据分布可能不均匀,性能提升有限
  • 使用RANGE、LIST的分区方式,则需要不断对分区维护。有分区以外的数据时,需要新建分区。

参考

  • mysql表分区详解

项目中分库分表涉及的问题

在MySQL、Postgresql等传统关系型数据库中,分区有一定局限性,往往不如直接分库分表解决的问题多,因此在此类数据库常常使用分库分表解决问题。

分区较为直观的好处是 1. 分区独立保存索引,控制索引大小 2. 分区表对外逻辑为一张表,不需要修改上层逻辑 。分片存储多节点单独考虑。

1. 为什么要分库分表

什么时候分库分表:

  1. 单机磁盘不足连接数过多、需要落地微服务理念而拆分业务的情况下需要分库
  2. 单表数据量过大导致分表是明因,索引过大才是本质:
    • 索引过大,一次无法将全部索引缓存,需要额外进行磁盘IO,引发的查询缓慢(调大BufferPool能解决?
    • 索引过大, 数据插入时重建索引比较耗时
    • 索引过大,B+树过高,回表后IO次数多。
      • 个人猜想:比如单表2亿数据,建立非聚集索引,树高度为4。拆分成10张表后 ,单表2千万,树高度为3,查询一条数据,走索引并回表,IO次数为4*2=8
      • 若分表,假设按查询条件分表。IO次数为1(查找分区)+ 3 * 2 (回表)= 7

如何计算树的高度以及如何判断一次查询的IO次数

分库分表参考:

分库分表的数据量判断

阿里巴巴的《Java开发手册》提出:单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。

我个人认为需要计算索引的树高度,超过3层时就要考虑分表了。

  • 还有别的说法,跟BufferPool大小有关。

设计分库分表注意点:

若是查询条件里没有分片键,就会在所有表里查询,性能反而下降。

2. 技术选型

Client模式 Proxy模式
概念 分库分表逻辑在本地控制,直接连接多个数据库,并且将查询结果在本地汇总 独立的服务管理分库,应用连接代理服务。
性能方面 较好。直连数据库 一般,多了层代理转发,并且还要维护一个路由关系表
内存方面 本地数据合并,占用本地的CPU和内存 不额外占用调用者的CPU和内存
架构复杂度 只需要引入jar包,不会有单点故障问题 需要额外部署服务,要考虑高可用,复杂度增加
升级管理 需要单独维护jar包,升级要更改各个服务的依赖版本 对调用者透明,升级维护无感知
中间件 Zebra、Sharding-jdbc、TSharding MyCat、KingShard、Atlas、Cobar

3. 分片策略以及如何扩缩容/迁移

(1)根据范围range分表

优点:

  • 扩容方便,直接增加对应的表即可

缺点:

  • 存在热点问题,比如按日期分表,最近一个月的数据都在一张表里,访问量大。而历史数据访问量小

(2)根据hash取模

优点: 不会存在明显的热点问题
缺点: 扩容难,重hash会发生缓存雪崩

如何避免重计算hash并导致所有数据需要迁移(类似缓存雪崩): 可以参考hashmap的扩容方式。节点数为2的幂次,每次扩容节点数翻倍,并且重新计算hash后,只有一部分数据需要迁移到新节点。

解决方案:

  • 提前设计,避免扩容
  • 从库升级为主库,删除冗余数据
  • 一致性hash(这种情况怎么在线迁移数据)个人认为跟Redis Cluster的槽有点像。都是避免redis节点和数据直接关联,利用槽去绑定数据。
  • 不长时间停机双写,可以用在分表方案变更时使用。问题是所有数据都要迁移

一致性hash简单原理:

  • 构造一个圆环,圆环上有 2 32 2^{32} 232个槽
  • 对服务器的IP或者名称进行hash,然后用 2 32 2^{32} 232取模,将其映射到圆环的槽位上
  • 将数据的key进行hash并取模,落到圆环的槽位上
  • 圆环上的数据沿着顺时针查找到的第一个服务器,就是所属的服务器。

优点: 扩缩容方便,不会引起缓存雪崩。直接添加或者删除结点后,只会有一部分数据需要迁移
缺点: 可能有数据倾斜造成数据分布不均的问题,可以对机器名称或者ip加编号后进行hash,得到多个虚拟结点。虚拟节点数据越多,均匀分布概率越大。

参考

  • 一致性哈希算法原理详解
  • Redis Cluster为什么选哈希槽不选一致性哈希?

分库分表如何迁移数据(感觉不太好,待完善)

停机迁移比较简单,建好新的分区表后直接将数据插过去就可以,然后修改代码读取表。这里主要描述下在线迁移。

数据双写:

  1. 代码层面,开启新旧表双写,并编写代理层能够控制在线切换读取的表。发布服务在线升级。
  2. 记录新表增量字段起始值,将旧表中小于该值的数据迁移到新表中
    • 推荐增量字段为 有序主键>唯一索引键>非唯一索引键
    • 唯一键好处理,非唯一键还需要将等于新表起始值的数据迁移过来(发生主键冲突的数据则不需要迁移)

异步监听: (没写清楚)
监听binlog,将数据迁移到新表上

4. 带来的业务问题

主键ID怎么生成

优点 缺点
uuid 实现简单 连续性差,作为主键每次新增数据都会触发索引重建。
分布式环境中可能重复
雪花算法 性能好,有序 依赖服务器时间,时钟回拨可能生成重复ID
号段模式
redis/zookeeper Redis基于INCR 命令生成 分布式全局唯一id
zookeeper一种通过节点,一种通过节点的版本号

基因算法

带来的分布式事务问题

需要分布式事务。不过分布式事务本身会带来额外的损耗,所以设计时应当尽量避免。

分布式事务见:分布式理论与Seata

业务查询时没有分表键(待补充)

详解分库分表后非分片键如何查询

join问题

跨库:

  1. 字段冗余,避免关联
  2. 若关联全局表,可以在所有库中冗余一份相同的全局表,避免跨结点
  3. 代码层面关联
  4. 借助ETL工具,定时关联汇聚,生成新的关联好的表
  5. 判断关联关系,将能够关联上的数据放到一个分片上。

同库分表后关联:

待办

group by聚合问题

各节点得到结果后,程序端合并

order by + limit offset分页问题

业务上禁止跳页,只允许上一页下一页:

  • 根据主键排序的话,每次分页需要记录上次查询结果最后一条数据的主键。查询下一页数据(比如一页十条),需要在所有分表执行select xxx from table where id > xx limit 10 。然后在程序端合并,得到最小的10条数据。
  • 根据唯一索引列排序时,方法同主键排序
  • 根据非唯一索引列排序时,应该在这个基础上按主键排序。即 order by 非唯一索引列,主键 。这时要记录上次查询结果最后一条数据的非唯一索引列和主键

允许跳页:

一次返回数据较多,不推荐

比如查询第二页数据。需要返回所有表的前两页数据,在程序中合并计算第二页数据是哪些。页数越远,需要处理的数据越多,网络开销、CPU性能都会占用较大。

参考

  • 关于分库分表
  • 数据库分区、分表、分库、分片
  • 分库分表-理论、方案、问题总结
  • 分库分表经典15连问
  • MySQL:互联网公司常用分库分表方案汇总

四、业务场景

  • 用户操作日志:分表

冷热分离怎么做

其它-数据库N范式

范式 在表中的体现 描述
第一范式 一列不能包含多类值 数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性
第二范式 非主键列只依赖一部分主键列 先满足第一范式,实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系
第三范式 非主键列依赖非主键列 先满足第二范式,数据库表中不包含已在其它表中已包含的非主关键字信息

在OLAP系统中,常常会反第三范式冗余些字段,避免表关联提高查询效率。比如某表既存在非主键码值字段,又存在非主键码值的名称(非主键之间依赖)

你可能感兴趣的:(#,数据库,数据库,java,服务器)