Sharding-JDBC之单库分表

#Sharding-JDBC之单库分表

##应用场景
最近的应用场景中,遇到了单表数据量太大,影响效率,分表的情况。所以就出现了“单库分表”这个需求。一开始我是自己写的工具类,但是这样业务代码就不简洁,每次CRUD操作之前都要自己计算表名。更严重的问题是我没有考虑到在关联表中主键重复这个问题,会导致业务异常 公司里有用过sharding-jdbc的,反映还不错,所以我就开始了踩坑之路。

##sharding-jdbc
sharding-jdbc可以保证全局主键唯一。
sharding-jdbc表的分片策略有四种,我的情况是根据两个字段分表,也就是多分片键,所以要使用complex模式,这个比标准的分片场景稍微复杂一点,要自己写分片策略。

###1.引入依赖

io.shardingsphere
sharding-jdbc-spring-boot-starter
3.1.2

###2.数据库、分表配置

···

application-test.yml 测试环境DB配置

sharding:
jdbc:
datasource: #配置数据源
user:
url:
username:
password:
config:
sharding:
props:
sql:
show: true #打印SQL

application.yml 公共配置

sharding:
  jdbc:
    datasource:
      names: user #数据源
      user:#数据库配置
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        initial-size: 5
        min-idle: 5
        max-active: 100
        max-wait: 10000
        validation-query: SELECT 1 FROM DUAL
        test-on-borrow: false
        test-on-return: false
        test-while-idle: true
        time-between-eviction-runs-millis: 30000
        min-evictable-idle-time-millis: 30000
    config:
      sharding:
        tables:#表的配置
          user_base_info:#逻辑表名
            actual-data-nodes: user.user_base_info$->{2019..2022}_0$->{0..7}#真实的表名
            table-strategy.complex.sharding-columns: finance_year,hash_code#分表字段
            table-strategy.complex.algorithm-class-name: #自定义分表策略实现类,要实现ComplexKeysShardingAlgorithm接口
            key-generator-column-name: id#主键
        default-data-source-name: user #默认数据库



import io.shardingsphere.api.algorithm.sharding.ListShardingValue;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
 
 
public class WalletComplexKeysShardingAlgorithmImpl implements ComplexKeysShardingAlgorithm {
 
    private static String logicTableName = "";
 
