Sharding-JDBC5.5.3分库分表生产实战手册

0 阅读22分钟

前言

本文基于SpringBoot3.5.x、Sharding-JDBC5.5.3稳定版、MyBatis-Flex、Seata1.8技术栈,落地电商订单分库分表生产方案。完整实现分库分表、读写分离、绑定表、广播表、分片分页聚合功能,同时兼容Seata AT、TCC双模式分布式事务。所有配置及代码适配生产环境,可直接复用,解决海量数据存储、高并发写入、跨库数据一致性核心问题。

一、业务与架构设计

1.1 业务场景

模拟电商订单核心业务,覆盖用户下单、订单管理、订单明细、支付流水、数据字典、用户管理等场景。解决单表数据量过大、高并发写入瓶颈、跨库事务数据不一致问题,适配千万级数据、资金级事务一致性的生产需求。

1.2 分片架构规则

采用user_id单一分片键复合分库分表架构,保证同一用户的订单、明细、支付数据落库一致,从架构层面规避跨库JOIN、跨库事务冗余问题,提升查询及事务性能。

1.2.1 分库规则

  • 数据库节点:order_db_0、order_db_1

  • 分片字段:user_id

  • 分片算法:user_id % 2

  • 路由规则:余数0路由order_db_0,余数1路由order_db_1

1.2.2 分表规则

  • 单库分表数量:4张业务分片表

  • 分片表名:t_order_0~3、t_order_item_0~3、t_pay_log_0~3

  • 分片字段:user_id

  • 分片算法:user_id % 4

1.2.3 节点分布

order_db_0:t_order_0、t_order_1、t_order_2、t_order_3
order_db_1:t_order_0、t_order_1、t_order_2、t_order_3

1.3 数据库表结构

项目包含分片表、绑定表、广播表、普通单库表、事务日志表,完全适配Sharding-JDBC分片规则及Seata事务机制,所有分片库需统一执行建表脚本。

1.3.1 主订单分片表(t_order)

核心业务分片表,存储订单核心数据,以user_id为分片键参与分库分表。

CREATE TABLE `t_order_0` (
  `id` bigint NOT NULL COMMENT '分布式主键',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户分片键',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `order_status` tinyint NOT NULL DEFAULT '0' COMMENT '订单状态 0-待支付 1-已支付 2-已发货 3-已完成 4-已取消',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主订单分片表';

1.3.2 订单明细绑定表(t_order_item)

与主订单表绑定关联,共用user_id分片键,保证同订单数据同库同表,杜绝跨库笛卡尔积。

CREATE TABLE `t_order_item_0` (
  `id` bigint NOT NULL COMMENT '分布式主键',
  `order_id` bigint NOT NULL COMMENT '订单主键ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户分片键',
  `goods_id` bigint NOT NULL COMMENT '商品ID',
  `goods_name` varchar(255) NOT NULL COMMENT '商品名称',
  `goods_num` int NOT NULL DEFAULT '1' COMMENT '购买数量',
  `goods_price` decimal(10,2) NOT NULL COMMENT '商品单价',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_order_id` (`order_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单详情绑定分片表';

1.3.3 支付流水分片表(t_pay_log)

资金业务分片表,跟随user_id分片,与订单、明细同库同表域,保障资金事务一致性。

CREATE TABLE `t_pay_log_0` (
  `id` bigint NOT NULL COMMENT '分布式主键',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户分片键',
  `pay_amount` decimal(10,2) NOT NULL COMMENT '支付金额',
  `pay_type` tinyint NOT NULL DEFAULT '1' COMMENT '支付方式 1-微信 2-支付宝 3-银行卡',
  `pay_status` tinyint NOT NULL DEFAULT '0' COMMENT '支付状态 0-待支付 1-支付成功 2-支付失败',
  `transaction_id` varchar(64) DEFAULT NULL COMMENT '第三方支付流水号',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_order_no` (`order_no`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付流水分片表';

1.3.4 数据字典广播表(t_dict)

全局公共数据表,所有分片库自动同步数据,避免业务跨库查询字典数据。

CREATE TABLE `t_dict` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `dict_code` varchar(64) NOT NULL COMMENT '字典编码',
  `dict_name` varchar(128) NOT NULL COMMENT '字典名称',
  `dict_value` varchar(255) DEFAULT NULL COMMENT '字典值',
  `sort` int DEFAULT '0' COMMENT '排序',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态 0-禁用 1-正常',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公共字典广播表';

1.3.5 用户单库表(t_user)

非分片公共表,统一存储于主库,不参与分片路由。

CREATE TABLE `t_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(32) NOT NULL COMMENT '用户名',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '用户状态',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户单库表';

1.3.6 TCC事务状态表(tcc_transaction_log)

用于解决TCC事务幂等、空回滚、悬挂三大生产问题,记录每笔分布式事务执行状态,所有分片库需创建。

CREATE TABLE `tcc_transaction_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `xid` varchar(128) NOT NULL COMMENT '全局事务ID',
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `biz_no` varchar(128) NOT NULL COMMENT '业务唯一单号',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '事务状态 0-TRY中 1-CONFIRM成功 2-CANCEL回滚',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_xid_branch` (`xid`,`branch_id`),
  UNIQUE KEY `uk_biz_no` (`biz_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TCC事务状态日志表';

1.3.7 Seata AT回滚日志表(undo_log)

AT模式核心依赖表,存储事务数据快照,实现自动回滚和事务幂等,所有分片库必须创建

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `xid` varchar(100) NOT NULL COMMENT '全局事务ID',
  `context` varchar(128) NOT NULL COMMENT '上下文信息',
  `rollback_info` longblob NOT NULL COMMENT '回滚快照数据',
  `log_status` int NOT NULL COMMENT '日志状态 0-正常 1-已删除',
  `log_created` datetime NOT NULL COMMENT '日志创建时间',
  `log_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '日志修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Seata AT事务回滚日志表';

二、项目技术栈

全部采用生产稳定版本,规避测试版、RC版已知BUG,适配高可用生产架构。

  • 基础框架:SpringBoot 3.5.0

  • 运行环境:JDK 17

  • ORM框架:MyBatis-Flex 1.8.0

  • 分片中间件:Sharding-JDBC 5.5.3(生产稳定版)

  • 连接池:Druid 1.2.20

  • 数据库:MySQL 8.0

  • 分布式事务:Seata 1.8.0(兼容AT/TCC双模式)

  • 缓存:Redis 7.x

  • 工具依赖:Lombok、Hutool 5.8.32

三、环境搭建与依赖配置

3.1 数据库初始化

创建两个分片业务库,两个库均需执行全部业务表、undo_log表、TCC日志表建表脚本,保证事务正常生效。

CREATE DATABASE order_db_0 DEFAULT CHARACTER SET utf8mb4;
CREATE DATABASE order_db_1 DEFAULT CHARACTER SET utf8mb4;

3.2 基础环境要求

  • 搭建MySQL一主一从架构,主库写入、从库读取,实现读写分离

  • 部署Seata1.8服务端,对接Nacos注册配置中心,数据库存储事务日志

  • 本地/服务器安装Redis,用于业务缓存及事务辅助场景

3.3 Maven核心依赖

统一稳定版本,适配SpringBoot3及Sharding-JDBC5.5.3,解决版本冲突问题。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
</parent>

<dependencies>
    <!-- Web核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Sharding-JDBC 5.5.3稳定版 -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
        <version>5.5.3</version>
    </dependency>

    <!-- MyBatis-Flex -->
    <dependency>
        <groupId>com.mybatis-flex</groupId>
        <artifactId>mybatis-flex-spring-boot-starter</artifactId>
        <version>1.8.0</version>
    </dependency>

    <!-- Druid连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.20</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Seata分布式事务 -->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.8.0</version>
    </dependency>

    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 工具类 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.32</version>
    </dependency>
</dependencies>

四、核心配置文件

适配Sharding-JDBC5.5.3新版语法,整合分库分表、读写分离、Seata AT自动代理,兼容双事务模式,无废弃配置、无版本冲突。

spring:
  # Sharding-JDBC核心配置
  shardingsphere:
    # 多数据源主从配置
    datasource:
      names: master0,slave0,master1,slave1
      # 0号分片库主从
      master0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/order_db_0?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: 123456
      slave0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3307/order_db_0?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: 123456
      # 1号分片库主从
      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: 123456
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3307/order_db_1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: 123456
    # 读写分离规则
    rules:
      readwrite-splitting:
        data-sources:
          ds0:
            write-data-source-name: master0
            read-data-source-names: [slave0]
          ds1:
            write-data-source-name: master1
            read-data-source-names: [slave1]
        load-balancers:
          round_robin:
            type: ROUND_ROBIN
    # 分片规则配置
    sharding:
      # 分库策略
      database-strategy:
        standard:
          sharding-column: user_id
          sharding-algorithm-name: db-mod
      # 分表策略
      tables:
        t_order:
          actual-data-nodes: ds${0..1}.t_order_${0..3}
          table-strategy:
            standard:
              sharding-column: user_id
              sharding-algorithm-name: table-mod
        t_order_item:
          actual-data-nodes: ds${0..1}.t_order_item_${0..3}
          table-strategy:
            standard:
              sharding-column: user_id
              sharding-algorithm-name: table-mod
        t_pay_log:
          actual-data-nodes: ds${0..1}.t_pay_log_${0..3}
          table-strategy:
            standard:
              sharding-column: user_id
              sharding-algorithm-name: table-mod
      # 绑定表:关联表同分片规则,避免跨库查询
      binding-tables:
        - t_order,t_order_item,t_pay_log
      # 广播表:全库同步数据
      broadcast-tables:
        - t_dict
      # 分片算法定义
      sharding-algorithms:
        db-mod:
          type: MOD
          props:
            sharding-count: 2
        table-mod:
          type: MOD
          props:
            sharding-count: 4
    # 雪花算法主键配置
    key-generators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 1
    # 生产级SQL治理
    props:
      sql-show: true
      sql-parser-cache: true
      disable-full-table-scan: true

# Seata双模式事务配置
seata:
  enabled: true
  application-id: order-sharding-service
  tx-service-group: default_tx_group
  # 自动代理多数据源(AT事务核心)
  auto-proxy:
    datasource: true
  # Nacos注册中心
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: public
      group: SEATA_GROUP
  # Nacos配置中心
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: public
      group: SEATA_GROUP
  # TCC模式配置
  tcc:
    resource:
      enable: true
    timeout: 30000
    max-retry-times: 2
  # 事务日志存储
  store:
    mode: db
  # 全局事务超时时间
  global-transaction-timeout: 60000
  # AT事务日志自动清理
  undo:
    log-expire-time: 10080
    log-clean-period: 60
  # 事务重试机制
  retry:
    branch-timeout-retry: true
    commit-retry-count: 5
    rollback-retry-count: 5

五、实体类代码实现

5.1 订单实体(TOrder)

package com.sharding.entity;

import com.mybatis.flex.annotation.Id;
import com.mybatis.flex.annotation.Table;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体类
 * 分片键:userId
 * 对应分片表:t_order_0 ~ t_order_3
 * 支持Seata AT自动回滚事务
 */
@Data
@Table("t_order")
public class TOrder {

    /** 分布式雪花主键 */
    @Id
    private Long id;

    /** 订单唯一编号 */
    private String orderNo;

    /** 用户ID(核心分片路由字段) */
    private Long userId;

    /** 订单总金额 */
    private BigDecimal totalAmount;

    /** 订单状态:0-待支付 1-已支付 2-已发货 3-已完成 4-已取消 */
    private Integer orderStatus;

    /** 订单支付时间 */
    private LocalDateTime payTime;

    /** 数据创建时间 */
    private LocalDateTime createTime;

    /** 数据更新时间 */
    private LocalDateTime updateTime;
}

5.2 订单明细实体(TOrderItem)

package com.sharding.entity;

import com.mybatis.flex.annotation.Id;
import com.mybatis.flex.annotation.Table;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单明细实体类
 * 绑定t_order主表,分片键与主表一致
 * 属于绑定表,保证同用户数据同库同表
 */
@Data
@Table("t_order_item")
public class TOrderItem {

    /** 分布式主键 */
    @Id
    private Long id;

    /** 关联主订单ID */
    private Long orderId;

    /** 订单编号 */
    private String orderNo;

    /** 用户分片键 */
    private Long userId;

    /** 商品ID */
    private Long goodsId;

    /** 商品名称 */
    private String goodsName;

    /** 购买数量 */
    private Integer goodsNum;

    /** 商品单价 */
    private BigDecimal goodsPrice;

    /** 创建时间 */
    private LocalDateTime createTime;
}

5.3 支付流水实体(TPayLog)

package com.sharding.entity;

import com.mybatis.flex.annotation.Id;
import com.mybatis.flex.annotation.Table;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 支付流水实体类
 * 资金业务分片表,参与订单绑定关联
 * 支持Seata AT、TCC分布式事务
 */
@Data
@Table("t_pay_log")
public class TPayLog {

    /** 分布式主键 */
    @Id
    private Long id;

    /** 关联订单编号 */
    private String orderNo;

    /** 用户分片键 */
    private Long userId;

    /** 实际支付金额 */
    private BigDecimal payAmount;

    /** 支付方式:1-微信 2-支付宝 3-银行卡 */
    private Integer payType;

    /** 支付状态:0-待支付 1-支付成功 2-支付失败 */
    private Integer payStatus;

    /** 第三方支付交易流水号 */
    private String transactionId;

    /** 创建时间 */
    private LocalDateTime createTime;

    /** 更新时间 */
    private LocalDateTime updateTime;
}

5.4 TCC事务日志实体(TccTransactionLog)

package com.sharding.entity;

import com.mybatis.flex.annotation.Id;
import com.mybatis.flex.annotation.Table;
import lombok.Data;
import java.time.LocalDateTime;

/**
 * TCC事务状态日志实体
 * 解决TCC三大核心问题:幂等、空回滚、悬挂
 * 记录每笔分布式事务的执行阶段与状态
 */
@Data
@Table("tcc_transaction_log")
public class TccTransactionLog {

    /** 自增主键 */
    @Id
    private Long id;

    /** Seata全局事务ID */
    private String xid;

    /** 分支事务ID */
    private Long branchId;

    /** 业务唯一单号(订单号) */
    private String bizNo;

    /** 事务状态:0-TRY执行中 1-CONFIRM已完成 2-CANCEL已回滚 */
    private Integer status;

    /** 创建时间 */
    private LocalDateTime createTime;

    /** 更新时间 */
    private LocalDateTime updateTime;
}

5.5 下单业务DTO(OrderTccDTO)

package com.sharding.dto;

import lombok.Data;
import java.math.BigDecimal;

/**
 * 下单业务传输对象
 * 统一适配AT、TCC双事务模式传参
 */
@Data
public class OrderTccDTO {
    /** 用户ID(分片核心键) */
    private Long userId;

    /** 订单编号 */
    private String orderNo;

    /** 订单总金额 */
    private BigDecimal totalAmount;

    /** 商品ID */
    private Long goodsId;

    /** 商品名称 */
    private String goodsName;

    /** 购买数量 */
    private Integer goodsNum;

    /** 商品单价 */
    private BigDecimal goodsPrice;

    /** 支付金额 */
    private BigDecimal payAmount;

    /** 支付方式 */
    private Integer payType;
}

六、Mapper接口代码

基于MyBatis-Flex通用BaseMapper实现基础CRUD,自定义业务专属方法,适配事务回滚、状态更新场景。

6.1 OrderMapper

package com.sharding.mapper;

import com.mybatis.flex.core.BaseMapper;
import com.sharding.entity.TOrder;
import org.apache.ibatis.annotations.Param;

public interface OrderMapper extends BaseMapper<TOrder> {

    /**
     * 根据订单号更新订单状态
     * @param orderNo 订单编号
     * @param status 目标状态
     * @return 影响行数
     */
    int updateStatusByOrderNo(@Param("orderNo") String orderNo, @Param("status") Integer status);

    /**
     * 根据订单号删除订单数据(TCC回滚专用)
     * @param orderNo 订单编号
     * @return 影响行数
     */
    int deleteByOrderNo(@Param("orderNo") String orderNo);
}

6.2 OrderItemMapper

package com.sharding.mapper;

import com.mybatis.flex.core.BaseMapper;
import com.sharding.entity.TOrderItem;
import org.apache.ibatis.annotations.Param;

public interface OrderItemMapper extends BaseMapper<TOrderItem> {

    /**
     * 根据订单号删除明细数据(TCC回滚专用)
     * @param orderNo 订单编号
     * @return 影响行数
     */
    int deleteByOrderNo(@Param("orderNo") String orderNo);
}

6.3 PayLogMapper

package com.sharding.mapper;

import com.mybatis.flex.core.BaseMapper;
import com.sharding.entity.TPayLog;
import org.apache.ibatis.annotations.Param;

public interface PayLogMapper extends BaseMapper<TPayLog> {

    /**
     * 根据订单号更新支付状态
     * @param orderNo 订单编号
     * @param status 支付状态
     * @return 影响行数
     */
    int updatePayStatusByOrderNo(@Param("orderNo") String orderNo, @Param("status") Integer status);

    /**
     * 根据订单号删除支付流水(TCC回滚专用)
     * @param orderNo 订单编号
     * @return 影响行数
     */
    int deleteByOrderNo(@Param("orderNo") String orderNo);
}

6.4 TccTransactionLogMapper

package com.sharding.mapper;

import com.mybatis.flex.core.BaseMapper;
import com.sharding.entity.TccTransactionLog;
import org.apache.ibatis.annotations.Param;

public interface TccTransactionLogMapper extends BaseMapper<TccTransactionLog> {

    /**
     * 根据全局事务ID+分支ID查询事务记录
     * @param xid 全局事务ID
     * @param branchId 分支事务ID
     * @return 事务记录
     */
    TccTransactionLog getByXidAndBranch(@Param("xid") String xid, @Param("branchId") Long branchId);

    /**
     * 判断分支事务是否已执行(幂等校验)
     * @param xid 全局事务ID
     * @param branchId 分支事务ID
     * @return true-已存在 false-不存在
     */
    boolean existsByXidAndBranch(@Param("xid") String xid, @Param("branchId") Long branchId);
}

七、Mapper XML映射文件

7.1 OrderMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sharding.mapper.OrderMapper">
    <update id="updateStatusByOrderNo">
        UPDATE t_order
        SET order_status = #{status}, update_time = NOW()
        WHERE order_no = #{orderNo}
    </update>

    <delete id="deleteByOrderNo">
        DELETE FROM t_order WHERE order_no = #{orderNo}
    </delete>
</mapper>

7.2 OrderItemMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sharding.mapper.OrderItemMapper">
    <delete id="deleteByOrderNo">
        DELETE FROM t_order_item WHERE order_no = #{orderNo}
    </delete>
</mapper>

7.3 PayLogMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sharding.mapper.PayLogMapper">
    <update id="updatePayStatusByOrderNo">
        UPDATE t_pay_log
        SET pay_status = #{status}, update_time = NOW()
        WHERE order_no = #{orderNo}
    </update>

    <delete id="deleteByOrderNo">
        DELETE FROM t_pay_log WHERE order_no = #{orderNo}
    </delete>
</mapper>

7.4 TccTransactionLogMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sharding.mapper.TccTransactionLogMapper">
    <select id="getByXidAndBranch" resultType="com.sharding.entity.TccTransactionLog">
        SELECT * FROM tcc_transaction_log
        WHERE xid = #{xid} AND branch_id = #{branchId}
    </select>

    <select id="existsByXidAndBranch" resultType="boolean">
        SELECT COUNT(1) > 0 FROM tcc_transaction_log
        WHERE xid = #{xid} AND branch_id = #{branchId}
    </select>
</mapper>

八、Seata AT模式落地实现

AT模式零代码侵入,基于数据库快照实现自动回滚,适配绝大多数分库分表业务,是生产首选分布式事务方案。

8.1 AT模式业务Service

package com.sharding.service;

import cn.hutool.core.util.IdUtil;
import com.sharding.dto.OrderTccDTO;
import com.sharding.entity.TOrder;
import com.sharding.entity.TOrderItem;
import com.sharding.entity.TPayLog;
import com.sharding.mapper.OrderItemMapper;
import com.sharding.mapper.OrderMapper;
import com.sharding.mapper.PayLogMapper;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Seata AT模式订单业务服务
 * 特性:零代码侵入、自动快照、跨库自动回滚
 * 适配Sharding-JDBC分库分表架构
 */
@Service
@RequiredArgsConstructor
public class OrderAtService {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final PayLogMapper payLogMapper;

    /**
     * AT模式分布式事务下单
     * @GlobalTransactional 开启全局事务,异常自动跨库回滚
     * @param orderDTO 下单参数
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class)
    public void createOrderByAt(OrderTccDTO orderDTO) {
        // 1.新增主订单数据
        TOrder order = new TOrder();
        order.setId(IdUtil.getSnowflakeNextId());
        order.setOrderNo(orderDTO.getOrderNo());
        order.setUserId(orderDTO.getUserId());
        order.setTotalAmount(orderDTO.getTotalAmount());
        order.setOrderStatus(1);
        orderMapper.insert(order);

        // 2.新增订单明细数据
        TOrderItem orderItem = new TOrderItem();
        orderItem.setId(IdUtil.getSnowflakeNextId());
        orderItem.setOrderId(order.getId());
        orderItem.setOrderNo(orderDTO.getOrderNo());
        orderItem.setUserId(orderDTO.getUserId());
        orderItem.setGoodsId(orderDTO.getGoodsId());
        orderItem.setGoodsName(orderDTO.getGoodsName());
        orderItem.setGoodsNum(orderDTO.getGoodsNum());
        orderItem.setGoodsPrice(orderDTO.getGoodsPrice());
        orderItemMapper.insert(orderItem);

        // 3.新增支付流水数据
        TPayLog payLog = new TPayLog();
        payLog.setId(IdUtil.getSnowflakeNextId());
        payLog.setOrderNo(orderDTO.getOrderNo());
        payLog.setUserId(orderDTO.getUserId());
        payLog.setPayAmount(orderDTO.getPayAmount());
        payLog.setPayType(orderDTO.getPayType());
        payLog.setPayStatus(1);
        payLogMapper.insert(payLog);

        // 打开注释可测试异常自动回滚
        // int error = 1 / 0;
    }
}

8.2 AT模式测试接口

package com.sharding.controller;

import com.sharding.dto.OrderTccDTO;
import com.sharding.service.OrderAtService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * AT模式事务测试接口
 * 支持正常提交、异常自动回滚全场景测试
 */
@RestController
@RequestMapping("/order/at")
@RequiredArgsConstructor
public class OrderAtController {

    private final OrderAtService orderAtService;

    @PostMapping("/create")
    public String createAtOrder(@RequestBody OrderTccDTO orderDTO) {
        orderAtService.createOrderByAt(orderDTO);
        return "AT模式下单成功,分布式事务提交完成";
    }
}

九、Seata TCC模式落地实现

TCC模式手动实现三阶段事务,解决AT模式不支持的特殊场景,手动处理幂等、空回滚、悬挂问题,适配资金核心高并发业务。

9.1 TCC事务顶层接口

package com.sharding.tcc;

import com.sharding.dto.OrderTccDTO;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * 订单TCC事务接口
 * 本地TCC模式,无远程调用,三阶段标准实现
 * Try-资源预占、Confirm-事务提交、Cancel-事务回滚
 */
@LocalTCC
public interface OrderTccAction {

    @TwoPhaseBusinessAction(
            name = "orderTccAction",
            commitMethod = "confirm",
            rollbackMethod = "cancel"
    )
    boolean tryCreateOrder(
            BusinessActionContext context,
            @BusinessActionContextParameter(paramName = "orderDTO") OrderTccDTO orderTccDTO
    );

    /**
     * 事务确认提交
     * @param context 事务上下文
     * @return 执行结果
     */
    boolean confirm(BusinessActionContext context);

    /**
     * 事务回滚撤销
     * @param context 事务上下文
     * @return 执行结果
     */
    boolean cancel(BusinessActionContext context);
}

9.2 TCC接口实现类(解决三大生产问题)

package com.sharding.tcc.impl;

import cn.hutool.core.util.IdUtil;
import com.sharding.dto.OrderTccDTO;
import com.sharding.entity.TOrder;
import com.sharding.entity.TOrderItem;
import com.sharding.entity.TPayLog;
import com.sharding.entity.TccTransactionLog;
import com.sharding.mapper.OrderItemMapper;
import com.sharding.mapper.OrderMapper;
import com.sharding.mapper.PayLogMapper;
import com.sharding.mapper.TccTransactionLogMapper;
import com.sharding.tcc.OrderTccAction;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

/**
 * TCC事务核心实现
 * 彻底解决:幂等性、空回滚、悬挂 三大生产问题
 */
@Service
@RequiredArgsConstructor
public class OrderTccActionImpl implements OrderTccAction {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final PayLogMapper payLogMapper;
    private final TccTransactionLogMapper tccTransactionLogMapper;

    /**
     * Try阶段:参数校验+幂等判断+资源预占+事务记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean tryCreateOrder(BusinessActionContext context, OrderTccDTO orderDTO) {
        String xid = context.getXid();
        long branchId = context.getBranchId();
        String bizNo = orderDTO.getOrderNo();

        // 幂等拦截:已执行过的分支事务直接返回
        if (tccTransactionLogMapper.existsByXidAndBranch(xid, branchId)) {
            return true;
        }

        // 业务参数校验
        if (orderDTO.getGoodsNum() <= 0 || orderDTO.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("订单参数非法");
        }

        // 预创建订单(待确认状态)
        TOrder order = new TOrder();
        order.setId(IdUtil.getSnowflakeNextId());
        order.setOrderNo(bizNo);
        order.setUserId(orderDTO.getUserId());
        order.setTotalAmount(orderDTO.getTotalAmount());
        order.setOrderStatus(0);
        orderMapper.insert(order);

        // 预创建订单明细
        TOrderItem orderItem = new TOrderItem();
        orderItem.setId(IdUtil.getSnowflakeNextId());
        orderItem.setOrderId(order.getId());
        orderItem.setOrderNo(bizNo);
        orderItem.setUserId(orderDTO.getUserId());
        orderItem.setGoodsId(orderDTO.getGoodsId());
        orderItem.setGoodsName(orderDTO.getGoodsName());
        orderItem.setGoodsNum(orderDTO.getGoodsNum());
        orderItem.setGoodsPrice(orderDTO.getGoodsPrice());
        orderItemMapper.insert(orderItem);

        // 预创建支付流水
        TPayLog payLog = new TPayLog();
        payLog.setId(IdUtil.getSnowflakeNextId());
        payLog.setOrderNo(bizNo);
        payLog.setUserId(orderDTO.getUserId());
        payLog.setPayAmount(orderDTO.getPayAmount());
        payLog.setPayType(orderDTO.getPayType());
        payLog.setPayStatus(0);
        payLogMapper.insert(payLog);

        // 记录TRY事务状态,防空回滚、悬挂
        TccTransactionLog transactionLog = new TccTransactionLog();
        transactionLog.setXid(xid);
        transactionLog.setBranchId(branchId);
        transactionLog.setBizNo(bizNo);
        transactionLog.setStatus(0);
        tccTransactionLogMapper.insert(transactionLog);

        return true;
    }

    /**
     * Confirm阶段:固化业务数据,完成事务提交
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean confirm(BusinessActionContext context) {
        String xid = context.getXid();
        long branchId = context.getBranchId();

        // 幂等拦截:已确认事务直接返回
        TccTransactionLog transactionLog = tccTransactionLogMapper.getByXidAndBranch(xid, branchId);
        if (transactionLog == null || transactionLog.getStatus() == 1) {
            return true;
        }

        // 更新业务状态为成功
        String bizNo = transactionLog.getBizNo();
        orderMapper.updateStatusByOrderNo(bizNo, 1);
        payLogMapper.updatePayStatusByOrderNo(bizNo, 1);

        // 更新事务状态为已完成
        transactionLog.setStatus(1);
        tccTransactionLogMapper.updateById(transactionLog);
        return true;
    }

    /**
     * Cancel阶段:回滚清理数据,释放资源
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancel(BusinessActionContext context) {
        String xid = context.getXid();
        long branchId = context.getBranchId();

        // 空回滚拦截:无TRY记录,无需回滚
        TccTransactionLog transactionLog = tccTransactionLogMapper.getByXidAndBranch(xid, branchId);
        if (transactionLog == null) {
            return true;
        }

        // 幂等拦截:已回滚事务直接返回
        if (transactionLog.getStatus() == 2) {
            return true;
        }

        // 清理所有预占数据
        String bizNo = transactionLog.getBizNo();
        orderMapper.deleteByOrderNo(bizNo);
        orderItemMapper.deleteByOrderNo(bizNo);
        payLogMapper.deleteByOrderNo(bizNo);

        // 更新事务状态为已回滚
        transactionLog.setStatus(2);
        tccTransactionLogMapper.updateById(transactionLog);
        return true;
    }
}

9.3 TCC业务服务层

package com.sharding.service;

import cn.hutool.core.lang.UUID;
import com.sharding.dto.OrderTccDTO;
import com.sharding.tcc.OrderTccAction;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * TCC事务业务服务
 * 对外提供下单能力,开启全局事务
 */
@Service
@RequiredArgsConstructor
public class OrderTccService {

    private final OrderTccAction orderTccAction;

    @GlobalTransactional(rollbackFor = Exception.class)
    public String createOrderByTcc(OrderTccDTO orderDTO) {
        // 生成唯一订单号
        orderDTO.setOrderNo("ORD_" + UUID.fastUUID().toString(true));
        // 执行TCC三阶段下单流程
        orderTccAction.tryCreateOrder(null, orderDTO);
        return "TCC模式下单成功,分布式事务执行完成";
    }
}

9.4 TCC模式测试接口

package com.sharding.controller;

import com.sharding.dto.OrderTccDTO;
import com.sharding.service.OrderTccService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * TCC模式事务测试接口
 * 覆盖正常提交、异常回滚、幂等重试、空回滚全场景
 */
@RestController
@RequestMapping("/order/tcc")
@RequiredArgsConstructor
public class OrderTccController {

    private final OrderTccService orderTccService;

    @PostMapping("/create")
    public String createTccOrder(@RequestBody OrderTccDTO orderDTO) {
        return orderTccService.createOrderByTcc(orderDTO);
    }
}

十、生产核心避坑与SQL治理规范

本章为Sharding-JDBC分库分表生产落地核心规范,解决线上全表扫描、分片路由失效、分页错误、事务失效、扩容异常等高频问题,适配千万级数据高并发生产环境。

10.1 禁止全表扫描生产强制规范

文档配置中已开启disable-full-table-scan: true强制拦截全表扫描SQL,生产严格遵循以下规则,避免SQL路由全库全表导致服务雪崩:

  • 必须携带分片键查询:所有业务查询、更新、删除SQL,必须携带user_id分片键,无分片键SQL直接拦截,杜绝广播路由

  • 禁止模糊无索引查询:禁止对分片表、绑定表执行无条件模糊查询、无条件排序分页,仅允许精准分片路由查询

  • 唯一字段查询适配:通过order_notransaction_id等唯一字段查询时,需提前建立缓存映射(订单号→user_id),解析分片键后再路由查询

  • 聚合查询限制:禁止跨分片复杂聚合、子查询、笛卡尔积关联,绑定表仅支持同分片内JOIN查询

10.2 分片分页生产踩坑解决方案

Sharding-JDBC默认分页仅支持单分片分页,跨分片普通分页会出现数据重复、分页错乱、数据缺失问题,生产统一采用以下方案:

  • 中小数据量:使用Sharding-JDBC内置LimitOffsetPagination分页,依托分片键精准单分片路由,规避跨分片分页问题

  • 大数据量分页:采用**游标分页(ID分页)**替代offset分页,禁止大offset偏移查询,避免全分片数据汇总导致OOM

  • 跨分片聚合分页:通过中间层聚合排序分页,优先查询各分片分页数据,内存轻量化聚合后返回结果,限制最大分页条数

10.3 线上高频异常与解决方案

10.3.1 分片路由失效问题

问题现象:参数为空、参数类型不匹配导致分片算法失效,触发全表扫描或路由异常。 解决方案:业务层强制校验分片键user_id非空、类型合法,禁止null/空字符串入参;统一分片键数据库类型、实体类类型,避免类型转换异常导致路由偏差。

10.3.2 跨库事务超时/回滚失败

问题现象:高并发下跨库事务超时、undo_log残留、事务悬挂。 解决方案:优化全局事务超时时间适配业务耗时,开启undo_log自动清理机制;TCC模式严格实现幂等、空回滚、拦截逻辑,避免事务状态脏数据。

10.3.3 读写分离路由错乱

问题现象:写操作路由从库、读操作路由主库,主从延迟导致数据查询不一致。 解决方案:Sharding-JDBC自动区分读写操作,强制DML语句走主库、DQL语句走从库;核心资金、订单实时查询业务,强制指定走主库查询,规避主从延迟。

10.4 分片扩容生产方案

当前架构为2库4表固定分片,生产数据量溢出时,采用倍数扩容+数据平滑迁移方案,避免停机扩容、数据重分布错乱:

  • 扩容规则:仅支持倍数扩容(2库→4库、4库→8库),保证原有分片数据路由规则兼容,无需修改业务代码

  • 扩容流程:新增分片数据源→配置新分片规则→双写校验→增量数据同步→存量数据迁移→灰度切换路由→下线旧分片

  • 核心保障:扩容期间开启双写,保证新旧分片数据一致,灰度切换无业务中断

十一、监控告警与压测规范

11.1 Sharding-JDBC核心监控指标

接入Prometheus+Grafana监控,核心观测指标如下,异常阈值接入告警机制:

  • 分片路由成功率、全表扫描拦截次数、非法SQL拦截次数

  • 各分片库读写QPS、响应耗时、连接池使用率

  • Seata事务提交率、回滚率、事务超时次数、undo_log残留数量

  • TCC事务TRY/CONFIRM/CANCEL执行成功率、幂等重试次数

11.2 生产压测标准

上线前必须完成压测,满足以下生产阈值方可投产:

  • 单节点下单接口QPS≥1000,平均响应耗时≤50ms

  • 跨库事务成功率100%,无事务悬挂、数据不一致问题

  • 高并发下无全表扫描、无路由异常、无数据库连接耗尽

  • 扩容后数据路由精准,无数据丢失、重复、错位问题

十二、版本兼容与上线规范

12.1 版本兼容说明

本方案所有组件版本严格适配,杜绝版本冲突,生产禁止随意升级版本:

  • Sharding-JDBC5.5.3仅适配SpringBoot3.x,不兼容SpringBoot2.x,降级需同步调整所有配置语法

  • Seata1.8.0兼容AT/TCC双模式,高版本Seata部分注解、配置废弃,升级需校验事务逻辑

  • MyBatis-Flex1.8.0适配当前分片主键生成规则,更换ORM框架需重写Mapper通用方法

12.2 生产上线步骤

  1. 环境初始化:创建双分片库,执行所有业务表、undo_log、TCC日志表建表语句

  2. 中间件部署:启动Nacos、Seata、Redis,完成注册配置中心绑定

  3. 配置校验:核对分片规则、读写分离、事务配置,无语法报错

  4. 功能自测:测试AT/TCC事务提交、回滚、幂等、分页全场景

  5. 压测验证:完成高并发压测,达标后灰度上线

  6. 监控值守:上线后重点监控路由异常、事务失败、SQL拦截指标

结语

本手册基于生产稳定技术栈,完整落地了分库分表+读写分离+双模式分布式事务全套方案,覆盖架构设计、表结构、配置、代码、避坑、监控、扩容、上线全流程。所有代码可直接复用,所有规范适配千万级数据、高并发、资金级一致性生产场景,彻底解决分库分表架构下的数据路由、跨库事务、性能瓶颈、线上异常等核心问题。