ShardingSphere-JDBC 实战指南

目录


一、前言

随着业务数据量的增长,单库单表逐渐无法满足高并发、大数据量的需求。分库分表成为解决这一问题的有效方案。

ShardingSphere是Apache旗下的开源分布式数据库中间件,本文档基于实际项目,讲解ShardingSphere-JDBC的配置和使用方法。


二、什么是ShardingSphere-JDBC

ShardingSphere-JDBC是Apache ShardingSphere的核心产品,定位为轻量级Java框架,在JDBC层提供分库分表服务。

核心特点:

  • 透明化 - 对业务代码零侵入,就像使用普通JDBC一样
  • 轻量级 - 无需额外部署,仅作为JDBC驱动存在
  • 配置简单 - 通过YAML配置即可实现分库分表

适用场景:

  • 需要分库分表的应用
  • 基于MyBatis、JPA等ORM框架的应用

三、为什么需要分库分表

3.1 单库单表的瓶颈

当数据量达到千万级时,会遇到以下问题:

  1. 性能瓶颈 - 单表数据量大,查询变慢
  2. 存储瓶颈 - 磁盘IO成为瓶颈
  3. 连接限制 - 数据库连接数不足

3.2 分库分表的优势

  1. 提升性能 - 单表数据量减少,查询速度提升
  2. 提高并发 - 多库分担读写压力
  3. 易于扩展 - 可以动态增加数据源

四、核心概念

4.1 逻辑表与物理表

逻辑表 - 应用层看到的表,比如t_user

物理表 - 实际存储在数据库中的表,比如t_user_0t_user_1

yaml 复制代码
t_user:  # 逻辑表
  actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}  # 物理表

4.2 分片键

决定数据路由到哪个分片的字段,必须包含在SQL中才能正确路由。

java 复制代码
// 正确:包含分片键,精确路由
SELECT * FROM t_user WHERE user_id = 100;

// 错误:不包含分片键,全路由(查询所有分片)
SELECT * FROM t_user WHERE username = 'zhangsan';

4.3 分片算法

ShardingSphere通过分片算法决定数据路由到哪个分片。

算法类型 说明 适用场景
INLINE 基于Groovy表达式的简单算法 简单的取模分片
STANDARD 标准分片算法 需要精确和范围查询

本项目使用INLINE算法,配置简单,性能优秀。

4.4 主键生成器

分布式环境下,需要全局唯一的主键生成策略。

生成器类型 说明 特点
SNOWFLAKE 雪花算法 分布式、高性能、趋势递增

4.5 广播表

在所有数据源中都存在的表,数据完全一致。

特点:

  • 插入、更新、删除操作会在所有数据源执行
  • 查询操作只在一个数据源执行
  • 适用于字典表、配置表等小表

配置示例:

yaml 复制代码
broadcast-tables:
  - t_dict

4.6 绑定表

多个表使用相同的分片键和分片算法,确保关联数据在同一分片。

配置示例:

yaml 复制代码
binding-tables:
  - t_order,t_order_item

优势:

  • 避免跨库JOIN
  • 提高查询性能
  • 简化事务处理

五、快速开始

5.1 添加依赖

pom.xml中添加ShardingSphere-JDBC依赖:

xml 复制代码
<dependencies>
    <!-- ShardingSphere-JDBC核心依赖 -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
        <version>5.2.1</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.1</version>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.38</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

5.2 配置数据源

application.yml中配置4个数据源:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1,ds2,ds3
      
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db1?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          connection-test-query: SELECT 1
      
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          connection-test-query: SELECT 1
      
      ds2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db3?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          connection-test-query: SELECT 1
      
      ds3:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db4?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          connection-test-query: SELECT 1

配置说明:

  • names - 定义所有数据源的名称
  • hikari - HikariCP连接池配置
    • minimum-idle - 最小空闲连接数
    • maximum-pool-size - 最大连接池大小
    • connection-timeout - 连接超时时间(毫秒)

