分库分表示例
为什么要分库分表
当数据库中的数据达到一定数量的时候(尤其是单张表的数据达到百万千万级别的时候),会影响sql的性能,查询速度降低。这时如果想保持sql的性能,就需要进行分库分表。
分库
对于数据库来说,当单库的并发量达到一定量级时,查询的效率就会下降,机器的压力也会增大。而分库,就是把单库的数据,拆分到多个库中,分散一个库的并发量和数据量。
分表
对于单表来说,当单表的数据量太大时,sql的性能会大幅下降。这个数量根据数据表的复杂程度,在百万到千万之间。分表就是把单表中的数据,拆分到多个表中,每个表保存一部分数据,使单表的数据量在可控的范围内。
分库分表中间件
Sharding-jdbc
client层的分库分表方案,需要与代码耦合。但是代码可以由自己控制。
Mycat
proxy层的分库分表方案,不需要侵入代码,但是需要单独部署中间件。且中间件代码不易控制。
分库分表方案(以Sharding-jdbc为例)
示例以拆分一个order订单表为例,将order表拆分到4个库中,每个库中有4张表,一共16张表。
对于库表的拆分依据,首先分析系统中的业务。对于客户端的查询来说,通常都与用户的userId相关。所以,我们把userId对4取模,以定位到某个库的某张表。这样,令同一用户的所有单据都落到同一个库中,在查询的时候,路由到具体的数据库,保证了查询的性能。
除此之外,也可以用orderId作为分片依据。我们在里面混入了userId作为分库基因,前一部分是orderId,后一部分是userId(关于orderId分片这种方法,可以查看另一篇文章)。这样,当用orderId进行查询的时候,也能从中解析出userId,以路由到确定的库。
主键id问题
分库分表以后,原本一个表的数据被分散到多个表中。如何让数据拥有唯一的主键id呢。这里介绍一种id的生成方案:雪花算法。
雪花算法是一个long类型的数组。它由42时间戳+10位节点标识+12位避免并发的数字组成。这样保证了它的有序性,并且在高并发下有高性能。在本例中使用的时候,对于算法中的max-vibration-offset最大抖动上限值,需要根据分片数量设置为2^n-1(2^n为分片数)。否则其默认值为1,对2^n取模后总为0或1,造成数据分散不均匀。
分库分表示例
首先在yml配置文件中添加数据源。
spring:
shardingsphere:
datasource:
names: order0,order1,order2,order3
order0:
url: jdbc:mysql://localhost:3306/ds_order_0?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: "root"
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
order1:
url: jdbc:mysql://localhost:3306/ds_order_1?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: "root"
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
order2:
url: jdbc:mysql://localhost:3306/ds_order_2?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: "root"
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
order3:
url: jdbc:mysql://localhost:3306/ds_order_3?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: "root"
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
接着,在数据源配置加载类中,加载数据源,并设置分片规则。
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.shardingsphere.datasource.order0")
public DataSource orderDataSource0() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.shardingsphere.datasource.order1")
public DataSource orderDataSource1() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.shardingsphere.datasource.order2")
public DataSource orderDataSource2() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.shardingsphere.datasource.order3")
public DataSource orderDataSource3() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource DataSource(@Autowired @Qualifier("orderDataSource0") DataSource orderDataSource0,
@Autowired @Qualifier("orderDataSource1") DataSource orderDataSource1,
@Autowired @Qualifier("orderDataSource2") DataSource orderDataSource2,
@Autowired @Qualifier("orderDataSource3") DataSource orderDataSource3) throws SQLException {
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("order0", orderDataSource0);
dataSourceMap.put("order1", orderDataSource1);
dataSourceMap.put("order2", orderDataSource2);
dataSourceMap.put("order3", orderDataSource3);
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("tb_order", "order${0..3}.tb_order_${0..3}");
// 配置分库 + 分表策略
orderTableRuleConfig.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id", new DbShardingAlgorithm()));
orderTableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id", new TbShardingAlgorithm()));
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
return dataSource;
}
}
其余的实体类、mapper、service、controller层代码与不分表的写法一致,这里不再讲解,具体可查看示例源码。
最后,调用controller层的rest接口新增数据和查询数据,功能正常运行。