MySQL - 浅谈分库分表

1、使用背景


关系型数据库以MySQL为例,其单机的存储能力连接数是有限的,这使得MySQL很容易会成为一个系统的性能瓶颈。当单表数据量在百万级时,我们还可以通过优化索引添加从库等方式来提升性能。然而一旦数据量跃向千万级,那么再怎么优化数据库,都无法避免数据库操作性能的严重下滑。

为了减少数据库的负担提升数据库响应速度缩短查询时间,这时候就需要进行分库分表

  • 表的数量达到了几百乃至上千张时,若众多的业务模块都访问该数据库,则会对该数据库造成较大的压力,此时需要考虑对其进行分库
  • 表的数据量达到了数千万级别时,则对该表进行对许多操作性能都会损耗严重,此时需要考虑对其进行分库或分表

2、切分方式简介


分库分表就是要将大量数据分散到多个数据库中,减少每个数据库中的数据量、加快响应速度,以此来提升数据库整体性能。其核心思想就是如何对数据进行切分( Sharding ),以及切分后如何对数据快速地定位与整合。
针对数据切分类型,大致可以分为垂直切分水平切分两种。

2.1、垂直切分

垂直切分,也称为纵向切分,是按照业务模块进行切分,将不同模块的表切分到同一或不同的数据库中。垂直切分又分为垂直分库垂直分表

  • 垂直分库
    垂直分库是基于业务分类的,根据不同的业务将表分到不同的数据库中。这与微服务管理很相似,微服务中每一个独立的服务都拥有自己的数据库,需要不同业务的数据需接口调用。

    在这里插入图片描述

  • 垂直分表
    垂直分表是基于数据表的列为依据切分的,是一种大表拆小表的模式。
    例如:一个商品表(product)有很多字段,比如价格,商品详情等。用户在进入一个商城的时候,琳琅满目的商品扑面而来,只有当我们真正的需要了解某个产品的时候,才会点进去查看该产品的详情介绍,于是在设计表结构的时候就可以将商品明细信息单独拆分到扩展表(product_extend)中:

    在这里插入图片描述
    数据库是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段长度也都较短,可以加载更多数据到内存中,增加查询的命中率,减少磁盘IO,以此来提升数据库性能。

垂直切分可以实现业务间解耦,不同业务的数据进行独立的维护、监控、扩展。在高并发场景下,一定程度上缓解了数据库的压力。

然而垂直切分在一定程度上提升了开发的复杂度,由于业务的隔离性,很多表无法直接访问,必须通过接口方式聚合数据。垂直切分也同时增加了分布式事务的管理难度。

2.2、水平切分

通过垂直切分,可以一定程度上缓解数据库的压力,但无法从根本上解决单表数据量过大的问题。当我们无法对业务逻辑进一步细粒度切分,且此时依旧存在单库读写、存储性能瓶颈的问题,就需要考虑进行水平切分。

水平切分,也称横向切分,是将一张大表以规定的切分规则,按行切分成不同的表或者切分到不同的库中。换言之:水平切分就是将一张大数据量的表,切分成多个表结构相同,且每个表只占原表一部分数据,然后按不同的
条件分散到一个或多个数据库中。

水平切分又分为库内分表分库分表两种:

  • 库内分表
    库内分表将表拆分在同一个数据库实例中。这种拆分方式只是解决了单一表数据量过大的问题,并没有将拆分后的表分布到不同机器的库上。那么对这些表对操作,会竞争同一个物理机的CPU、内存、网络IO。

    在这里插入图片描述

  • 分库分表
    分库分表则是将按照一定规则切分出来的子表,分散到不同的数据库中,从而使得单个表的数据量变小,以达到分布式的效果。

    在这里插入图片描述

水平切分解决了高并发时单库数据量过大的问题,提升了系统的稳定性和负载能力,对于业务系统改造的工作量不是很大。

