java 分库分表分区总结

时间:2025-08-31 08:30:01来源:互联网

下面小编就为大家分享一篇java 分库分表分区总结,具有很好的参考价值,希望对大家有所帮助。

概念说明


读写分离: 不同的数据库,同步相同的数据,分别只负责数据的读和写;

分区:指定分区列表达式,把记录拆分到不同的区域中(必须是同一服务器,可以是不同硬盘),应用看来还是同一张表,没有变化;把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,分区实现比较简单,数据库mysql、oracle等很容易就可支持。

分库:一个系统的多张数据表,存储到多个数据库实例中;分为垂直分库和水平分库

垂直分库:根据业务耦合性,将关联度低的不同表存储在不同的数据库。


分表: 对于一张多行(记录)多列(字段)的二维数据表,又分两种情形:

垂直分表: 竖向切分,不同分表存储不同的字段,可以把不常用或者大容量、或者不同业务的字段拆分出去;
水平分表(最复杂): 横向切分,按照特定分片算法,不同分表存储不同的记录。

在实际生产中,通常的进化过程是:单库单表->单库多表->多库多表;;分区->分表->分库(垂直分库 - 水平分库 - 读写分离)

单库单表
单库单表是最常见的数据库设计,例如,有一张订单表(order)放在数据库中,所有的订单都可以在order表中查到。

单库多表
随着订单数量的增加,order表的数据量会越来越大,当数据量达到一定程度的时候,对order表的查询会变慢,从而影响整个DB的性能。

另外,随着需求的迭代,如果增加添加一列的时候,mysql会锁表,期间所有的读写操作只能等待,别无他法。

这时候,可以将order进行水平的切分,产生多个表结构完全一样的order表。比如:order_01,order_02....,order_n,那么order_01+order_02+order_n的数据是一份完整的订单数据。

水平切分,简单的做法如:
按数量切分,1~1000的存在第一张表,1001~2000存在第二张表;
按时间切分,比如:2019年1月份存在第一张表,2019年2月份存在第二张表;
按照id的哈希值进行切分;
根据数值取模,比如用户Id mod n,余数为0的记录放到第一张表,余数为1的放到第二张表;

多库多表
单台数据库的硬件存储不够,这时候就需要对数据库进行水平区分。比如按地区分库,一个省份在一个物理数据库等等
分库分表的优缺点分析
优点:
减小数据库的负担,提高数据库的效率,缩短查询时间。

缺点:
跨库join问题:分库分表后,表之间的关联操作将受到限制,无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。
横向扩容的问题

分库:
缺点:
1、分布式事务处理复杂


应用场景

一张表数据量过大,查询速度变得很慢,当旧的数据访问比较少可以进行分区
当单表的数据量达到1000W或100G以后,就要考虑分表,分库的话数量级再上一级
如果并发写很大,那就要分库,如果只是数据量大,写的频率并不是很大,那就分表吧,查询快。

分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master服务器无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。此时可以考虑分库。
海量数据和高并发考虑分库分表

分区表主要用作归档管理,多用于快递行业和电商行业订单表
按照日期年份分区,那么2013年的数据是历史数据不经常读,这样才行,如果是做数据分析的话,表分区就不太合适,如果经常夸分区读数的话,不但速度不快,反而变慢了

分库分表原则

能不切分尽量不要切分
分库分表之前,不要为分而分,先尽力去做力所能及的事情,例如:升级硬件、升级网络、读写分离、索引优化等等。当数据量达到单表的瓶颈时候,再考虑分库分表。
一般来说,Mysql 单库超过5000万条记录,Oracle单库超过1亿条记录,DB压力就很大
如果没有达到几百万,通常无需分库分表

2、如果一定要切分,尽量通过数据冗余或表分组来降低跨库 Join 的可能。对于现在市面上有好几种数据库中间件,这些中间件对数据 Join 实现,只能自己体会。业务读取尽量少使用多表 Join。
3、如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。也可以两种都用
5、具体分多少个库,要综合评估,一般初次分库建议分4-8个库。

分库分表中间件

基于代理方式的有MySQL Proxy和Amoeba,基于Hibernate框架的是Hibernate Shards,基于jdbc的有当当sharding-jdbc,基于mybatis的类似maven插件式的有蘑菇街的蘑菇街TSharding,通过重写spring的ibatis template类是Cobar Client
中间件地址:
简单易用的:
sharding-jdbc(当当):https://github.com/shardingjdbc;这近几年比较流行,比较牛逼。
TSharding(蘑菇街):https://github.com/baihui212/tsharding

