把一个实例中的多个数据库拆分到不同的实例
如果在数据库集群节点一承担的写压力无法承载,我们可以先该集群节点进行拆分,拆分后效果如右图所示
但如果拆分后,订单的节点一的写压力依然无法承载,这就需要进行把一个库中的表分离到不同的数据库中
但是随着业务的发展,数据量越来越庞大,我们就需要对单张表进行水平拆分到不同实例的数据库中。那么我们需要对数据库进行一个分片前到准备。这里需要注意的是,我们并不推荐在数据库刚刚上线的时候就进行分片处理,在数据库并发和负载远远没有达到限制时就开始分片处理,其实是完全没有必要的。我们应该首先考虑是否能够通过性能调优,应用和数据库设计来推迟分片。数据库分片后还会变的难以维护,除非不得不分片的时候,才去进行分片处理。
如何选择分区键
- 分区键要尽量避免跨分片查询到发生,在OLTP的应用中尽量如此。比如我们在开发博客系统中,可以考虑使用用户id来进行分片而不是文章id来分片,这样我们在查询单个用户的所有文章的时候就可以在单个分片中查询出该用户的所有文章。
- 分区键要能尽量使各个分片中的数据平均。对id取模时,分片数量使用素数,而不是合数,具体数量可以参考数据结构整理 中关于哈希的一章。
如何存储无需分片的表
- 每个分片中存储一份相同的表,产生冗余信息,便于关联查询的效率
- 使用额外的节点统一存储,冗余信息底,但关联查询效率上会比上一种方式慢。
如何在节点上部署分片
- 每个分片使用单一数据库,并且数据库名也相同。
- 将多个分片表存储在一个数据库中,并在表名上加入分片号后缀。
- 在一个节点中部署多个数据库,每个库包含一个分片。
如何分配分片中的数据
- 按分区键的Hash值取模来分配分片数据
- 按分区键的范围来分配分片数据
- 使用分区键和分片的映射表来分配分片数据
假设我们有一个table_data表,现在要将其分成5个分表table_data0、table_data1、table_data2、table_data3、table_data4
表内字段大致如下,id为主键
我们要使用的是shardingsphere的shardingjdbc模块,添加pom如下(该版本为Apache最新孵化版本)
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0-RC1</version> </dependency>
因为我使用的是mysql8的版本,配置文件如下
spring:
shardingsphere:
datasource:
names: ds0
ds0:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3306/database?useSSL=FALSE&serverTimezone=GMT%2B8
username: root
password: *****
type: com.alibaba.druid.pool.DruidDataSource
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
sharding:
tables:
table_data:
actual-data-nodes: ds0.table_data$->{0..4}
table-strategy:
inline:
sharding-column: id
algorithm-expression: table_data$->{id % 5}
以上配置中table_data为逻辑表,如果要修改成其他的逻辑表,一定要将tables下的table_data改成新的逻辑表。
在SpringBootApplication标签中添加如下值
@SpringBootApplication(exclude = JtaAutoConfiguration.class)
我们在mybatis的配置文件中添加一个批量插入
@Mapper public interface TableDataDao { int insert(List<TableData> tableDataList); }
<insert id="insert" parameterType="java.util.List"> insert into table_data (id,table_id,table_model_id,table_name,vehicle_id, vehicle_start_date,vehicle_brand,vehicle_no,vehicle_type,table_date,user_id, user_name,check_item_id,check_item_important,check_item_name,item_id, item_check_content,item_check_name,item_declared,item_ok,item_ok_url, item_problem_description,item_problem_url) values <foreach collection="list" item="item" index="index" separator=","> (#{item.id,jdbcType=BIGINT}, #{item.tableId,jdbcType=BIGINT}, #{item.tableModelId,jdbcType=BIGINT}, #{item.tableName,jdbcType=VARCHAR}, #{item.vehicleId,jdbcType=INTEGER}, #{item.vehicleStartDate,jdbcType=TIMESTAMP}, #{item.vehicleBrand,jdbcType=VARCHAR}, #{item.vehicleNo,jdbcType=VARCHAR}, #{item.vehicleType,jdbcType=VARCHAR}, #{item.tableDate,jdbcType=TIMESTAMP}, #{item.userId,jdbcType=BIGINT}, #{item.userName,jdbcType=VARCHAR}, #{item.checkItemId,jdbcType=INTEGER}, #{item.checkItemImportant,jdbcType=INTEGER}, #{item.checkItemName,jdbcType=VARCHAR}, #{item.itemId,jdbcType=INTEGER}, #{item.itemCheckContent,jdbcType=VARCHAR}, #{item.itemCheckName,jdbcType=VARCHAR}, #{item.itemDeclared,jdbcType=INTEGER}, #{item.itemOk,jdbcType=INTEGER}, #{item.itemOkUrl,jdbcType=VARCHAR}, #{item.itemProblemDescription,jdbcType=VARCHAR}, #{item.itemProblemUrl,jdbcType=VARCHAR}) </foreach> </insert>
以上批量插入的为逻辑表data_table
在Controller中对其进行插入
public void insertTableDataBatch(List<TableData> tableDataList) { tableDataDao.insert(tableDataList); }
测试如下
成功执行后,我们来查看各个分表
table_data0中如下
table_data1中如下
table_data2中如下
table_data3中如下
table_data4中如下
我们可见这些数据被很好的分配到了5张不同的表中,证明分表对批量插入有效。