前言
本文基于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 节点分布
plaintext
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为分片键参与分库分表。
sql
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分片键,保证同订单数据同库同表,杜绝跨库笛卡尔积。
sql
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分片,与订单、明细同库同表域,保障资金事务一致性。
sql
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)
全局公共数据表,所有分片库自动同步数据,避免业务跨库查询字典数据。
sql
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)
非分片公共表,统一存储于主库,不参与分片路由。
sql
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事务幂等、空回滚、悬挂三大生产问题,记录每笔分布式事务执行状态,所有分片库需创建。
sql
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模式核心依赖表,存储事务数据快照,实现自动回滚和事务幂等,所有分片库必须创建。
sql
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日志表建表脚本,保证事务正常生效。
sql
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,解决版本冲突问题。
xml
<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自动代理,兼容双事务模式,无废弃配置、无版本冲突。
yaml
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)
java
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)
java
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)
java
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)
java
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)
java
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
java
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
java
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
java
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
java
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
<?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
<?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
<?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
<?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
java
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模式测试接口
java
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事务顶层接口
java
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接口实现类(解决三大生产问题)
java
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业务服务层
java
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模式测试接口
java
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_no、transaction_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 生产上线步骤
-
环境初始化:创建双分片库,执行所有业务表、undo_log、TCC日志表建表语句
-
中间件部署:启动Nacos、Seata、Redis,完成注册配置中心绑定
-
配置校验:核对分片规则、读写分离、事务配置,无语法报错
-
功能自测:测试AT/TCC事务提交、回滚、幂等、分页全场景
-
压测验证:完成高并发压测,达标后灰度上线
-
监控值守:上线后重点监控路由异常、事务失败、SQL拦截指标
结语
本手册基于生产稳定技术栈,完整落地了分库分表+读写分离+双模式分布式事务全套方案,覆盖架构设计、表结构、配置、代码、避坑、监控、扩容、上线全流程。所有代码可直接复用,所有规范适配千万级数据、高并发、资金级一致性生产场景,彻底解决分库分表架构下的数据路由、跨库事务、性能瓶颈、线上异常等核心问题。