重量级的:
TDDL Smart Client的方式(淘宝):https://github.com/alibaba/tb_tddl
alibaba.cobar(是阿里巴巴(B2B)部门开发):https://github.com/alibaba/cobar
Atlas(奇虎360):https://github.com/Qihoo360/Atlas
cobar(阿里巴巴):https://github.com/alibaba/cobar;缺点查询回来的数据没有排序,和分页,这些都要自己处理,用的少。
MyCAT(基于阿里的Cobar):http://www.mycat.io/;用的还算比较多
Oceanus(58同城):https://github.com/58code/Oceanus
Vitess(谷歌开发的):https://github.com/vitessio/vitess
OneProxy(支付宝首席架构师楼方鑫开发)

分区


官网分区说明:https://dev.mysql.com/doc/refman/8.0/en/partitioning.html
分区类型
mysql支持的分区类型包括Range、List、Hash、Key,其中Range比较常用:

  • RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
  • LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
  • HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。
  • KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。

分区限制

  • 主键或者唯一索引必须包含分区字段,如primary key (id,username),不过innoDB的大主键性能不好。
  • 很多时候,使用分区就不要在使用主键了,否则可能影响性能。
  • 每个表最多1024个分区,而且多分区会大量消耗内存。一般小于100个为好
  • 分区的表不支持外键,相关的逻辑约束需要使用程序来实现。
  • 分区后,可能会造成索引失效,需要验证分区可行性。

分区表不建议创建主键原因

因为建主键的同时会建一个唯一性的全局索引,在drop分区表时如果不指定update global indexes则 会使索引失效,导致数据无法入库。 
如果非要建主键,要2种方法: 1.应用上drop 分区表时显示指定update global indexes, 2.将主键上的索引建成本地索引 

分区表达式


分区的数据迁移
生产环境大多是采用这种方式:新建一个和原来表一样的分区表,然后把数据从原表导出,接着导入新表,最后建立普通索引。


分表

最简单的方式就是对字段取模分表。

分表字段

对于互联网企业来说,大部分数据都是与用户关联的,因此,用户id是最常用的分表字段。

分表数量

拆分后表的数量一般为2的n次方

分区和分表对比


https://www.jb51.net/article/83718.htm
1、实现方式上
a),mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完正的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构文件。
b),分区不一样,一张大表进行分区后,他还是一张表,不会变成二张表,但是他存放数据的区块变多了。

2、数据处理上
a),分表后,数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。
b),分区呢,不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表呢,还是一张表。数据处理还是由自己来完成。

3,提高性能上
a)分表后,单表的并发能力提高了,磁盘I/O性能也提高了。并发能力为什么提高了呢,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同的查询,将并发压力分到不同的小表里面。磁盘I/O性能怎么搞高了呢,本来一个非常大的.MYD文件现在也分摊到各个小表的.MYD中去了。
b)侧重磁盘I/O性能提升很多

4、实现的难易度上
a),分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式根分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。
b),分区实现是比较简单的,建立分区表,根建平常的表没什么区别,并且对开代码端来说是透明的。


分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。


分库分表

路由策略
一种分库分表的路由策略如下:
    1. 中间变量 = user_id % (分库数量 * 每个库的表数量)
    2. 库 = 取整数 (中间变量 / 每个库的表数量)
3. 表 = 中间变量 % 每个库的表数量

实例:
假设将原来的单库单表order拆分成256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对于user_id=262145 的访问,路由的计算过程如下:

1.  中间变量 = 262145 % (256 * 1024) = 1

2.  库 = 取整 (1/1024) = 0

3.  表 = 1 % 1024 = 1

这就意味着,对于user_id=262145 的订单记录的查询和修改,将被路由到第0个库的第1个order_1表中执行
问题详解

分布式事务

当进行分库(水平或垂直)操作后,会涉及到跨库执行SQL的问题,需要考虑分布式事务问题:
处理方式:
使用分布式事务中间件
使用MySQL自带的针对跨库的事务一致性方案(XA)。
缺点:性能要比单库的慢10倍左右
将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并通过应用程序来总控各个小事务。
缺点:如果使用 了spring的事务管理,改动起来会面临一定的困难。
跨库join

分库分表后,表之间的关联操作将受到限制,就无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。
简单的解决方法:

全局表:基础数据,可看做是"数据字典表",,就是系统中所有模块都可能依赖的一些表,可以将这类表在每个数据库中都保存一份。这些数据通常很少会进行修改,所以也不担心一致性的问题。

字段冗余:把需要join的字段冗余在各个表中,这样有些字段就不用join去查询了。
系统层组装:应用端先分别查询出所有复核条件的,然后在应用端组装起来,类似于一个mapreduce的过程(较复杂)。
分开查询:解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。

横向扩容
如果采用数值范围分片,只需要添加节点就可以进行扩容了,不需要对分片数据迁移。如果采用的是数值取模分片,则考虑后期的扩容问题就相对比较麻烦。

当我们使用哈希取模做分表的时候,针对数据量的递增,可能需要动态的增加表,此时就需要考虑数据迁移的问题。

原来使用的是hash后对8进行取模,那么,数据是均分在8个表(库)上。

如果8个表不够的时候,我们要扩展到16个表,这时候,我们hash后对16取模,新数据是没有问题的,旧数据就会发生错乱。

如果哈希后是9,那么,原来我们对8取模后,是1,会到表1进行查询;但是,现在我们是对16取模,那么是到表9进行查询的,而这个数据在表9又不存在,因此,就会找不到数据了

来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制

结果集合并、排序的问题
跨节点的count,order by,group by以及聚合函数问题

因为我们是将数据分散存储到不同的库、表里的,当我们查询指定数据列表时,数据来源于不同的子库或者子表,就必然会引发结果集合并、排序的问题。

如果每次查询都需要排序、合并等操作,性能肯定会受非常大的影响。

解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。

ID问题
一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由.
一些常见的主键生成策略

UUID
使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。

结合数据库维护一个Sequence表
此方案的思路也很简单,在数据库中建立一个Sequence表,表的结构类似于:

CREATE TABLE `SEQUENCE` (  
    `table_name` varchar(18) NOT NULL,  
    `nextid` bigint(20) NOT NULL,  
    PRIMARY KEY (`table_name`)  
) ENGINE=InnoDB
每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用Master-Slave进行主从同步,但这也只能解决单点问题,并不能解决读写比为1:1的访问压力问题。

Twitter的分布式自增ID算法Snowflake
在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。

* 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。

这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。

结合数据库和snowflake的唯一ID方案,可以参考业界较为成熟的解法:Leaf——美团点评分布式ID生成系统

跨分片的排序分页
当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。
因为各分片节点中的数据可能是随机的,为了排序的准确性,必须把所有分片节点的前N页数据都排序好后做合并,最后再进行整体的排序。很显然,这样的操作是比较消耗资源的,用户越往后翻页,系统性能将会越差。
那如何解决分库情况下的分页问题呢?有以下几种办法:

如果是在前台应用提供分页,则限定用户只能看前面n页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。

如果是后台批处理任务要求分批获取数据,则可以加大page size,比如每次获取5000条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。

分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。

可以利用es,把我们常用的搜索条件和排序字段都索引进去,这样我们先查询es,然后返回id(分表策略选取的id),然后再拿这个id去各个表中查询

数据迁移
一般做法是先读出历史数据,然后按指定的分片规则再将数据写入到各个分片节点中。
一般建议单个分片上的单表数据量不超过1000W


实战

分区实战

查看是否有支持Partition分区表:
SHOW PLUGINS ;

有它表示支持分区表
注意:MySQL 5.6.1 之前的版本,可以下命令查看 have_partitioning 参数,新的版本已移除该参数。 
mysql> SHOW VARIABLES LIKE '%partition%';

 

订单系统
订单系统使用分库分表的比较多
路由字段选择:
统计使用那个字段参与查询的频率最高,订单号/订单id/用户id
拆分策略选择:
实践中,为了处理简单,选择mod分库的比较多,同时二次分库时,为了数据迁移方便,一般是按倍数增加,比如初始4个库,二次分裂为8个,再16个。这样对于某个库的数据,一半数据移到新库,剩余不动
分库数量:
分库数量首先和单库能处理的记录数有关,一般来说,Mysql 单库超过5000万条记录,一般初次分库建议分4-8个库。
聚合查询
对于带聚合运算的多库查询,如带groupBy/orderby/min/max/avg等关键字,建议DAL汇总单个库返回的结果

本站部分内容转载自互联网,如果有网站内容侵犯了您的权益,可直接联系我们删除,感谢支持!