为什么要分库分表


当数据库中的数据达到一定数量的时候(尤其是单张表的数据达到百万千万级别的时候),会影响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接口新增数据和查询数据,功能正常运行。

示例代码:https://github.com/jasonlu0117/sharding-springboot