5.3 配置分片规则

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          # 用户表配置
          t_user:
            # 实际数据节点: ds0.t_user_0, ds0.t_user_1, ds1.t_user_0, ds1.t_user_1
            actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}
            
            # 分库策略
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: user-db-inline
            
            # 分表策略
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: user-table-inline
            
            # 主键生成策略
            key-generate-strategy:
              column: user_id
              key-generator-name: snowflake

配置说明:

  • actual-data-nodes - 定义物理表的分布
    • ds$->{0..1} - 表示ds0和ds1两个数据源
    • t_user_$->{0..1} - 表示t_user_0和t_user_1两个表
    • 组合后: ds0.t_user_0, ds0.t_user_1, ds1.t_user_0, ds1.t_user_1

5.4 配置分片算法

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        sharding-algorithms:
          # 用户表分库算法
          user-db-inline:
            type: INLINE
            props:
              algorithm-expression: 'ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }'
          
          # 用户表分表算法
          user-table-inline:
            type: INLINE
            props:
              algorithm-expression: 't_user_$->{ Math.abs(user_id.hashCode()) % 2 }'

算法说明:

  1. 分库算法 : ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }

    • 计算user_id的hashCode取绝对值
    • 对4取模,得到0、1、2、3
    • 整除2,得到0、1
    • 最终路由到ds0或ds1
  2. 分表算法 : t_user_$->{ Math.abs(user_id.hashCode()) % 2 }

    • 计算user_id的hashCode取绝对值
    • 对2取模,得到0、1
    • 最终路由到t_user_0或t_user_1

数据分布示例:

ini 复制代码
user_id = 100
- 分库: (100 % 4).intdiv(2) = 0 → ds0
- 分表: 100 % 2 = 0 → t_user_0
- 最终路由: ds0.t_user_0

5.5 配置主键生成器

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:
          snowflake:
            type: SNOWFLAKE

雪花算法原理:

  • 1位符号位
  • 41位时间戳(毫秒级)
  • 10位机器ID
  • 12位序列号

5.6 配置广播表和绑定表

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        # 绑定表配置
        binding-tables:
          - t_order,t_order_item
        
        # 广播表配置
        broadcast-tables:
          - t_dict

5.7 配置属性

yaml 复制代码
spring:
  shardingsphere:
    props:
      # 显示SQL(生产环境建议关闭)
      sql-show: true
      # 允许没有分片列的插入通过主键生成器
      check-table-metadata-enabled: false

5.8 创建实体类

java 复制代码
package com.example.demo.model;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
    
    /**
     * 用户ID(主键,由ShardingSphere自动生成)
     */
    private Long userId;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 邮箱
     */
    private String email;
    
    /**
     * 年龄
     */
    private Integer age;
    
    /**
     * 创建时间
     */
    private LocalDateTime createdTime;
    
    /**
     * 更新时间
     */
    private LocalDateTime updatedTime;
}

5.9 创建Mapper接口

java 复制代码
package com.example.demo.mapper;

import com.example.demo.model.User;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface UserMapper {
    
    /**
     * 插入用户
     * 注意:不包含user_id字段,由ShardingSphere自动生成
     */
    @Insert("INSERT INTO t_user (username, email, age) VALUES (#{username}, #{email}, #{age})")
    int insert(User user);
    
    /**
     * 根据ID更新用户
     */
    @Update("UPDATE t_user SET username = #{username}, email = #{email}, age = #{age} WHERE user_id = #{userId}")
    int updateById(User user);
    
    /**
     * 根据ID查询用户
     */
    @Select("SELECT * FROM t_user WHERE user_id = #{userId}")
    User selectById(Long userId);
    
    /**
     * 根据用户名查询用户
     * 注意:此查询不包含分片键,会导致全路由
     */
    @Select("SELECT * FROM t_user WHERE username = #{username}")
    User selectByUsername(String username);
    
    /**
     * 查询所有用户
     * 注意:此查询不包含分片键,会导致全路由
     */
    @Select("SELECT * FROM t_user")
    List<User> selectAll();
    
    /**
     * 根据ID删除用户
     */
    @Delete("DELETE FROM t_user WHERE user_id = #{userId}")
    int deleteById(Long userId);
}