    /**
     * 自定义分片策略
     * @param collection    实际表名集合
     * @param shardingValues    分片键集合
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection, Collection<ShardingValue> shardingValues) {
        Collection<Integer> financeYearValues = getShardingValue(shardingValues, FINANCE_YEAR);
        Collection<Integer> hashCodeValues = getShardingValue(shardingValues, HASH_CODE);
 
        List<String> shardingSuffix = new ArrayList<>();
 
        for (Integer financeYear : financeYearValues) {
            for (Integer hashCode : hashCodeValues) {
                long numSuffix = 0L;
                if (logicTableName.equals(TableTypeEnum.WALLET.getCode())) {
                    numSuffix = Math.abs(hashCode) % 8;
                }
 
                if (logicTableName.equals(TableTypeEnum.WALLET_DETAIL.getCode())
                        || logicTableName.equals(TableTypeEnum.WALLET_ACTION.getCode())
                        || logicTableName.equals(TableTypeEnum.WALLET_ACTION_REL.getCode())
                        || logicTableName.equals(TableTypeEnum.WALLET_DETAIL_BIZ_REL.getCode())) {
                    numSuffix = Math.abs(hashCode) % 16;
 
                }
 
                String tableSuffix = "";
                if (numSuffix < 10) {
                    tableSuffix = financeYear + "_0" + numSuffix;
                } else {
                    tableSuffix = financeYear + "_" + numSuffix;
                }
 
                for (String tableName : collection) {
                    if (tableName.endsWith(tableSuffix)) {
                        shardingSuffix.add(tableName);
                    }
                }
            }
        }
 
        return shardingSuffix;
    }
 
    /**
     * 获得分片键的值
     * @param shardingValues    分片键集合
     * @param splitKey   分片键
     * @return
     */
    private Collection<Integer> getShardingValue(Collection<ShardingValue> shardingValues, final String splitKey) {
        Collection<Integer> valueSet = new ArrayList<>();
        for (ShardingValue shardingValue : shardingValues) {
            if (shardingValue instanceof ListShardingValue) {
                ListShardingValue listShardingValue = (ListShardingValue) shardingValue;
                if (listShardingValue.getColumnName().equals(splitKey)) {
                    logicTableName = listShardingValue.getLogicTableName();
                    return listShardingValue.getValues();
                }
            }
        }
 
        return valueSet;
    }

###4.问题

第一、多分片键要使用complex模式,我一开始没注意,被inline表达式折磨了很久
第二、如果配置的不对,它找不到真正的表,它就会执行所有表
第三、大小写敏感,如果分片键,表中字段是小写,配置文件中是大写,恭喜你,它没办法找到真正的表
第四、相同的逻辑表的真实表,必须结构相同。启动应用的时候,加载配置文件,它会检查配置的分表表结构是否一致,比如你的数据库中只有2019年的表,而你配置的是2019-2022年的表,很好,工程会起不来,抛异常表结构不一致。

5.批量insert,不能foreach insert语句,只能foreach values。这样就会有一个问题,必须指明字段。如果我没有说清楚,请看代码


# 不支持的写法
 
<insert id="batchInsertSelective" parameterType="java.util.Map">
    <foreach collection="userList" index="index" item="userDO" separator=";">
      insert into user
      <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="userDO.id != null">
          id,
        </if>
        <if test="userDO.createDatetime != null">
          create_datetime,
        </if>
        <if test="userDO.updateDatetime != null">
          update_datetime,
        </if>
        <if test="userDO.createUser != null">
          create_user,
        </if>
        <if test="userDO.updateUser != null">
          update_user,
        </if>
        <if test="userDO.financeYear != null">
          finance_year,
        </if>
        <if test="userDO.hashCode != null">
          hash_code,
        </if>
      </trim>
      <trim prefix="values (" suffix=")" suffixOverrides=",">
        <if test="userDO.id != null">
          #{userDO.id,jdbcType=BIGINT},
        </if>
        <if test="userDO.createDatetime != null">
          #{userDO.createDatetime,jdbcType=TIMESTAMP},
        </if>
        <if test="userDO.updateDatetime != null">
          #{userDO.updateDatetime,jdbcType=TIMESTAMP},
        </if>
        <if test="userDO.createUser != null">
          #{userDO.createUser,jdbcType=VARCHAR},
        </if>
        <if test="userDO.updateUser != null">
          #{userDO.updateUser,jdbcType=VARCHAR},
        </if>
        <if test="userDO.financeYear != null">
          #{userDO.financeYear,jdbcType=INTEGER},
        </if>
        <if test="userDO.hashCode != null">
          #{userDO.hashCode,jdbcType=INTEGER},
        </if>
      </trim>
    </foreach>
  </insert>
 
# 支持的写法
  <insert id="batchInsertSelective" parameterType="java.util.List">
    insert into wallet_action_rel (
      create_user, update_user,finance_year,hash_code
    ) values
    <foreach collection="list" index="index" item="userDO" separator=",">
      <trim prefix="(" suffix=")" >
        #{userDO.createUser,jdbcType=VARCHAR},
        #{userDO.updateUser,jdbcType=VARCHAR},
        #{userDO.financeYear,jdbcType=INTEGER},
        #{userDO.hashCode,jdbcType=INTEGER}
      </trim>
    </foreach>
  </insert>

###6.select和update语句,必须把分片键写入到where条件中,否则就操作所有的表。这里就要注意SQL的效率,分片键最好建索引,否则很影响查询效率。

之前看过一个大佬说的话,觉得很有道理,“能不分表就不分表,能用分区表就不要用物理分表”。sharding-jdbc还有很多限制,因为我的应用场景比较简单,所以目前还没有遇到,仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值