Java如何实现分库分表

article/2025/10/27 16:28:33

一、为啥要分库分表

在大型互联网系统中,大部分都会选择mysql作为业务数据存储。一般来说,mysql单表行数超过500万行或者单表容量超过2GB,查询效率就会随着数据量的增长而下降。这个时候,就需要对表进行拆分。

那么应该怎么拆分呢?

通常有两种拆分方法,垂直拆分和水平拆分。

先说垂直拆分,这个比较简单,我们可以把原先的一张表根据业务属性拆分成多张表。比如用户表user有很多字段,我们可以新建一张用户属性表user_profile,把一些不常用的字段都拆分到user_profile表里,再用user_id作为外键将两张表关联起来就可以了。

再说水平拆分,水平拆分针对的不是表,而是数据。比如订单表,数据量一般都会非常大。我们可以创建多个数据库实例,每个实例上创建多张订单表,把订单数据相对均匀的分散存储到这些表里。查询的时候,根据分表策略可直接定位到数据在哪个表里,可以大大提高查询效率。

下面讲到的都是如何水平拆分。

二、怎么做分库分表

分库分表已经有一些成熟的解决方案,本文是用ShardingSphere-JDBC框架来实现的。

1.什么是ShardingSphere-JDBC

ShardingSphere-JDBC定义为轻量级Java框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

更多详细内容可直接参考:ShardingSphere官方文档

2.ShardingSphere-JDBC分表实践

ShardingSphere-JDBC分库和分表配置类似,下面介绍下分表怎么实现。

(1)先建分表

先在mysql数据库建10张用户表:tb_user_0到9,建表语句如下,改下表名,执行10遍即可:

CREATE TABLE `tb_user_0`  (`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',`sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)POM依赖

使用spring boot + mybatis-plus + shardingsphere-jdbc来实现,pom主要引入的包配置如下:

  <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId><version>5.0.0</version></dependency>

(3)实体类和Mapper代码

注意,实体类和Mapper只有一个就行,注意这里的tableName注解一定要和后面配置分表策略的逻辑名一致,不然无法匹配路由策略。

@TableName(value = "tb_user")
public class User implements Serializable {private static final long serialVersionUID = 1L;/*** 主键,注意此处IdType必须是AUTO,不然框架就会自动生成id,分表时生成id的策略就不生效了*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 姓名*/@TableField(value = "name")private String name;/*** 性别*/@TableField(value = "sex")private String sex;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}@Overridepublic String toString() {return "User{" +"id=" + id +", name=" + name +", sex=" + sex +"}";}
}
public interface UserMapper extends BaseMapper<User> {}

(4)配置数据源和分表规则

我们引入的包是shardingsphere-jdbc-core-spring-boot-starter,直接在application.yml里配置数据源和分表规则就行。

spring:shardingsphere:datasource:# 数据源名称,有几个数据源就写几个,如果是分表,就会写多个names: db0# 为每个数据源单独配置,注意这里要跟上面写的名称一致db0:# 数据库连接池实现类型,这里使用的是Hikaritype: com.zaxxer.hikari.HikariDataSource# 数据库驱动类,连接地址,用户名,密码等driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/sharding?useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456rules:sharding:tables:# 分表的表名,程序中对这张表的操作,都会采用下面的路由方案tb_user:# 这里是实际的数据节点信息,要把库名和表名都写全,这里也支持使用表达式,比如下面这张$->{0..9}actual-data-nodes: db0.tb_user_$->{0..9}# 配置分表策略table-strategy:# 这里选择的标准策略,也可以配置复杂策略,或者也可以用代码来实现standard:# 分片字段,这里是用用户id作为分片字段sharding-column: id# 这里是我们自定义的分片算法名称,后面会有实现方案sharding-algorithm-name: user-inline# 主键生成策略key-generate-strategy:# 生成主键算法的名称key-generator-name: snowflake# 主键字段column: id# 自定义的主键算法key-generators:snowflake:# 使用雪花算法生成主键type: SNOWFLAKE# 自定义的分表算法sharding-algorithms:user-inline:#使用inline类型实现type: inlineprops:#分片表达式,用id对10取模,然后分散到10个表中algorithm-expression: tb_user_$->{id % 10}props:# 打印日志,方便我们观察执行的sql语句sql-show: true

(5)写单测

先测试插入语句,如下插入100条数据:

@Autowiredprivate UserMapper userMapper;@Testpublic void insertTest() {for (int i=0; i<100; i++) {User user = new User();user.setName("test" + i);user.setSex("男");userMapper.insert(user);}}

执行之后,发现每张表都有数据插入,但是分布并不均匀,这是由雪花算法特性导致的。下图是tb_user_0表的数据:

再测试下查询语句,先测试用id查询:

@Testpublic void selectByIdTest() {userMapper.selectById(1668501944537858050L);}

查询sql语句如下图,从图中可以看出,根据id查询的时候,会自动走分表路由策略,查询id为1668501944537858050L的数据,会自动去tb_user_table_0中查找。

再测试一下根据name字段查询:

    @Testpublic void selectByNameTest() {QueryWrapper<User> qy = new QueryWrapper<>();qy.eq("name","test1");userMapper.selectList(qy);}

查询sql语句如下图,从图中可以看出,如果不是根据分表字段来查询的话,会自动union所有分表查询,这样反而效率会更低。

所以,分库分表时一定要选择合适的字段,并且查询的时候尽量要在查询条件里先指定分库分表的字段,这样可以直接定位到表中,提高查询效率。

3.ShardingSphere-JDBC自定义分表策略类

ShardingSphere-JDBC可支持多种分片算法,比如标准分片,复合分片等,每种分片算法有多种类型,如行表达式INLINE,时间范围分片INTERVAL等,上面的例子我们就是用的标准分片行表达式做的。对于一些需要自定义的分片算法,我们可以通过自定义分片算法类来实现。

比如我们还是要实现取模算法,可以自定义一个UserShardingAlgorithm类来实现StandardShardingAlgorithm接口,实现doSharding接口来自定义分片算法,代码如下:

//分片字段数据类型是什么,这里泛型就写什么
public class UserShardingAlgorithm implements StandardShardingAlgorithm<Long> {//精确分片算法实现,collection是实际表,也就是配置文件里的actual-data-nodes内容//preciseShardingValue对象包括逻辑表名,分表算法的字段和字段值@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {//对分片字段也就是用户id取模String suffix = String.valueOf(preciseShardingValue.getValue() % 10);//遍历表名,找到符合要求的表,返回即可for (String tableName : collection) {if (tableName.endsWith(suffix)) {return tableName;}}throw new UnsupportedOperationException();}//范围分片,我们暂不支持@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {throw new UnsupportedOperationException();}//初始化信息接口@Overridepublic void init() {}//分片算法类型@Overridepublic String getType() {return "USER_SHARDING";}
}

在配置文件里,我们只需要改一下分片算法部分的配置即可,之前的配置是这样的:

        sharding-algorithms:user-inline:type: inlineprops:algorithm-expression: tb_user_$->{id % 10}

分片类型改成class_based,也就是自定义类分片算法,配置如下:

        sharding-algorithms:user-inline:type: class_based # 自定义类分片算法类型props:strategy: standard# 自定义算法类的路径algorithmClassName: com.github.learn.sharding.algorithm.UserShardingAlgorithm

还是再跑一下上面selectById单测,如下图,可以顺利去tb_user_0中查询数据,证明我们自定义的分片算法生效了:

4.主键生成策略

ShardingSphere-JDBC提供了两种内置的分布式主键生成器,uuid和雪花算法。

uuid:采用UUID.randomUUID()的方式产生分布式主键。

雪花算法:

雪花算法是由 Twitter 公布的分布式主键生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。

(1)实现原理

在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。 同时由于时间位是单调递增的,且各个服务器如果大体做了时间同步,那么生成的主键在分布式环境可以认为是总体有序的,这就保证了对索引字段的插入的高效性。 例如 MySQL 的 Innodb 存储引擎的主键。

使用雪花算法生成的主键,二进制表示形式包含 4 部分,从高位到低位分表为:1bit 符号位、41bit 时间戳位、10bit 工作进程位以及 12bit 序列号位。

  • 符号位(1bit)

预留的符号位,恒为零。

  • 时间戳位(41bit)

41 位的时间戳可以容纳的毫秒数是 2 的 41 次幂,一年所使用的毫秒数是:365 * 24 * 60 * 60 * 1000。 通过计算可知:

  1. Math.pow(2,41)/(365*24*60*60*1000L);

结果约等于 69.73 年。 Apache ShardingSphere 的雪花算法的时间纪元从 2016年11月1日 零点开始,可以使用到 2086 年,相信能满足绝大部分系统的要求。

  • 工作进程位(10bit)

该标志在 Java 进程内是唯一的,如果是分布式应用部署应保证每个工作进程的 id 是不同的。 该值默认为 0,可通过属性设置。

  • 序列号位(12bit)

该序列是用来在同一个毫秒内生成不同的 ID。如果在这个毫秒内生成的数量超过 4096 (2 的 12 次幂),那么生成器会等待到下个毫秒继续生成。

雪花算法主键的详细结构见下图。

(2)配置信息

在ShardingSphere-JDBC中,雪花算法提供了三个属性。

worker-id:工作机器唯一标识

