Springboot+Sharding-JDBC分库分表实践一

前言

随着业务的快速发展,业务系统数据表记录也随着急剧增长,带来的明显结果就是当用户访问某些表时性能显著下降,通过分析后决定来拆分大的数据表以降低单表数据量,提高查询性能。

分库分表又分为垂直拆分和水平拆分,这里简单介绍下:
  • 垂直分库

    即按照业务模块进行拆分,比如将订单模块独立为一个数据库,商品模块独立为一个数据库

  • 垂直分表

    即将宽表拆分为窄表,所有记录都能在单表中找到,通过将一些字段拆分出去建立副表来降低单表的数据量;比如订单表中,通过垂直拆分我们可以将低频、占用空间大的字段拆分出去

  • 水平分库

    即将相同的表拆分到不同的数据库,以来降低单库的压力;比如我们可以通过用户id来拆分,用户id为奇数的存储到order_1库的订单表,用户id为偶数的订单存储到order_2库的订单表;

  • 水平分表

    即将相同的表复制出来多份,以来降低单表的数据量,提升单表查询性能;比如在order数据库中创建user_order_1、user_order_2表,通过订单id求余来存储到不同的表中

一、Sharding-JDBC是什么?

在Java的JDBC层提供的额外服务, 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
其主要支持功能有数据分片、读写分离、分布式事务等;本篇文章主要介绍使用其数据分片功能来实现分库分表
逻辑架构

二、使用步骤

这里主要以springboot+Mybaits-Plus+来演示使用方式

1.引包

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>

2.配置

application.yml
server:
  port: 8099

mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.none.sharding.domain.entity
spring:
  application:
    name: sharding
  shardingsphere:
    datasource:
      names: ds0
      ds0:
        url: jdbc:mysql://localhost:3306/order
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: 123456
    sharding:
      defaultDataSourceName: ds0  #默认数据源,即不需要分库分表的表查询时会默认使用ds0数据库
      tables:
        t_address:  #t_address
          key-generator-column-name: id  #主键 通过雪花算法生成
          actual-data-nodes: ds0.t_address${0..1}    #数据节点,数据库中t_address0,t_address1
          table-strategy:  #分表策略
            inline: #行表达式
              sharding-column: id #分表字段为id
              algorithm-expression: t_address${id % 2} #分表算法为求余

        t_user_order:
          key-generator-column-name: id  #主键
          actual-data-nodes: ds0.t_user_order${0..2}    #数据节点,均匀分布
          table-strategy:  #分表策略
            inline:
              sharding-column: id
              algorithm-expression: t_user_order${id % 3}
    props:
      sql.show: true #显示原sql以及解析生成的实际sql
      allow.range.query.with.inline.sharding: true  #支持范围查询例如大于、小于、between;

2. 使用

由于Sharding-JDBC定义为增强版jdbc所以使用方式和jdbc相同,对于业务代码无侵入,原有查询怎么写接入Sharding-JDBC后基本不用进行任何改变;
这里我们使用Springboot+mybatis-plus+Sharding-JDBC做个简单示例:

userOrderDO实体
@Data
@TableName("t_user_order")
public class UserOrderDO
        extends Model<UserOrderDO> {
    private Long id;
    private String orderNo;
    private Long userId;
    private Integer amount;
}
UserOrderMapper
@Mapper
public interface UserOrderMapper
        extends BaseMapper<UserOrderDO> {
}
UserOrderService
public interface UserOrderService
        extends IService<UserOrderDO>  {
}
UserOrderServiceImpl
@Service
public class UserOrderServiceImpl extends ServiceImpl<UserOrderMapper, UserOrderDO>
        implements UserOrderService {
}
UserOrderController
@RestController
@RequestMapping("/userOrder/")
public class UserOrderController {
    @Resource
    private UserOrderService userOrderService;

    /**
     * 生成订单
     * @return
     */
    @RequestMapping("save")
    public String save() {
        UserOrderDO userOrderDO = new UserOrderDO();
        userOrderDO.setOrderNo(String.valueOf(System.currentTimeMillis()));
        userOrderDO.setUserId((long)123);
        userOrderDO.setAmount(500);
        userOrderService.save(userOrderDO);
        return "success";
    }

    /**
     * 通过订单id查询订单
     * @param id
     * @return
     */
    @RequestMapping("get")
    public UserOrderDO getOrderById(@RequestParam Long id) {
        return userOrderService.getById(id);
    }
}

调用/userOrder/save,控制台可以看到如下两条sql:

Logic SQL: INSERT INTO t_user_order  ( id, order_no, user_id,amount )  VALUES  ( ?, ?, ?, ? )

Actual SQL: ds0 ::: INSERT INTO t_user_order0  ( id, order_no, user_id, amount )  VALUES  (?, ?, ?, ?) ::: [1340265974084612097, 1608379283781, 123, 500]

可以看到,我们对t_user_order进行插入的时候,Sharding-JDBC最终会根据分表规则进行替换,1340265974084612097%3=0,所以最终sql会路由到t_user_order0这个数据表中;

那我们再看下查询,

Logic SQL: SELECT id,order_no,user_id,amount FROM t_user_order WHERE id=? 

Actual SQL: ds0 ::: SELECT id,order_no,user_id,amount FROM t_user_order2 WHERE id=?  ::: [2]

因为2%3=2所以这条sql最终会去查询t_user_order2表

3. 注意

虽然Sharding-JDBC与传统jdbc的使用方式基本无异,但是在分表场景下仍然有些sql不能支持,或者支持不完善;

  • 5.x之前不支持子查询;5.0.0-alpha内测中,5.0.0版本已经支持
  • 5.x之前不支持更新操作 set column=column 操作
  • 对分库分表的表进行curd时,一定要使用分片字段;SQL 中如果无分片字段,将执行全路由,性能较差;比如:对t_user_order表拆分了40张表,分片字段为id,当使用order_no进行查询时,将会去查询40张表然后聚合结果集返回
  • 慎用范围查询,包括>=、<=、between;范围查询通常也会走全路由 (除非特殊情况可以在算法中进行拆解)
  • join操作;对于两张进行join操作的表可以进行绑定关系设置分片键、分片规则一致;避免join操作变为笛卡尔积式查询
  • 对分片键函数操作则会全路由,比如contact(‘id’,‘a’)
  • 禁止在sql中使用case when、if else等业务查询;
  • mybatis-plus使用getById、updateById、updateBatchById等操作时要考虑分表键是否为id,如果不是id字段则需要重写这些方法,在where条件中要增加相关分片键的条件;否则将会全路由;如果更新操作中要更新分片键的值,则会报错不能修改分片键;

总结

以上即为Springboot+MyBatis-plus+Sharding-JDBC进行拆分表的简单demo,后续会更新拆分表的几个策略:标准分片策略、复合分片策略、行表达式分片策略、Hint分片策略
Demo 地址 https://github.com/nangge/sharding.git

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值