重要说明:

  • INSERT语句中不包含分片键字段(user_id),由ShardingSphere自动生成
  • 查询语句最好包含分片键,否则会导致全路由,性能较差

5.10 创建Controller层

java 复制代码
package com.example.demo.controller;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
    
    @Autowired
    private UserMapper userMapper;
    
    @PostMapping
    public String insert(@RequestBody User user) {
        int result = userMapper.insert(user);
        return result > 0 ? "User inserted successfully" : "Failed to insert user";
    }
    
    @PutMapping
    public String update(@RequestBody User user) {
        int result = userMapper.updateById(user);
        return result > 0 ? "User updated successfully" : "Failed to update user";
    }
    
    @GetMapping("/{userId}")
    public User selectById(@PathVariable Long userId) {
        return userMapper.selectById(userId);
    }
    
    @GetMapping("/username/{username}")
    public User selectByUsername(@PathVariable String username) {
        return userMapper.selectByUsername(username);
    }
    
    @GetMapping
    public List<User> selectAll() {
        return userMapper.selectAll();
    }
    
    @DeleteMapping("/{userId}")
    public String deleteById(@PathVariable Long userId) {
        int result = userMapper.deleteById(userId);
        return result > 0 ? "User deleted successfully" : "Failed to delete user";
    }
}

5.11 初始化数据库

执行SQL脚本创建数据库和表:

sql 复制代码
-- 创建数据库
CREATE DATABASE IF NOT EXISTS test_db1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS test_db2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户表
CREATE TABLE IF NOT EXISTS t_user_0
(
    user_id      BIGINT PRIMARY KEY,
    username     VARCHAR(50) NOT NULL,
    email        VARCHAR(100),
    age          INT,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS t_user_1
(
    user_id      BIGINT PRIMARY KEY,
    username     VARCHAR(50) NOT NULL,
    email        VARCHAR(100),
    age          INT,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

5.12 启动项目

bash 复制代码
mvn spring-boot:run

5.13 测试API

bash 复制代码
# 插入用户
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"username":"zhangsan","email":"zhangsan@example.com","age":25}'

# 根据ID查询用户
curl http://localhost:8080/users/100

# 根据用户名查询用户
curl http://localhost:8080/users/username/zhangsan

# 查询所有用户
curl http://localhost:8080/users

# 更新用户
curl -X PUT http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"userId":100,"username":"lisi","email":"lisi@example.com","age":26}'

# 删除用户
curl -X DELETE http://localhost:8080/users/100

六、分片策略详解

6.1 标准分片策略

标准分片策略是最常用的分片策略,支持精确分片和范围查询。

配置示例:

yaml 复制代码
t_user:
  actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}
  database-strategy:
    standard:
      sharding-column: user_id
      sharding-algorithm-name: user-db-inline
  table-strategy:
    standard:
      sharding-column: user_id
      sharding-algorithm-name: user-table-inline

6.2 行表达式分片算法

行表达式分片算法是最简单的分片算法,使用Groovy表达式定义分片规则。

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        sharding-algorithms:
          user-db-inline:
            type: INLINE
            props:
              algorithm-expression: 'ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }'
          
          user-table-inline:
            type: INLINE
            props:
              algorithm-expression: 't_user_$->{ Math.abs(user_id.hashCode()) % 2 }'

常用表达式:

表达式 说明 示例
$->{column} 获取分片列的值 user_id
$->{column % n} 取模运算 user_id % 2
$->{Math.abs(column.hashCode())} 取绝对值 Math.abs(user_id.hashCode())
$->{column.intdiv(n)} 整除运算 (user_id % 4).intdiv(2)