max-vibration-offset:最大抖动上限值,范围[0, 4096)。注:若使用此算法生成值作分片值,建议配置此属性。此算法在不同毫秒内所生成的 key 取模 2^n (2^n一般为分库或分表数) 之后结果总为 0 或 1。为防止上述分片问题,建议将此属性值配置为 (2^n)-1。如果有10个分表,可将此值设置为9,这样数据分布会更均匀一下。

max-tolerate-time-difference-milliseconds:最大容忍时钟回退时间,单位:毫秒,默认10毫秒

(3)多节点worker-id配置

服务器可能是有多个节点的,此时如果worker-id用同一个配置,有可能会产生重复的id,因此每个节点的worker-id最好是不同的。我们可以用ip地址的一部分来作为节点的worker-id,worker-id是十位,我们直接取ip地址的后10位即可,一般都是不会重复的。比如机器的IP为192.168.1.108,二进制表示:11000000 10101000 00000001 01101100,截取最后10位 01 01101100,转为十进制364,设置workerId为364。

实现方式如下:

首先是配置文件,要加入work-id属性配置:

        key-generators:user-id-generator:type: SNOWFLAKEprops:max-vibration-offset: 9worker-id: ${workerId}

然后,加一个配置类,在static代码块中获取ip地址,取后十位,作为worker-id。