然而水平切分跨库之间的事务难以保证其一致性。同时,跨库的join关联查询性能较差,扩容的难度和维护量都相对较大。

3、切分原则与规则


3.1、切分原则

  • 第一原则:能不切分尽量不要切分。
  • 第二原则:如果要切分一定要选择合适的切分规则,提前规划好
  • 第三原则:数据切分尽量通过数据冗余表分组降低跨库 Join 的可能
  • 第四原则:由于数据库中间件对数据 Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 Join

3.2、切分规则

分库分表以后会出现一个问题,一张表会出现在多个数据库里,到底该往哪个库的表里存呢?

  • 根据取值范围
    按照时间区间或ID区间来切分。比如说:假设有一张用户表(User)需要切分,可以定义每个库的User表里只存10000条数据,第一个库 userId 从1 ~ 9999,第二个库10000 ~ 20000,第三个库20001 ~ 30000…以此类推。同理,也可以按照日期等其他业务逻辑字段进行切分。
    根据取之范围切分的优点
    (1)单表数据量是可控的。
    (2)且水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移。
    (3)能快速定位要查询的数据在哪个库。
    根据取之范围切分的缺点
    (1)由于连续分片可能存在数据热点,如果按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询。

  • hash取模(对hash结果取余)
    hash取模(hash() mod N)的切分方式比较常见,仍以User表为例,对数据库从 0 到 N-1 进行编号,对 User 表中 userId 字段哈希运算得到的值进行取模,得到余数 i:
    i = 0:存第一个库,
    i = 1:存第二个库,
    i = 2:存第三个库,
    以此类推…

    这样同一个用户的数据都会存在同一个库里,用 userId 作为条件查询就很好定位了。
    hash取模切分的优点
    (1)数据分片相对比较均匀,不易出现某个库并发访问的问题
    hash取模切分的缺点
    (1)这种算法存在一些问题,当某一台机器宕机,本应该落在该数据库的请求就无法得到正确的处理,这时宕掉的实例会被踢出集群,此时算法变成hash(userId) mod (N-1),用户信息可能就不再在同一个库中。


4、分库分表带来的问题


  • 事务一致性问题
    由于表分布在不同库中,不可避免会带来跨库事务问题。一般可使用XA协议两阶段提交处理,但是这种方式性能较差,代码开发量也比较大。通常做法是做到最终一致性的方案,即不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。
  • 分页、排序问题
    日常开发中分页、排序是必备功能,而多库进行查询时 limit 分页、 order by 排序,着实让人比较头疼。分页需按照指定字段进行排序,如果排序字段恰好是分片字段时,通过分片规则就很容易定位到分片的位置。然而,一旦排序字段非分片字段时,就需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户,过程比较复杂。
  • 全局唯一性约束问题
    由于分库分表后,表中的数据同时存在于多个数据库,而某个分区数据库的自增主键已经无法满足全局唯一,所以此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID。常见的解决方式有以下几种:
    (1)redis incr命令
    (2)数据库生成主键(不建议)
    (3)uuid(不建议)
    (4)snowflake(雪花算法)
  • 跨库 Join 问题
    (1)通过业务分析,将不同库的join查询拆分成多个select
    (2)建立全局表(每个库都有一个相同的表)
    (3)冗余字段(不符合数据库三范式)
    (4)Mycat - E-R分片(将有ER关系的记录都存储到一个库中)
    (5)Sharding JDBC - 绑定表
    (6)最多支持跨两张表跨库的join

6、分库分表工具


自己开发分库分表工具的工作量是巨大的,好在业界已经有了很多比较成熟的分库分表中间件,我们可以将更多的时间放在业务实现上:

  • sharding-jdbc(当当)
  • TSharding(蘑菇街)
  • Atlas(奇虎360)
  • Cobar(阿里巴巴)
  • MyCAT(基于Cobar)
  • Oceanus(58同城)
  • Vitess(谷歌)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值