分库分表(以MySQL为例说明)

本文详细阐述了MySQL中的分库分表原因、时机选择、表键设计、查询策略以及遇到的问题,如事务处理、join连接和分页,同时介绍了分布式ID和分库分表中间件的选择,以及分库分表过程中的部署策略。
摘要由CSDN通过智能技术生成


#  分库分表(以MySQL为例说明)

##  1、为什么要分库?

①减轻磁盘压力
②单库并发连接请求数有限,如果存在过多连接会出现too many connections的问题

## 2、为什么要分表?

①一般单表数据量超千万很大了,单表数据量过大会出现性能瓶颈,建索引、SQL优化、数据库连接参数优化等也挽救不了单库的压力
非常重要的一点,单表数据过多,树深度就会变深,一般3层的B+树索引就可以承载千万级别的数据了。超5000万数据,B+树深度可能会到4层,B+树越深,查找数据耗时越长。
有个公式可以计算3层B+树可承载的数据。B+树的存放总记录数=根结点指针数*单个叶子节点记录行数
MySQL数据存放是以数据页为单位的,一页16k。假设一行数据为1k,那么单个页子节点就可以存放16k/1k=16个记录数
根节点指针数,非叶子节点存放多少个指针呢?假设使用bigint作为主键,它占用64位,合计8字节。而指针在innodb源码中设置的是6字节,据此可推算出非叶子节点可容纳的指针数:16 * 1024/(8 + 6) ≈ 1170
所以3层B+树的存放总记录数= 16 * 1170 * 1170 = 21902400。超过3层,B+容纳的数据就是个天文数字了。不管怎么优化,都是问题

## 3、什么时候考虑分库分表?

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

## 4、如何选择分表键?

分表键,其实就是参于分库分表的字段。比如订单表可用订单ID作为分表键,用户表可用用户ID作为分表键,员工表可用员工工号作为分表键
分表键的选择要考虑到业务的主题。比如你的数据库表是一张企业的客户信息表,就可考虑用客户号做为分表键。
为什么考虑用客户号做分表键呢?
这是因为表是基于客户信息的,所以,需要保证同一个客户信息的数据,落到一个表中,避免触发全表路由。

## 5、非分表键如何查询?

分库分表后无法避免一些特殊的业务场景,比如使用非分表键来查询。
比如用户表,使用用户ID分库分表后,需要使用用户手机号登录,这个时候就需要用用户手机号查询用户信息,而手机号是非分表键
解决方案:
①遍历:遍历所有表,找到符合条件的手机号(一般人不会选择这种方案吧)
②数据冗余同步到ES,然后通过ES来 查询
③基因法:非分表键可以拆分出分表键来,比如生成订单号时可以把客户编号保存进去,通过订单号查询时可以解析出客户号。但是前面说的登录场景,手机号冗余用户ID似乎不太友好

## 6、分表策略选择

①range范围:还是以订单为例,订单表数据量很大,1~300w为一个表,300~600万一张表
优点:扩容简单
缺点:热数据聚集。可能某一段时间数据特别集中,比如双十一、双十二、618大促,订单量远高于平时,仅仅依靠订单ID做范围range分库分表就不太靠谱了
②hash取模:指定的路由key(一般是用户ID、订单ID、客户好作为key)对分表总数进行取模,把数据分散到各个表中。为尽可能分散数据,一般,一般会取哈希值,再做取余:Math.abs(orderId.hashCode()) / tableNumber
③一致性hash:使用hash取模方式,前期如果未做好规划,后期还是需要扩容二次分表,表数量需要增加,所以hash值需要重新计算,这时候就需要迁移数据了。
比如一开始分了10张表,随着业务扩展需要,增加到20张表。那问题随之产生,之前根据订单ID取模10后的数据分散在了各个表中,现在需要重新对所有数据重新取模20来分配数据
为了解决这个扩容迁移问题,可以使用一致性hash思想来解决。一致性哈希:在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表存在的动态伸缩等问题

## 7、如何避免热数据倾斜?

使用range范围+ hash哈希取模结合的分表策略。
例如双十一大促,订单量陡增,可先对订单ID按范围划分到不同库,然后针对单个库使用range方式将数据分散到不同的分表中

## 8、分库后事务如何解决?

未分库分表前,单库单表使用本地事务就好了,但是分库分表后,数据落到了不同库表中,传统事务处理方式就不行了,就要用到分布式事务。
分布式事务解决方案:
①2PC
②3PC
③TCC
④本地消息表
⑤seata
⑥saga
⑦最大努力通知

## 9、跨库join连接问题

分库分表后,数据分散在不同库表中,有时候一个需求需要跨多个库表访问,传统的库表连接就不适用啦
跨库join方案:
①字段冗余:把需要关联的字段放入主表,避免关联操作。例如订单中关联客户号,可将客户姓名冗余到订单表中
②全局表:系统中所有模块都会依赖的表可以在每个库中都保留一份
③数据抽象同步:定时将指定库表做同步,生成新的汇总表。可以借助ETL工具实现
④应用层代码组装:分多次查询不同模块,获取数据后,在应用层做数据过滤

## 10、order by、group by问题

count、order by、group by等聚合函数在分库分表后,都会面临一样的问题,会基于全部数据进行统计计算。可以先在每个节点得到结果,然后在应用层组装数据

## 11、分库分表后的分页问题

解决方案:
①全局视野:在每个节点得到结果,然后在应用层组装数据。
优点:业务无侵入,可精准返回所需数据
缺点:查询不需要的数据,加大网络传输压力。
②禁止跳页。类似于ES,数据量过大时,禁止跳页,只有上一页和下一页。分页时,根据时间排序,将最后一条数据的时间传到分页参数里,然后在每个节点查询大于这个时间的数据,然后在业务层面对数据聚合汇总,做内存排序后返回

## 12、分布式ID选择

①雪花算法(64位)
第一位:符号位(0正数,1负数),ID肯定是要正数,固定0
中间四十一位:给定时间依赖的毫秒值
十位:机器ID,防止冲突
最后十二位:每个机器上生成的ID序列号
②UUID

## 13、分库分表中间件选择

①sharding-jdbc(用过)
②mycat(用过)
③cobar
④Atlas
⑤TDDL
⑥Vitees

## 14、垂直分库、垂直分表、水平分库、水平分表

垂直分库:以表为拆分依据,根据表的不同业务归属,划分到不同库
垂直分表:以字段为拆分依据,按照字段活跃性(重要程度)将表的字段拆到不同表中(主表和子表)
水平分库:以字段为拆分依据,按照一定的拆分策略(range、hash),将一个库的数据拆分到多个库
水平分表:以字段为拆分依据,按照一定的拆分策略(range、hash),将一个表的数据,拆分到多个表

## 15、分库分表完成后,部署是否需要停服?不停服怎么做?

①提供代理层,添加一个开关是访问新的DAO还是旧的DAO,灰度期间,只访问旧的库
②全量发布后,保持双写分库和旧的库,增量新增或更新。用日志或者临时表记录新表ID起始值,旧表中小于这个ID的就是存量数据,需要迁移
③通过脚本,将旧表数据迁移到新表
④停读旧表,改读新表。新表已承载了读写请求,保持新旧表双写一段时间
⑤当读写新表一段时间后,没有问题,就可以直接停掉旧表了

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值