示例:

  1. 简单取模
yaml 复制代码
algorithm-expression: 't_user_$->{user_id % 2}'
  1. Hash取模
yaml 复制代码
algorithm-expression: 't_user_$->{Math.abs(user_id.hashCode()) % 2}'

注意事项:

  • 表达式必须返回字符串
  • 表达式中的变量名必须与分片列名一致
  • 复杂表达式建议使用自定义算法

6.3 本项目分片策略总结

用户表 (t_user)

  • 数据源: ds0, ds1 (对应数据库 test_db1, test_db2)
  • 分片键: user_id
  • 分库策略: 按 user_id % 4 后整除2 (偶数分到ds0, 奇数分到ds1)
  • 分表策略: 按 user_id % 2 分表
  • 表结构: 两个数据源共包含 t_user_0 和 t_user_1 四个分表

订单表 (t_order)

  • 数据源: ds2, ds3 (对应数据库 test_db3, test_db4)
  • 分片键: order_id
  • 分库算法: 按 order_id % 2 映射到 ds2, ds3
  • 分表算法: 按 order_id % 4 分片
  • 表结构: 每个数据源包含 t_order_0 到 t_order_3 四个分表

订单项表 (t_order_item)

  • 数据源: ds2, ds3 (对应数据库 test_db3, test_db4)
  • 分片键: order_id
  • 与订单表为绑定表关系

广播表 (t_dict)

  • 在所有数据源中都存在
  • 数据完全一致

七、高级特性

7.1 Hint分片策略

Hint分片策略通过Hint指定分片,适用于需要强制路由的场景。

使用场景:

  • 批量操作时指定分片提高性能
  • 需要强制路由到特定分片的场景
  • 数据迁移场景

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds$->{2..3}.t_order_$->{0..3}
            table-strategy:
              hint:
                sharding-algorithm-name: order-hint
        
        sharding-algorithms:
          order-hint:
            type: INLINE
            props:
              algorithm-expression: 't_order_$->{value}'

使用Hint路由:

java 复制代码
package com.example.demo.controller;

import com.example.demo.mapper.OrderMapper;
import com.example.demo.model.Order;
import org.apache.shardingsphere.infra.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/orders")
public class OrderController {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 使用Hint强制路由到指定分片查询
     * 
     * @param orderId 订单ID
     * @param tableIndex 表索引(0-3)
     * @return 订单对象
     */
    @GetMapping("/hint/{orderId}/{tableIndex}")
    public Order selectByHint(@PathVariable Long orderId, @PathVariable int tableIndex) {
        try (HintManager hintManager = HintManager.getInstance()) {
            // 设置Hint值,指定路由到哪个分片
            hintManager.addTableShardingValue("t_order", tableIndex);
            return orderMapper.selectById(orderId);
        }
    }
    
    /**
     * 批量查询,指定多个分片
     * 
     * @param tableIndices 表索引列表(0-3)
     * @return 订单列表
     */
    @GetMapping("/hint/batch/{tableIndices}")
    public List<Order> batchSelectByHint(@PathVariable List<Integer> tableIndices) {
        try (HintManager hintManager = HintManager.getInstance()) {
            // 设置多个Hint值
            for (int tableIndex : tableIndices) {
                hintManager.addTableShardingValue("t_order", tableIndex);
            }
            return orderMapper.selectAll();
        }
    }
}

注意事项:

  • HintManager使用后需要关闭,可以使用try-with-resources
  • Hint只在当前线程有效
  • Hint优先级高于其他分片策略

7.2 读写分离