@Configuration
public class WorkerIdConfig {private static final Logger LOGGER = LoggerFactory.getLogger(WorkerIdConfig.class);static {try {InetAddress address = InetAddress.getLocalHost();// IP地址byte[]数组形式,这个byte数组的长度是4,数组0~3下标对应的值分别是192,168,1,108byte[] ipAddressByteArray = address.getAddress();// workerId取ip地址后十位long workerId = ((ipAddressByteArray[ipAddressByteArray.length - 2] & 0x03) << 8) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF);LOGGER.info("当前机器 workerId: {}", workerId);System.setProperty("workerId", String.valueOf(workerId));} catch (Exception e) {LOGGER.error("worker id failed:{}", e.getMessage(), e);}}
}


http://chatgpt.dhexx.cn/article/QA332th1.shtml

相关文章

mysql 垂直分表 设计_水平分表和垂直分表

一、数据库瓶颈 不管是IO瓶颈&#xff0c;还是CPU瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、…

ShardingSphere分库分表

ShardingSphere是一款起源于当当网内部的应用框架。2015年在当当网内部诞生&#xff0c;最初就叫ShardingJDBC。2016年的时候&#xff0c;由其中一个主要的开发人员张亮&#xff0c;带入到京东数科&#xff0c;组件团队继续开发。在国内历经了当当网、电信翼支付、京东数科等多…

mysql innodb分表技术_mysql分表技术

一般来说&#xff0c;当我们的数据库的数据超过了100w记录的时候就应该考虑分表或者分区了&#xff0c;这次我来详细说说分表的一些方法。 目前我所知道的方法都是MYISAM的&#xff0c;INNODB如何做分表并且保留事务和外键&#xff0c;我还不是很了解。 首先&#xff0c;我们需…

mybatis实现分表

分析上面登陆&#xff0c;当前表按照年份分表了&#xff0c;最大的一张表超过500w建议分表 注意:之前写的经过多方测试发现遍历的时候参数传递不进去&#xff0c;现如今已经完善 package org.jeecg.config.mybatis;import lombok.extern.slf4j.Slf4j; import org.apache.ibati…

mysql 分表条件_mysql分表详解

本人混迹qq群2年多了&#xff0c;经常听到有人说“数据表太大了&#xff0c;需要分表”&#xff0c;“xxxx了&#xff0c;要分表”的言论&#xff0c;那么&#xff0c;到底为什么要分表&#xff1f; 难道数据量大就要分表&#xff1f; mysql数据量对索引的影响 本人mysql版本为…

ShardingSphere简介与分表使用

一、ShardingSphere简介 1、简介 ShardingSphere 已于 2020 年 4 月 16 日成为 Apache 软件基金会的顶级项目。 ShardingSphere 是一套开源的分布式数据库中间件解决方案。 ShardingSphere 产品定位为 Database Plus&#xff0c;旨在构建异构数据库上层的标准和生态圈。 它…

mysql mybatis分表查询_mybatis 自动分表

参考: 相关源码已上传至我的 github 欢迎转载,转载请注明出处,尊重作者劳动成果:https://www.cnblogs.com/li-mzx/p/9963312.html 前言 小弟才疏学浅,可能很多问题也没有考虑到,权当抛砖引玉,希望各位大神指点 项目背景: 希望做一个功能,能在sql操作数据库时,根据某个…

mysql 分区分表_mysql分库分区分表

一、分表 分表分为水平分表和垂直分表。 水平分表原理&#xff1a; 分表策略通常是用户ID取模&#xff0c;如果不是整数&#xff0c;可以首先将其进行hash获取到整。 水平分表遇到的问题&#xff1a; 1. 跨表直接连接查询无法进行 2. 我们需要统计数据的时候 3. 如果数据…

Mock平台介绍

Mock平台可以用来模拟接口&#xff0c;具备了get方法&#xff0c;post方法&#xff0c;header&#xff0c;cookie&#xff0c;重定向等功能。 Mock平台的搭建应用于mock框架&#xff0c;在github上可以下载到开源的代码。 下载地址&#xff1a;http://repo1.maven.org/maven2/…

Mock 框架 Moq 的使用

Intro# Moq 是 .NET 中一个很流vb.net教程行的 Mock 框架&#xff0c;使c#教程用 Mock 框架我python基础教程们可以只针对我java基础教程们关注的代码进行测试&#xff0c;对于sql教程依赖项使用 Mock 对象配置预期的依赖服务的行为。 Moq 是基于 Castle 的动态代理来实现的&…

mockjs入门

mockjs 1,mock.js是什么&#xff1f; mockjs是生成随机数据的一款前端工具&#xff0c;用来模拟 Ajax 请求&#xff0c;生成并返回模拟数据 2&#xff0c;为什么用mockjs&#xff1f; 当程序员做项目开发时&#xff0c;前端工程师要请求后端做好的数据时&#xff0c;有可能…

monkey简介

https://blog.csdn.net/lebang08/article/details/70858532 https://www.cnblogs.com/aland-1415/p/6949964.html https://blog.csdn.net/aisemi/article/details/55254348 一、Monkey 简介 monkey是Android SDK中自带的一个命令行工具&#xff0c;使用Java语言写成&#xf…

mokey的介绍和使用

一、monkey介绍 monkey是Android SDK提供的一个命令行工具&#xff0c;可以简单方便的发送伪随机的用户时间流&#xff0c;对Android APP做压力&#xff08;稳定性、健壮性&#xff09;测试。主要是为了测试APP是否存在无响应和崩溃的情况。 二、monkey的使用 1、前提条件&a…

APP测试— 测试工具mokey

文章目录 1 Mokey概念2 运行Monkey&#xff08;对手机进行300次无规律点击&#xff09;3 Mokey常规参数4 Monkey 事件类参数5 Monkey 约束类参数 1 Mokey概念 1&#xff09;Monkey是Android SDK提供的一个命令行工具&#xff0c;可以简单、方便的运行任何版本的Android模拟器和…

Kafka配置用户名密码访问

1 软件版本 kafka_2.12-2.4.0.tgz&#xff08;带zookeeper&#xff09; 2 kafka服务端部署 2.1 将安装包上传到服务器&#xff0c;并解压 tar zxvf kafka_2.12-2.4.0.tgz -C /datamv kafka_2.12-2.4.0 kafka2.2 修改kafka配置文件 server.properties vim /data/kafka/conf…

linux 用户名和密码的处理

1. 创建新用户和密码 # 创建用户 testuser useradd testuser# 给已创建的用户testuser设置密码 passwd testuser# 新创建的用户会在 /home 下创建一个用户目录testuser# 修改用户这个命令的相关参数 usermod --help# 删除用户testuser userdel testuser# 删除用户所在目录rm -…

用户名,密码登录

1.导入项目需要的依赖&#xff0c;分层 注意&#xff1a;如果你的数据库是5.5的版本&#xff0c;依赖要用低版本的&#xff0c;高版本不稳定&#xff0c;新增的内容不识别&#xff0c;会报各种各样奇葩的错误 2.创建实体类 它的属性要和数据库字段对应 package com.oa.entity…

实现用户输入用户名和密码登录

题目 实现用户输入用户名和密码登录&#xff0c;当用户名为admin或administrator且密码为666666时&#xff0c;显示“登录成功”&#xff0c;否则显示“登录失败”&#xff0c;登录失败时允许重复输入三次。 实例 参考程序 User1 "admin" User2 "administr…

计算机用户名和初始密码,电脑默认的用户名和密码是多少

优质回答 回答者&#xff1a;止树2018 电脑用户默认是没有密码的&#xff0c;除非你设置了&#xff0c;没有设置的前提下&#xff0c;直接按回车键就可以进系统了。 电脑默认的用户是administrator&#xff0c;如果你创建了自己的新用户名&#xff0c;那么&#xff0c;原始管理…

服务器密码以及用户名怎么修改

服务器密码以及用户名怎么修改 我是艾西&#xff0c;今天给大家说下服务器密码如何修改 windows2003系统&#xff1a; 1、右键我的电脑&#xff0c;点击“管理”&#xff1a; 2、在“本地用户和组”中打开“用户”&#xff0c;在右侧找到 Administrator 账户进行修改。 200…