为什么要分库分表
关系型数据库以MySQL为例,单机的存储能力、连接数是有限的,它自身就很容易会成为系统的瓶颈。当单表数据量在百万以里时,我们还可以通过添加从库、优化索引提升性能。一旦数据量朝着千万以上趋势增长,再怎么优化数据库,很多操作性能仍下降严重。为了减少数据库的负担,提升数据库响应速度,缩短查询时间,这时候就需要进行 分库分表
如何分库分表
分库分表就是要将大量数据分散到多个数据库中,使每个数据库中数据量小响应速度快,以此来提升数据库整体性能。核心理念就是对数据进行切分(Sharding ),以及切分后如何对数据的快速定位与整合
针对数据切分类型,大致可以分为:垂直(纵向)切分和水平(横向)切分两种
垂直切分
垂直切分又细分为垂直分库 和 垂直分表
垂直分库
垂直分库是基于业务分类的,和我们常听到的微服务治理观念很相似,每一个独立的服务都拥有自己的数据库,需要不同业务的数据需接口调用。而垂直分库也是按照业务分类进行划分,每个业务有独立数据库
垂直分表
垂直分表 是基于数据表的列为依据切分的,是一种大表拆小表的模式
数据库是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段长度也都较短,可以加载更多数据到内存中,增加查询的命中率,减少磁盘IO,以此来提升数据库性能
-
优点:
-
业务间解耦,不同业务的数据进行独立的维护、监控、扩展
-
在高并发场景下,一定程度上缓解了数据库的压力
-
-
缺点:
-
提升了开发的复杂度,由于业务的隔离性,很多表无法直接访问,必须通过接口方式聚合数据
-
分布式事务管理难度增加
-
数据库还是存在单表数据量过大的问题,并未根本上解决,需要配合水平切分
-
水平切分
水平切分将一张大数据量的表,切分成多个表结构相同,而每个表只占原表一部分数据,然后按不同的条件分散到多个数据库中
假如一张 order 表有2000万数据,水平切分后出来四个表,order_1 、order_2 、 order_3 、 order_4 ,每张表数据500万,以此类推
水平切分又分有库内分表和 分库分表
库内分表
库内分表虽然将表拆分,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分布到不同机器的库上,还在竞争同一个物理机的CPU、内存、网络IO
分库分表
分库分表则是将切分出来的子表,分散到不同的数据库中,从而使得单个表的数据量变小,达到分布式的效果
-
优点:
-
解决高并发时单库数据量过大的问题,提升系统稳定性和负载能力
-
业务系统改造的工作量不是很大
-
-
缺点:
-
跨分片的事务一致性难以保证
-
跨库的join关联查询性能较差
-
扩容的难度和维护量较大,(拆分成几千张子表想想都恐怖)
-
数据的分片策略
根据客户或租户进行切片
SaaS应用程序通常为许多客户(称为“租户”)服务,这就是为什么我们经常将这些应用程序称为“ 多租户应用程序”。如果您是SaaS业务,客户们的数据通常不会相互影响。而社交网络应用则与此不同,社交网络应用中不同用户生成的数据之间存在很多相互依存关系
使用多租户的SaaS应用程序,数据通常应该是事务性的。如果您丢失了一些数据,付费客户会抗议投诉。由于许多这些事务型多租户SaaS应用程序(比如:CRM软件,营销操作,网络分析)的性质,当数据保存到数据库时,您需要强有力的保证数据能确实保存下来了。客户希望您执行引用完整性
多租户应用程序另一个有趣的事情是,他们的数据模型随着时间的推移而发展,提供越来越多的功能。不同于C2C是受益于网络效应而扩展规模,B2B应用程序是通过为客户添加新的(粘性)功能而不断增长扩展。新功能意味着新的数据模型。最终的结果是从10个表到100个表,有时是复杂的join。如果这听起来和你一样,按租户分割是一种安全(推荐)的分片方法
按地理划分
可以通过手机app采集到数据的地理位置。但是,只是因为你有一个注重地理的应用程序,这并不意味着地理是正确的分片key。按区域划分的关键是您的特定地理位置中的数据不与另一个地理位置进行交互。并不是所有具有地理数据的应用程序进行地理分片是有道理的
如果应用将查询限制在某个地理位置,并且数据很少跨越地理边界,那么可以考虑按地理位置分片
时间划分
分片的最终方法是顺应某些应用程序自然而然的倾向。如果您正在处理时间为主轴的数据,则按日,周,小时,月份分区是正确的。查看某种形式的事件数据时,时间分割是非常常见的。事件数据可能包括广告的点击次数/展示次数,可能是网络事件数据,或者来自系统监控的数据
下面情况您将需要使用基于时间的方法进行分区:
- 通过对数据进行分析,以时间为横轴,生成报告/警报。
- 你会经常来回翻阅历史数据,因此在时间上你需要保留一定历史数据。
以时间维度进行数据分片还是有一些场景的应用场景的,例如:在页面只需要展示30天广告数据,或者仅需要查看最近7天的监控网络日志。如果在实现这些功能时,数据库中包含了一年前的数据那么就会对性能有很大的影响,此时需要根据时间维度对数据进行分片处理
根据取值范围
按照 时间区间 或ID区间 来切分,举个栗子:假如我们切分的是用户表,可以定义每个库的 User表 里只存100000条数据,第一个库userId 从1 ~ 9999,第二个库10000 ~ 19999,第三个库20000~ 29999......以此类推
-
优点:
-
单表数据量是可控的
-
水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移
-
能快速定位要查询的数据在哪个库
-
-
缺点:
- 由于连续分片可能存在数据热点,如果按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
hash取模
hash取模mod( 对hash结果取余数 (hash() mod N) )的切分方式比较常见,还拿User表 举例,对数据库从0 到N-1进行编号,对User表 中 userId 字段进行取模,得到余数i, i=0 存第一个库, i=1 存第二个库, i=2 存第三个库....以此类推
这样同一个用户的数据都会存在同一个库里,用userId 作为条件查询就很好定位了
-
优点:
- 数据分片相对比较均匀,不易出现某个库并发访问的问题
-
缺点:
- 但这种算法存在一些问题,当某一台机器宕机,本应该落在该数据库的请求就无法得到正确的处理,这时宕掉的实例会被踢出集群,此时算法变成hash(userId) mod N-1,用户信息可能就不再在同一个库中
分库分表工具
自己开发分库分表工具的工作量是巨大的,好在业界已经有了很多比较成熟的分库分表中间件,我们可以将更多的时间放在业务实现上
- sharding-jdbc(当当)
- TSharding(蘑菇街)
- Atlas(奇虎360)
- Cobar(阿里巴巴)
- MyCAT(基于Cobar)
- Oceanus(58同城)
- Vitess(谷歌)