读写分离是提升系统性能的重要手段,将读操作分发到从库,写操作在主库执行。

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: master,slave0,slave1
      
      # 主库配置
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
      
      # 从库0配置
      slave0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3307/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
      
      # 从库1配置
      slave1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3308/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
    
    rules:
      # 读写分离规则
      readwrite-splitting:
        data-sources:
          # 读写分离数据源配置
          readwrite_ds:
            type: Static
            props:
              # 写数据源
              write-data-source-name: master
              # 读数据源列表
              read-data-source-names: slave0,slave1
              # 负载均衡算法
              load-balancer-name: round_robin
        
        # 负载均衡算法
        load-balancers:
          round_robin:
            type: ROUND_ROBIN
          random:
            type: RANDOM

负载均衡算法:

算法类型 说明 适用场景
ROUND_ROBIN 轮询 从库性能相近
RANDOM 随机 从库性能相近

强制读主库:

java 复制代码
package com.example.demo.controller;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.apache.shardingsphere.infra.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 强制从主库读取数据
     * 用于需要读取最新数据的场景
     * 
     * @param userId 用户ID
     * @return 用户对象
     */
    @GetMapping("/master/{userId}")
    public User selectFromMaster(@PathVariable Long userId) {
        try (HintManager hintManager = HintManager.getInstance()) {
            // 设置从主库读取
            hintManager.setWriteRouteOnly();
            return userMapper.selectById(userId);
        }
    }
}

注意事项:

  • 读写分离依赖MySQL主从复制
  • 主从复制有延迟,可能读到旧数据
  • 需要读取最新数据时,使用Hint强制读主库
  • 确保从库数据与主库一致

7.3 数据加密

ShardingSphere支持数据加密,保护敏感数据。

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      # 加密规则
      encrypt:
        tables:
          t_user:
            columns:
              # 邮箱加密配置
              email:
                cipher-column: email_cipher  # 加密列
                assisted-query-column: email_assisted  # 辅助查询列
                encryptor-name: aes_encryptor  # 加密器名称
              # 手机号加密配置
              phone:
                cipher-column: phone_cipher
                encryptor-name: aes_encryptor
        
        # 加密器配置
        encryptors:
          aes_encryptor:
            type: AES
            props:
              aes-key-value: 1234567890123456  # AES密钥,16位

实体类调整:

java 复制代码
package com.example.demo.model;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
    
    private Long userId;
    private String username;
    
    /**
     * 邮箱(逻辑列,自动加密)
     */
    private String email;
    
    /**
     * 手机号(逻辑列,自动加密)
     */
    private String phone;
    
    private Integer age;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

数据库表结构:

sql 复制代码
CREATE TABLE IF NOT EXISTS t_user_0
(
    user_id            BIGINT PRIMARY KEY,
    username           VARCHAR(50) NOT NULL,
    email_cipher       VARCHAR(255),  -- 加密后的邮箱
    email_assisted     VARCHAR(255),  -- 辅助查询列
    phone_cipher       VARCHAR(255),  -- 加密后的手机号
    age                INT,
    created_time       TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time       TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

加密算法类型:

算法类型 说明 适用场景
AES AES加密 通用加密
MD5 MD5加密 不可逆加密

注意事项:

  • 加密后无法直接查询,需要使用辅助查询列
  • 加密会影响性能,谨慎使用
  • 密钥需要安全存储,建议使用密钥管理服务

7.4 分布式主键生成策略

除了Snowflake算法,ShardingSphere还支持其他主键生成策略。

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:
          # 雪花算法
          snowflake:
            type: SNOWFLAKE
            props:
              max-vibration-offset: 1  # 最大抖动偏移量
              max-tolerate-time-difference-milliseconds: 10  # 最大容忍时间差(毫秒)

主键生成器类型:

生成器类型 说明 特点
SNOWFLAKE 雪花算法 分布式、高性能、趋势递增
UUID UUID 无序、长度长
NIL 不生成主键 需要手动指定

使用建议:

  • 优先使用Snowflake算法
  • 性能敏感场景考虑调整max-tolerate-time-difference-milliseconds
  • 确保主键唯一性

7.5 SQL解析与改写

ShardingSphere支持SQL解析和改写,这是其核心能力之一。

功能说明:

  1. 自动路由 - 根据分片键自动路由到目标分片
  2. 结果归并 - 合并多个分片的结果
  3. SQL改写 - 修改SQL以适应分片场景

示例:

java 复制代码
// 原始SQL
SELECT * FROM t_user WHERE user_id = 100

// 改写后的SQL(路由到ds0.t_user_0)
SELECT * FROM ds0.t_user_0 WHERE user_id = 100

使用建议:

  • 理解SQL改写原理,优化SQL语句
  • 避免全路由查询,性能较差
  • 使用绑定表避免跨库JOIN

7.6 配置中心

ShardingSphere支持配置中心,实现动态配置管理。

支持的配置中心:

  • Zookeeper
  • Nacos
  • Etcd
  • Apollo

配置示例:

yaml 复制代码
spring:
  shardingsphere:
    mode:
      type: Cluster
      repository:
        type: Zookeeper
        props:
          namespace: sharding-sphere
          server-lists: 127.0.0.1:2181
          retry-interval-milliseconds: 500
          max-retries: 3
          time-to-live-milliseconds: 5000

优势:

  • 配置动态更新,无需重启
  • 配置集中管理
  • 配置版本控制

使用建议:

  • 生产环境使用配置中心
  • 配置变更需要灰度发布
  • 配置需要备份

八、生产环境最佳实践

7.1 配置优化

数据源配置:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      ds0:
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          connection-test-query: SELECT 1

ShardingSphere配置:

yaml 复制代码
spring:
  shardingsphere:
    props:
      # 生产环境关闭SQL显示
      sql-show: false
      # 允许没有分片列的插入通过主键生成器
      check-table-metadata-enabled: false
      # 核心执行线程池大小
      kernel-executor-size: 16

7.2 性能优化

1. 使用绑定表

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        binding-tables:
          - t_order,t_order_item

优势:

  • 避免跨库JOIN
  • 提高查询性能
  • 简化事务处理

2. 避免全路由

java 复制代码
// 不推荐:不包含分片键,全路由
List<User> users = userMapper.selectAll();

// 推荐:包含分片键,精确路由
User user = userMapper.selectById(100L);

3. 合理使用索引

sql 复制代码
-- 在分片键上创建索引
CREATE INDEX idx_user_id ON t_user(user_id);

-- 在常用查询字段上创建索引
CREATE INDEX idx_username ON t_user(username);

7.3 监控告警

监控指标:

  1. 数据库指标

    • 连接数使用率
    • 慢查询数量
    • 锁等待时间
  2. 应用指标

    • SQL执行时间
    • SQL错误率
    • 分片路由时间
  3. 业务指标

    • 请求响应时间
    • 请求成功率
    • 请求吞吐量

7.4 安全加固

1. SQL注入防护

java 复制代码
// 不推荐:字符串拼接SQL
String sql = "SELECT * FROM t_user WHERE username = '" + username + "'";

// 推荐:使用参数化查询
@Select("SELECT * FROM t_user WHERE username = #{username}")
User selectByUsername(String username);

2. 权限控制

sql 复制代码
-- 创建只读用户
CREATE USER 'readonly'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON test_db.* TO 'readonly'@'%';

-- 创建读写用户
CREATE USER 'readwrite'@'%' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON test_db.* TO 'readwrite'@'%';

7.5 运维建议

1. 定期备份

bash 复制代码
# 备份数据库
mysqldump -h 127.0.0.1 -u root -p test_db1 > test_db1_backup.sql

# 恢复数据库
mysql -h 127.0.0.1 -u root -p test_db1 < test_db1_backup.sql

2. 定期清理

sql 复制代码
-- 清理过期数据
DELETE FROM t_user WHERE created_time < DATE_SUB(NOW(), INTERVAL 1 YEAR);

-- 优化表
OPTIMIZE TABLE t_user_0;
OPTIMIZE TABLE t_user_1;

九、常见问题与解决方案

9.1 分片键自动生成问题

问题: INSERT语句不包含分片键,导致路由失败。

解决方案:

yaml 复制代码
spring:
  shardingsphere:
    props:
      check-table-metadata-enabled: false
java 复制代码
@Insert("INSERT INTO t_user (username, email, age) VALUES (#{username}, #{email}, #{age})")
int insert(User user);

9.2 全路由问题

问题: 查询不包含分片键,导致全路由,性能较差。

解决方案:

java 复制代码
// 不推荐:全路由
List<User> users = userMapper.selectAll();

// 推荐:精确路由
User user = userMapper.selectById(100L);

9.3 跨库JOIN问题

问题: 跨库JOIN性能较差或无法执行。

解决方案:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        binding-tables:
          - t_order,t_order_item

9.4 主键冲突问题

问题: 分布式主键生成器配置错误,导致主键冲突。

解决方案:

yaml 复制代码
spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:
          snowflake:
            type: SNOWFLAKE

9.5 连接池耗尽问题

问题: 连接池配置不合理,导致连接池耗尽。

解决方案:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      ds0:
        hikari:
          minimum-idle: 10
          maximum-pool-size: 50
          connection-timeout: 30000

9.6 Hint分片不生效问题

问题: 使用HintManager指定分片后,查询仍然路由到所有分片。

解决方案:

java 复制代码
// 确保使用try-with-resources或手动关闭HintManager
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.addTableShardingValue("t_order", 0);
    return orderMapper.selectById(orderId);
}

