1.为什么要使用分库分表:
分表前提:当单表数据量太大,会极大的影响sql的执行性能,这时sql会跑的很慢。当单表到达几百万的时候,性能就会有所下降。
分库前提:单库而言,最大的并发可能就两三千左右,但是一个单库来说最好的并发保持在1000左右,当单数据库增大或者并发增加的时候,可以将一个库的数据拆分到多个库中。
2.什么时候进行分库分表:
参考《阿里巴巴Java开发手册(公开版).pdf》建表规约,推荐单表行数设计时,预估三年内超过500万行或者单表容量超过2GB,才推荐进行分库分表
3.数据库如何实现分库分表:
两种方案:垂直拆分和水平拆分
指的是按照业务对数据库中的表进行分组,同组的放到一个新的数据库中。需要从实际业务出发将大业务分割成小业务。比如商城的整个业务组中的用户相关表,订单表,物流表分别各自分类形成 用户系统数据库,订单系统数据库,物流系统数据库。垂直拆分就是把数据的列分成多份,也就是把表的”宽“分成多份存储。
在数据库垂直拆分后 遇到单机数据库性能瓶颈之后,就可以考虑数据库水平拆分了。之所以先垂直拆分是因为垂直拆分后数据业务清晰而单一,更加方便指定水平拆分的标准。水平拆分就是把数据的行数量进行拆分,也就是数据“高度"分成多份存储,以达到水平拆分的目的。
本文章使用水平拆分
在介绍Sharding-JDBC之前,有必要先介绍下Sharding-JDBC的大家族ShardingSphere。在介绍ShardingSphere之后,相信大家会对ShardingSphere的整体架构以及Sharding-JDBC扮演的角色会有更深的了解。
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
同样可以做分库分表,mycat提供的能力一样的可以做到和sharding-jdbc大部分相同的功能,为什么不选mycat而选择sharding-jdbc,之所以选择sharding-jdbc是因为考虑到sharding作为一个apache顶级项目,目前开源社区文档多,文档多就更容易上手些,在做项目时我觉得方便配置,且更加直观,我可以知道sharding究竟做了什么,怎么查的数据等等
<dependencies>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-jdbc-spring-boot-starterartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>3.3.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>2.3.12.RELEASEversion>
<scope>testscope>
dependency>
dependencies>
server:
port: 10000
servlet:
context-path: /
spring:
profiles:
active: sharding
datasource:
dynamic: # 未分库分表的动态数据源配置
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/base_user_info?zeroDateTimeBehavior=convertToNull&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
# 配置Mybatis日志,显示执行的SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 打印日志级别设置
logging:
level:
com.test: debug
spring:
shardingsphere:
datasource:
names: sharding-clock-system
sharding-clock-system:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
# 正常的连接数据库
url: jdbc:mysql://127.0.0.1:3306/base_user_info?zeroDateTimeBehavior=convertToNull&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
props:
# 日志显示SQL
sql.show: true
sharding:
tables:
# 打卡记录 分表:10张表
t_user_clock_in_item:
key-generator:
# 指定绑定的主键
column: id
# 指定主键生成策略, 雪花算法
type: SNOWFLAKE
# 真实表 t_user_clock_in_item_0, 包含0, 包含9
actual-data-nodes: sharding-clock-system.t_user_clock_in_item_$->{0..9}
# 分库策略
databaseStrategy:
none:
# 分表策略
table-strategy:
inline:
# 绑定分表主键(主键不建议使用雪花算法, 主键使用自增以免浪费mysql页空间和产生碎片空间, 这里只是为了学习)
sharding-column: id
# 分片算法行表达式,需符合groovy语法 '& Integer.MAX_VALUE' 位运算使hash值为正数, 取模运算
# algorithm-expression: t_user_clock_in_item_$->{(id.hashCode() & Integer.MAX_VALUE) % 9}
# 获取id取模范围为0-10, 包含0, 不包含10
algorithm-expression: t_user_clock_in_item_$->{id % 10}
CREATE TABLE `t_user_clock_in_item_0` (
`id` bigint(20) NOT NULL,
`user_id` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL,
`user_clock_in_id` int(11) DEFAULT NULL,
`book_id` int(11) DEFAULT NULL,
`word_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='';
/**
* 动态数据源配置:
*
* 使用{@link com.baomidou.dynamic.datasource.annotation.DS}注解,切换数据源
*
* @DS(DataSourceConfiguration.SHARDING_DATA_SOURCE_NAME)
*
* @author songyinyin
* @date 2020/7/27 15:19
*/
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class,
SpringBootConfiguration.class})
public class DataSourceConfiguration {
/**
* 分表数据源名称
*/
private static final String SHARDING_DATA_SOURCE_NAME = "sharding-system-datasource";
/**
* 动态数据源配置项
*/
@Resource
private DynamicDataSourceProperties properties;
/**
* shardingjdbc有四种数据源,需要根据业务注入不同的数据源
*
* 1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
*
2. 主从数据源: masterSlaveDataSource;
*
3. 脱敏数据源:encryptDataSource;
*
4. 影子数据源:shadowDataSource
*/
@Lazy
@Resource(name = "shardingDataSource")
AbstractDataSourceAdapter shardingDataSource;
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
final Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
return new AbstractDataSourceProvider() {
@Override
public Map<String, DataSource> loadDataSources() {
Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);
// 将 shardingjdbc 管理的数据源也交给动态数据源管理
dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource);
return dataSourceMap;
}
};
}
/**
* 将动态数据源设置为首选的
* 当spring存在多个数据源时, 自动注入的是首选的对象
* 设置为主要的数据源之后,就可以支持shardingjdbc原生的配置方式了
*
* @return
*/
@Primary
@Bean
public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setProvider(dynamicDataSourceProvider);
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
}
/**
* @author :seagull
* @date :Created in 2022/10/30
* @description:
* @modified By:
*/
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@MapperScan("com.test.sharding.mapper")
public class ShardingApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ShardingApplication.class, args);
String port = context.getEnvironment().getProperty("server.port");
System.out.println("port = " + port);
}
}
通过使用mybatis的注解,直接在代码中写sql进行使用
/**
* 使用sharding分表数据源
* @author :seagull
* @date :Created in 2022/11/1
* @description:
* @modified By:
*/
@Mapper
@Component
@DS("sharding-system-datasource")
public interface UserClockInItemMapper {
@Insert("insert into t_user_clock_in_item(user_id, user_clock_in_id, book_id, word_id) values(#{userId}, #{userClockInId}, #{bookId}, #{wordId})")
int insertClockItem(@Param("userId") String userId,
@Param("userClockInId") String userClockInId,
@Param("bookId") String bookId,
@Param("wordId") String wordId);
@Select("select t.* from t_user_clock_in_item t")
List<Map> list();
}
/**
* 使用普通数据源
* @author :seagull
* @date :Created in 2022/11/1
* @description:
* @modified By:
*/
@Mapper
@Component
public interface UserClockInMapper {
@Select("select t.* from t_user_clock_in t")
List<Map> list();
}
@ExtendWith(SpringExtension.class)
@SpringBootTest
class UserClockInItemMapperTest {
@Resource
private UserClockInItemMapper userClockInItemMapper;
@Resource
private UserClockInMapper userClockInMapper;
@Test
void testShardingInsert() {
for (int i = 0; i < 1000; i++) {
String userId = "12312";
String userClockInId = "12312";
String bookId = "12312";
String wordId = "12312";
userClockInItemMapper.insertClockItem(userId, userClockInId, bookId, wordId);
}
}
@Test
void testShardingSelect() {
List<Map> list = userClockInItemMapper.list();
System.out.println(list.size());
list.forEach(System.out::println);
System.out.println("ok,切换普通数据源查询");
List<Map> list2 = userClockInMapper.list();
System.out.println("list2.size() = " + list2.size());
list2.forEach(System.out::println);
}
}
通过启动测试,可以看到数据成功插入与读取,从打印的sql来看,使用sharding查询分表时,虽然查询的是一个逻辑表,但是实际上sharding-jdbc在查询时实际上自动会去实际的表中查询,最后再通过sharding-jdbc去整合查询结果
参考: