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(谷歌)