// 或者手动关闭
HintManager hintManager = HintManager.getInstance();
try {
    hintManager.addTableShardingValue("t_order", 0);
    return orderMapper.selectById(orderId);
} finally {
    hintManager.close();
}

9.7 读写分离主从延迟问题

问题: 读写分离导致主从延迟,读取到旧数据。

解决方案:

java 复制代码
// 强制读主库
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.setWriteRouteOnly();
    return userMapper.selectById(userId);
}

十、总结

ShardingSphere-JDBC是一个功能强大的分库分表中间件,本文档详细介绍了其配置和使用方法。

核心要点:

  1. 分片键选择 - 选择高频查询字段作为分片键
  2. 分片算法 - 使用INLINE算法,配置简单,性能优秀
  3. 绑定表 - 使用绑定表避免跨库JOIN
  4. 主键生成 - 使用Snowflake算法保证唯一性
  5. 避免全路由 - 查询语句最好包含分片键

最佳实践:

  1. 优先使用INLINE算法,性能好
  2. 避免全路由查询,性能差
  3. 使用绑定表,避免跨库操作
  4. 配置合理的连接池大小
  5. 生产环境关闭sql-show
  6. 定期备份,防止数据丢失

参考资料:

相关推荐
源代码•宸35 分钟前
goframe框架签到系统项目开发(实现总积分和积分明细接口、补签日期校验)
后端·golang·postman·web·dao·goframe·补签
无限进步_41 分钟前
【C语言】堆(Heap)的数据结构与实现:从构建到应用
c语言·数据结构·c++·后端·其他·算法·visual studio
初次攀爬者42 分钟前
基于知识库的知策智能体
后端·ai编程
喵叔哟42 分钟前
16.项目架构设计
后端·docker·容器·.net
强强强79543 分钟前
python代码实现es文章内容向量化并搜索
后端
A黑桃1 小时前
Paimon 表定时 Compact 数据流程与逻辑详解
后端
掘金者阿豪1 小时前
JVM由简入深学习提升分(生产项目内存飙升分析)
后端
天天摸鱼的java工程师1 小时前
RocketMQ 与 Kafka 对比:消息队列选型的核心考量因素
java·后端
星浩AI1 小时前
10 行代码带你上手 LangChain 智能 Agent
人工智能·后端
洛卡卡了1 小时前
从活动编排到积分系统:事件驱动在业务系统中的一次延伸
前端·后端·面试