Skip to content

分库分表-节目服务

阅读此文前,建议小伙伴先阅读分库分表的前置知识,对分库分表 和 shardingsphere 有了大概的理解后,在继续阅读本文

技术精华-全面剖析分库分表

介绍

在开始介绍前,需要先知道大麦网中数据库表的关系是怎么样,关于数据库表设计的详细介绍,小伙伴可跳转到相关文档

基础讲解-数据库表关系

配置

引入 ShardingSphere 的相关依赖

xml
<properties>
	<shardingsphere.version>5.3.2</shardingsphere.version>
</properties>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>${shardingsphere.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>logback-classic</artifactId>
            <groupId>ch.qos.logback</groupId>
        </exclusion>
    </exclusions>
</dependency>

根据规则进行分库分表的规则配置

ShardingSphere 官网的规则配置说明:

数据分片 :: ShardingSphere

节目项目相关配置:

yaml
spring:
  datasource:
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    url: jdbc:shardingsphere:classpath:shardingsphere-program.yaml

shardingsphere-program.yaml配置:

yaml
dataSources:
  # 第一个节目库
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_program_0?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    password: root
  # 第一个节目库
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_program_1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    password: root

rules:
  - !SHARDING
    tables:
      # 对d_program表进行分库分表
      d_program:
        # 库为damai_program_0 damai_program_1 表为d_program_0 至 d_program_1
        actualDataNodes: ds_${0..1}.d_program_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: id
            # 使用id分库算法
            shardingAlgorithmName: databaseProgramModModel
        # 分表策略
        tableStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: id
            # 使用id分表算法
            shardingAlgorithmName: tableProgramModModel
      # 对d_program_group表进行分库分表      
      d_program_group:
        # 库为damai_program_0 damai_program_1 表为d_program_group_0 至 d_program_group_1
        actualDataNodes: ds_${0..1}.d_program_group_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: id
            # 使用id分库算法
            shardingAlgorithmName: databaseProgramGroupModModel
        # 分表策略    
        tableStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: id
            # 使用id分库算法
            shardingAlgorithmName: tableProgramGroupModModel
      # 对d_program_show_time表进行分库分表
      d_program_show_time:
        # 库为damai_program_0 damai_program_1 表为d_program_show_time_0 至 d_program_show_time_1
        actualDataNodes: ds_${0..1}.d_program_show_time_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分库算法
            shardingAlgorithmName: databaseProgramShowTimeModModel
        # 分表策略
        tableStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分表算法
            shardingAlgorithmName: tableProgramShowTimeModModel
      # 对d_seat表进行分库分表
      d_seat:
        # 库为damai_program_0 damai_program_1 表为d_seat_0 至 d_seat_1
        actualDataNodes: ds_${0..1}.d_seat_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分库算法
            shardingAlgorithmName: databaseSeatModModel
        # 分表策略
        tableStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分表算法
            shardingAlgorithmName: tableSeatModModel
      # 对d_ticket_category表进行分库分表
      d_ticket_category:
        # 库为damai_program_0 damai_program_1 表为d_ticket_category_0 至 d_ticket_category_1
        actualDataNodes: ds_${0..1}.d_ticket_category_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分库算法
            shardingAlgorithmName: databaseTicketCategoryModModel
        # 分表策略
        tableStrategy:
          standard:
            # 使用id作为分片键
            shardingColumn: program_id
            # 使用id分表算法
            shardingAlgorithmName: tableTicketCategoryModModel
      # 对d_program_record_task表进行分库分表
      d_program_record_task:
        # 库为damai_program_0 damai_program_1 表为d_program_record_task_0 至 d_program_record_task_1
        actualDataNodes: ds_${0..1}.d_program_record_task_${0..1}
        # 分库策略
        databaseStrategy:
          standard:
            # 使用create_time作为分片键
            shardingColumn: create_time
            # 使用create_time分库算法
            shardingAlgorithmName: databaseProgramRecordTaskModModel
        # 分表策略
        tableStrategy:
          standard:
            # 使用create_time作为分片键
            shardingColumn: create_time
            # 使用create_time分表算法
            shardingAlgorithmName: tableProgramRecordTaskModModel
    # 广播表
    broadcastTables:
      - d_program_category
    # 具体的算法(采用基因位分离策略:分库用低位bit0,分表用次低位bit1,避免数据倾斜)
    shardingAlgorithms:
      # d_program表分库算法 - 使用id的低位(bit0)
      databaseProgramModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${id % 2}
      # d_program表分表算法 - 使用id的次低位(bit1)
      tableProgramModModel:
        type: INLINE
        props:
          algorithm-expression: d_program_${(id.intdiv(2)) % 2}
      # d_program_group表分库算法 - 使用id的低位(bit0)
      databaseProgramGroupModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${id % 2}
      # d_program_group表分表算法 - 使用id的次低位(bit1)
      tableProgramGroupModModel:
        type: INLINE
        props:
          algorithm-expression: d_program_group_${(id.intdiv(2)) % 2}
      # d_program_show_time表分库算法 - 使用program_id的低位(bit0)
      databaseProgramShowTimeModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${program_id % 2}
      # d_program_show_time表分表算法 - 使用program_id的次低位(bit1)
      tableProgramShowTimeModModel:
        type: INLINE
        props:
          algorithm-expression: d_program_show_time_${(program_id.intdiv(2)) % 2}
      # d_seat表分库算法 - 使用program_id的低位(bit0)
      databaseSeatModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${program_id % 2}
      # d_seat表分表算法 - 使用program_id的次低位(bit1)
      tableSeatModModel:
        type: INLINE
        props:
          algorithm-expression: d_seat_${(program_id.intdiv(2)) % 2}
      # d_ticket_category表分库算法 - 使用program_id的低位(bit0)
      databaseTicketCategoryModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${program_id % 2}
      # d_ticket_category表分表算法 - 使用program_id的次低位(bit1)
      tableTicketCategoryModModel:
        type: INLINE
        props:
          algorithm-expression: d_ticket_category_${(program_id.intdiv(2)) % 2}
      # d_program_record_task表分库算法 - 使用create_time的hashCode低位(bit0)
      databaseProgramRecordTaskModModel:
        type: INLINE
        props:
          algorithm-expression: ds_${Math.abs(create_time.hashCode()) % 2}
      # d_program_record_task表分表算法 - 使用create_time的hashCode次低位(bit1)
      tableProgramRecordTaskModModel:
        type: INLINE
        props:
          algorithm-expression: d_program_record_task_${(Math.abs(create_time.hashCode()).intdiv(2)) % 2}
props:
  # 打印真实sql
  sql-show: true

节目服务的分库分表配置比较常规,基本都是用program_id作为分片键,d_program_category表存储的是节目种类数据,数据量并不是很大,也就几百条,并且每个节目数据都需要此表关联,所以d_program_category比较适合作为广播表

分库分表的算法详解

问题背景:为什么传统 MOD 算法会导致数据倾斜?

如果分库和分表都使用相同的 id % 2 算法:

groovy
// 错误的配置方式
分库: ds_${id % 2}
分表: d_program_${id % 2}

导致的问题

plain
              表_0    表_1
库_0 (ds_0)   ✔️       ✖️    ← 偶数库的奇数表永远没数据
库_1 (ds_1)   ✖️       ✔️    ← 奇数库的偶数表永远没数据

解决方案:intdiv 基因位分离策略

groovy
// 正确的配置方式
分库: ds_${id % 2}                          // 使用 bit0
分表: d_program_${(id.intdiv(2)) % 2}        // 使用 bit1

intdiv 是什么?

intdiv 是 Groovy 的整数除法,等价于 id / 2 取整数部分:

groovy
id.intdiv(2)  等价于  id / 2  (取整数部分,即右移1位)

核心原理:使用不同的二进制位

plain
id 的二进制表示: ... bit3  bit2  bit1  bit0
                          │     │     │     │
                          │     │     │     └─── id % 2     → 分库
                          │     │     └─────── (id/2) % 2 → 分表
  • id % 2:提取最低位 (bit0),用于决定分库
  • (id.intdiv(2)) % 2:先除以2(右移1位),再取最低位,即提取次低位 (bit1),用于决定分表

完整数据分布表

id二进制分库 (bit0)分表 (bit1)路由结果
00000ds_0.d_program_0
10110ds_1.d_program_0
21001ds_0.d_program_1
31111ds_1.d_program_1
410000ds_0.d_program_0
510110ds_1.d_program_0

结果:所有4个分片都有数据,均匀分布!

plain
              表_0    表_1
库_0 (ds_0)   ✔️       ✔️    ← 偶数库的奇数表和偶数表都有数据
库_1 (ds_1)   ✔️       ✔️    ← 奇数库的奇数表和偶数表都有数据

关联表分片键选择

分片键原因
d_programid主键,唯一标识
d_program_groupid独立分组
d_program_show_timeprogram_id与节目关联,便于查询
d_seatprogram_id与节目关联,便于查询
d_ticket_categoryprogram_id与节目关联,便于查询

设计思想:所有与节目相关的子表都使用 program_id 作为分片键,确保同一节目的所有数据落在同一个分片,提升关联查询性能。

架构图

plain
┌─────────────────────────────────────────────────────────────┐
│                    Program Service                           │
├─────────────────────────────────────────────────────────────┤
│                    ShardingSphere                            │
│                  (基因位分离策略)                             │
├──────────────────────────┬──────────────────────────────────┤
│      damai_program_0     │       damai_program_1            │
├──────────────────────────┼──────────────────────────────────┤
│ d_program_0~1            │ d_program_0~1                    │
│ d_program_group_0~1      │ d_program_group_0~1              │
│ d_program_show_time_0~1  │ d_program_show_time_0~1          │
│ d_seat_0~1               │ d_seat_0~1                       │
│ d_ticket_category_0~1    │ d_ticket_category_0~1            │
│ d_program_record_task_0~1│ d_program_record_task_0~1        │
├──────────────────────────┴──────────────────────────────────┤
│              d_program_category (广播表)                     │
└─────────────────────────────────────────────────────────────┘

更新: 2026-01-23 09:56:09
原文: https://www.yuque.com/u22210564/ykdrdh/sk52gx76cu3a73cc

Java 后端面试知识库