分库分表

1. 数据库分库分表

整个应用中,应用节点可以通过集群或分布式方式能得到很好的扩展,而往往数据库会成为瓶颈,当单表数据行数过大、存储大小过大或者库中数据表数过多都会造成单机性能极速下降,这时即使有过多的复制节点也是无济于事,唯一的办法就是分库分表。

2. 何时分库分表

2.1. 什么物理条件下需要分库分表

数据库性能一般受索引创建合理性、数据行数、数据文件大小、表数量、连接数、硬件性能等因素影响,索引创建的合理性及连接数大小不在本次考虑之内。根据实践经验单库的表数量超过300个,超过需要考虑进行分库,单表的数据行数超过500万,单表数据文件大小超过2G,超过之后性能影响可能较大,需要考虑分表,具体还要依照实际性能分析情况来判断是否需要拆分。

2.2. 何时规划分库分表

分库分表操作在一定情况下带来的是性能的提升,但是同时也带来了应用实现的复杂度。在数据的增删改查以及事务一致性上都会带来额外的处理工作。所以在数据增长情况不明确的情况下不要进行分库分表,过度设计费力不讨好。可以根据线上的数据增长情况提前一定时间考虑和设计实施,时间考量应以不影响业务为标准。

3. 数据切分策略

数据切分策略一般会从横、纵两个维度来考虑处理,即水平切分(横)和垂直切分(纵)。

3.1. 垂直切分

垂直切分包括垂直分表和垂直分库。

3.1.1. 垂直分库

  • 垂直分库即大库拆小库,把众多表按照关联耦合度拆分到多个库中,拆分后的库内部的各表关联度较高,与其他库中的表关联度较低。拆分后原来库的数据压力分散到了各个库,思路跟微服务一致。
  • 垂直分库后单表的数据量并没有减少,大数据量情况下仍然要考虑水平切分。原本在同一表中join查询操作只能通过接口来处理了,DML操作也需要考虑事务一致性问题。

3.1.2. 垂直分表

  • 垂直分表即大表拆小表,将表中的不常使用或者字段占用空间过大的列拆分到其他表。这种方式可以解决表行数据过大导致跨页带来的额外性能开销,单行数据量减少,高频率访问的数据加载到内存中,减少磁盘io提升查询性能。
  • 垂直拆分同样不能减少数据行数,带来的问题是夸表操作。

3.2. 水平切分

水平切分即不破坏原有表结构,只是根据某一个规则(一般根据表某一个字段进行逻辑运算)来把数据分散到多个同类的表中,单表/库的数据量减少。

3.2.1. 水平切分常用规则

3.2.1.1. 切分维度选择
  • 用户id
  • 时间维度
  • 主键id
3.2.1.2. 切分方式
  • 范围切分法,即选取某一维度通过范围划分表或库。如业务数据根据时间维度每一个月生成一张数据表。
  • 数值取模切分法,即选取某一维度通过对其取模将放到对应表或库。如根据主键id将其hash并mod,值为0的存入库0,值为1的存入库1...采用这种方式需要考虑增库/表时的原有数据重新hash(经典hash一致性问题)

4. 分库分表问题

4.1. 垮库join操作

  • 在设计分库时要考虑表的关联关系,关联度高的分在同一个库,尽量避免夸库join操作
  • 当不可避免出现夸库操作,解决方案:
    • 服务层调用接口,需要注意怎样提高接口执行效率,比如使用多值接受而不是循环调用接口
    • 全局表(数据变更少的基于全局应用的表)
    • 适度的字段冗余。

4.2. 夸分片数据排序分页

  • 应用层设计

4.3. 唯一主键

  • UUID/GUID。缺点:太长,性能比较低
  • snowflake(雪花算法)
  • 借助数据库自增长id
  • 借助Redis的INCR/INCRBY原子操作

4.4. 分布式事务问题

  • 两阶段提交保证强一致性,效率低下。
  • 最终一致性方案。

5. 分库分表工具

分库分表可以在应用层处理,也可以使用一些代理工具实现。

  • 应用层处理
    • 实现增删改查时需要操作的库名/表名的逻辑
    • 垂直分表情况需要通过join方式或两次查表实现。
    • 分页、排序、函数处理等
  • 代理工具处理
    • sharding-jdbc(当当)
    • TSharding(蘑菇街)
    • Atlas(奇虎360)
    • Cobar(阿里巴巴)
    • MyCAT(基于Cobar)
    • Oceanus(58同城)
    • Vitess(谷歌)

6. 参考

数据库分库分表思路