Spring MVC事务隔离全攻略:从脏读到串行化的高并发实战指南

在这里插入图片描述

肖哥弹架构 跟大家“弹弹” Spring MVC设计与实战应用,需要代码关注

欢迎 点赞,关注,转发。

关注公号Solomon肖哥弹架构获取更多精彩内容

历史热点文章

“当你的支付系统出现金额错乱,当你的秒杀商品遭遇超卖,90%的问题都源于事务隔离级别的错误选择!”

本文将为你彻底解密Spring MVC中的事务隔离机制,通过电商、金融等真实案例。

🔥 精准选择隔离级别

  • 4大隔离级别深度对比(附原理时序图)
  • 20+生产环境代码示例
  • 高并发场景下的性能与一致性平衡术

💎 核心内容
✅ 从READ UNCOMMITTED到SERIALIZABLE的完整应用场景
✅ 电商系统全链路隔离级别实战(订单/库存/支付)
✅ 隔离级别导致的6大经典生产事故复盘
✅ Spring事务注解的高级配置技巧

🚀 性能优化亮点

  • 监控大屏:如何用READ UNCOMMITTED实现毫秒级响应
  • 秒杀系统:READ COMMITTED+乐观锁的完美搭配
  • 财务对账:REPEATABLE READ的分批处理技巧
  • 银行转账:SERIALIZABLE的行级锁优化方案

一、事务隔离架构时序图

在这里插入图片描述

1. 关键组件说明

  1. 客户端应用

    • 通过JDBC/ORM框架(如MyBatis)操作数据库
    • 设置事务边界和隔离级别
    // Spring示例
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void businessMethod() {
        // 业务代码
    }
    
  2. 连接池

    • 管理物理连接(如HikariCP)

    • 关键配置参数:

      spring:
        datasource:
          hikari:
            isolation-level: READ_COMMITTED # 默认隔离级别
            max-lifetime: 180000 # 连接存活时间(ms)
      
  3. MySQL服务层

    • 事务协调器(Transaction Coordinator)

    • 隔离级别实现逻辑:

      -- 查看当前隔离级别
      SELECT @@transaction_isolation;
      
      -- 设置全局/会话级别
      SET GLOBAL transaction_isolation = 'REPEATABLE-READ';
      SET SESSION transaction_isolation = 'READ-COMMITTED';
      

二、 事务如何选择?

// 根据业务动态选择隔离级别
@Transactional(isolation = 
    systemConfig.isHighConsistency() ? 
        Isolation.SERIALIZABLE : 
        Isolation.READ_COMMITTED
)
public void businessOperation() {
    // ...
}

关键原则

  1. 默认使用数据库的默认级别(MySQL默认为REPEATABLE_READ

  2. 在以下情况升级隔离级别:

    • 涉及金钱操作 → SERIALIZABLE
    • 报表生成 → REPEATABLE_READ
  3. 在以下情况降级隔离级别:

    • 允许短暂不一致的统计 → READ_UNCOMMITTED
    • 高并发写入场景 → READ_COMMITTED

三、掌握事务5种隔离策略

1. READ_UNCOMMITTED(读未提交)

原理图(实时监控场景)

在这里插入图片描述

适用场景特点:对数据准确性要求低,但对实时性要求极高的非关键业务

在这里插入图片描述

业务场景
// 实时监控系统(允许脏读)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public BigDecimal getRealtimeSales() {
    // 实时统计销售额(允许读到未提交的临时数据)
    return salesDao.sumUncommittedOrders();
}

必须使用场景

  1. 实时监控大屏

    • 如物流仓库的温度监控大屏,需要每秒刷新数据,短暂显示异常值可接受
    • 必要性:牺牲准确性换取毫秒级延迟,若用更高级别会导致数据延迟明显
  2. 网站实时在线人数

    • 显示"当前正在浏览人数"的近似值
    • 必要性:用户理解这是估算值,系统可承受±5%误差
  3. 游戏实时排行榜

    • 玩家战力榜每分钟自动刷新
    • 必要性:允许排名短暂跳动,避免锁竞争导致卡顿
  4. IoT设备状态看板

    • 展示工厂设备实时运行状态(如转速、电压)
    • 必要性:设备状态变化极快,需优先保证刷新速度
  5. 社交媒体在线状态

    • 显示好友"2分钟前在线"的提示
    • 必要性:状态延迟比短暂错误更影响体验
错误用法后果
// 错误:在支付系统中使用READ_UNCOMMITTED
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void checkBalance(Long userId) {
    BigDecimal balance = accountDao.getBalance(userId); // 可能读到未提交的脏数据
    if (balance.compareTo(price) < 0) {
        throw new RuntimeException("余额不足"); // 可能误判
    }
}

后果:用户看到错误的余额导致支付失败投诉。

2. READ_COMMITTED(读已提交)

原理图(库存扣减场景)

在这里插入图片描述

适用场景特点:需要看到最新已确认数据,但允许结果变化的常规业务

在这里插入图片描述

业务场景
// 电商订单处理(避免脏读)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void processOrder(Long orderId) {
    Order order = orderDao.findById(orderId); // 读到已提交数据
    if (order.getStatus().equals("PAID")) {
        inventoryDao.deduct(order.getProductId()); // 扣减库存
    }
}

必须使用场景

  1. 电商订单支付

    • 支付成功后更新订单状态
    • 必要性:必须确保状态是已确认的,但支付记录可分多次查询
  2. 银行ATM余额查询

    • 查询卡内可用余额
    • 必要性:需显示最新确认金额,但允许查询后立即发生转账
  3. 机票预订系统

    • 用户查询航班余票
    • 必要性:需反映实时库存,但两次搜索间可能减少
  4. 新闻评论审核

    • 展示已通过审核的评论
    • 必要性:避免显示未审核内容,但新评论可实时增加
  5. 库存预占系统

    • 下单前检查商品库存
    • 必要性:读取已确认库存,但允许后续并发扣减
错误用法后果
// 错误:在对账系统中使用READ_COMMITTED
@Transactional(isolation = Isolation.READ_COMMITTED)
public void reconcile() {
    BigDecimal start = salesDao.getDailyTotal(); // 第一次查询
    // ...其他操作
    BigDecimal end = salesDao.getDailyTotal();   // 第二次查询结果可能不同
    generateReport(start, end); // 报表数据不一致
}

后果:财务对账不平,需人工干预核查。

3. REPEATABLE_READ(可重复读)

原理图(财务对账场景)

在这里插入图片描述

适用场景特点:需要事务内多次读取结果一致的长期操作

在这里插入图片描述

业务场景
// 生成对账单(保证多次读取一致)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Report generateDailyReport() {
    BigDecimal morningSales = salesDao.getTotalSales(); // 时间点快照
    // ...其他操作
    BigDecimal eveningSales = salesDao.getTotalSales(); // 与morningSales一致
    return new Report(morningSales, eveningSales);
}

必须使用场景

  1. 财务月末对账

    • 生成银行账户日终对账单
    • 必要性:对账期间账户余额必须冻结
  2. 数据迁移工具

    • 将旧系统数据迁移到新库
    • 必要性:迁移过程需要数据快照一致性
  3. 销售月报生成

    • 统计月度销售数据生成PDF
    • 必要性:报表需反映统计开始时的完整状态
  4. 购物车价格计算

    • 结算时计算商品总价
    • 必要性:防止价格在计算中途被修改
  5. 数据库备份

    • 生产环境在线热备份
    • 必要性:备份文件需要保持数据关联一致性
错误用法后果
// 错误:在实时库存检查中使用REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public boolean checkStock(Long productId) {
    Integer stock = inventoryDao.getStock(productId); // 快照读
    // 其他服务此时可能已修改库存
    return stock > 0; // 返回过期数据
}

后果:超卖问题,实际库存不足但系统允许下单。

4. SERIALIZABLE(串行化)

原理图(银行转账场景)

在这里插入图片描述

适用场景特点:不允许任何并发冲突的关键业务

在这里插入图片描述

业务场景
// 银行转账(绝对隔离)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountDao.findById(fromId);
    Account to = accountDao.findById(toId);
    if (from.getBalance().compareTo(amount) >= 0) {
        accountDao.deduct(fromId, amount);
        accountDao.add(toId, amount); // 其他事务无法并发修改这两个账户
    }
}

必须使用场景

  1. 银行转账系统

    • 从账户A向账户B转账
    • 必要性:必须绝对防止并发操作导致金额计算错误
  2. 机票选座系统

    • 用户锁定航班座位
    • 必要性:避免两个用户同时选择同一座位
  3. 医院处方系统

    • 医生开具药品并扣库存
    • 必要性:确保麻醉类药品不会超发
  4. 拍卖系统结标

    • 确定最终中标者
    • 必要性:防止多个出价同时被确认为获胜者
  5. 区块链交易打包

    • 验证并打包新区块交易
    • 必要性:交易顺序必须严格串行化记录
错误用法后果
// 错误:在商品列表查询中使用SERIALIZABLE
@Transactional(isolation = Isolation.SERIALIZABLE)
public List<Product> listProducts() {
    return productDao.findAll(); // 全表加锁
}

后果

  1. 系统并发性能骤降
  2. 用户浏览商品时卡顿超时

四、 事务隔离对比总结表

隔离级别原理简述必须使用的业务场景代码注解提示
READ_UNCOMMITTED允许读取未提交数据实时监控/大数据看板// 允许脏读,性能优先
READ_COMMITTED只读已提交数据订单状态更新/余额查询// 避免脏读,允许不可重复读
REPEATABLE_READ快照读(MVCC实现)财务对账/数据导出// 保证同一事务内读取一致性
SERIALIZABLE全表锁(完全串行)银行转账/库存核销// 绝对隔离,性能代价高

五、事务隔离优化方案

1. READ UNCOMMITTED(读未提交)优化

问题:数据可能脏读,但要求极快响应
优化口诀“非关键数据,缓存兜底”

具体方法

  1. 给监控大屏加Redis缓存层,设置1秒自动过期:
---------------------物流实时位置大屏
@Service
public class LogisticsService {
    // 使用Redis缓存降低数据库压力
    @Cacheable(value = "vehicle_positions", key = "#vehicleId", ttl = 1) // 1秒过期
    public Position getCurrentPosition(String vehicleId) {
        // 注意:这里故意不使用事务,允许读取未提交数据
        return jdbcTemplate.queryForObject(
            "SELECT latitude, longitude FROM vehicle_positions WHERE vehicle_id=?",
            (rs, rowNum) -> new Position(rs.getDouble(1), rs.getDouble(2)),
            vehicleId
        );
    }

    // 异步修正脏数据
    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    public void fixDirtyData() {
        List<Position> dirtyPositions = getDirtyReads();
        dirtyPositions.forEach(pos -> {
            Position committedPos = getCommittedPosition(pos.getVehicleId());
            if (!pos.equals(committedPos)) {
                redisTemplate.opsForValue().set(
                    "vehicle_positions::" + pos.getVehicleId(), 
                    committedPos
                );
            }
        });
    }
}

2. READ COMMITTED(读已提交)优化

问题:可能不可重复读,但并发要高
优化口诀“短平快,乐观锁”

具体方法

  1. 把下单事务拆成多个小事务:

在这里插入图片描述

  1. 用版本号避免锁等待:
-- 商品表添加version字段
UPDATE products 
SET stock=stock-1, version=version+1 
WHERE id=100 AND version=5;
----------------------电商订单支付
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderDao orderDao;
    private final InventoryDao inventoryDao;

    // 短事务+乐观锁控制
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void payOrder(Long orderId) {
        // 阶段1:快速校验
        Order order = orderDao.findByIdForUpdate(orderId);
        if (order.getStatus() != OrderStatus.UNPAID) {
            throw new IllegalStateException("订单状态异常");
        }

        // 阶段2:库存扣减(乐观锁)
        boolean stockDeducted = inventoryDao.deductWithVersion(
            order.getProductId(), 
            order.getQuantity(),
            order.getInventoryVersion()
        );
        if (!stockDeducted) {
            throw new ConcurrentModificationException("库存并发修改");
        }

        // 阶段3:支付核心(新事务)
        paymentTransaction(order);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void paymentTransaction(Order order) {
        paymentGateway.charge(order);
        orderDao.updateStatus(order.getId(), OrderStatus.PAID);
    }
}

3. REPEATABLE READ(可重复读)优化

问题:长事务导致性能差
优化口诀“快照读,分批搞”

具体方法

  1. 数据迁移时分批提交:
@Transactional
public void migrateData() {
    List<Data> batch = queryBatch(1000); // 每次查1000条
    batch.forEach(this::processSingleRecord);
}

@Transactional(propagation = REQUIRES_NEW) // 每批新事务
void processSingleRecord(Data data) {
    targetDao.insert(data);
}
  1. 对账时手动加锁:
-- 对账前先锁定
SELECT * FROM accounts WHERE id=123 FOR UPDATE;
  1. 财务月末对账
@Service
public class ReconciliationService {
    
    // 分批处理+显式加锁
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void monthlyReconciliation(LocalDate month) {
        // 1. 锁定账户(避免对账期间数据变动)
        List<Account> accounts = accountDao.lockAccountsForReconciliation();
        
        // 2. 分批处理交易记录
        int batchSize = 1000;
        List<Transaction> batch;
        int offset = 0;
        
        do {
            batch = transactionDao.getBatch(month, offset, batchSize);
            reconcileBatch(accounts, batch);
            offset += batchSize;
            EntityManagerHelper.clear(); // 防止JPA缓存膨胀
        } while (!batch.isEmpty());
    }

    // 每批新事务提交
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void reconcileBatch(List<Account> accounts, List<Transaction> batch) {
        batch.forEach(tx -> {
            Account acc = accounts.stream()
                .filter(a -> a.getId().equals(tx.getAccountId()))
                .findFirst()
                .orElseThrow();
            acc.reconcile(tx);
        });
        accountDao.batchUpdate(accounts);
    }
}

4. SERIALIZABLE(串行化)优化

问题:性能差但必须强一致
优化口诀“精细化锁,异步排队”

具体方法

  1. 转账时精准锁定账户:
@Transactional
public void transfer(Long from, Long to) {
    // 只锁两个账户行
    accountDao.lockAccounts(List.of(from, to)); 
    // 执行转账...
}
  1. 用Redis队列串行化请求:
# 伪代码:转账请求排队
def transfer_task(amount):
    with redis.lock("account_lock"):
        process_transfer(amount)

# 所有请求入队
queue.enqueue(transfer_task, amount=100)
  1. 银行跨账户转账
@Service
public class BankTransferService {
    private final AccountDao accountDao;
    private final TransferQueue transferQueue;

    // 最终入口(异步化)
    public void transferAsync(TransferRequest request) {
        transferQueue.enqueue(request); // 进入RabbitMQ/Kafka队列
    }

    // 实际处理(串行消费)
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void processTransfer(TransferRequest request) {
        // 1. 精准锁定两个账户(避免全表锁)
        List<Account> accounts = accountDao.lockAccountsByIds(
            List.of(request.fromAccountId(), request.toAccountId())
        );

        Account from = accounts.get(0);
        Account to = accounts.get(1);

        // 2. 业务校验
        if (from.getBalance().compareTo(request.amount()) < 0) {
            throw new InsufficientBalanceException();
        }

        // 3. 执行转账
        from.debit(request.amount());
        to.credit(request.amount());
        
        // 4. 记录流水(新事务)
        recordTransaction(request);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void recordTransaction(TransferRequest request) {
        transactionDao.insert(new Transaction(request));
    }
}

5. 通用优化技巧

  1. 查看当前锁情况(MySQL):
SHOW ENGINE INNODB STATUS;  -- 看锁等待
SELECT * FROM performance_schema.events_waits_current; -- 当前阻塞
-- 查看长事务
SELECT * FROM information_schema.innodb_trx; 
  1. 连接池配置(HikariCP示例):
spring:
  datasource:
    hikari:
      maximum-pool-size: 20      # 建议值:CPU核心数 * 2
      connection-timeout: 3000   # 3秒拿不到连接报错
      leak-detection-threshold: 5000 # 5秒未关闭连接报警
  1. 避免全表锁
-- 好的写法(使用索引)
SELECT * FROM orders WHERE user_id=100 FOR UPDATE;

-- 坏的写法(全表锁)
SELECT * FROM orders WHERE status='PAID' FOR UPDATE;
  1. 动态降级策略(高并发时):
// 根据系统负载自动降级
@Transactional(isolation = getCurrentIsolationLevel())
public void businessMethod() {
    // ...
}

Isolation getCurrentIsolationLevel() {
    return systemLoad > 80% ? Isolation.READ_COMMITTED 
                           : Isolation.REPEATABLE_READ;
}

6. 通用优化工具类

public class TransactionHelper {
    
    // 动态隔离级别切换
    public static <T> T executeWithIsolation(
        TransactionTemplate txTemplate,
        IsolationLevel level,
        Callable<T> task
    ) {
        txTemplate.setIsolationLevel(level.getValue());
        return txTemplate.execute(status -> {
            try {
                return task.call();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    // 监控事务耗时
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object monitorTransaction(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            Metrics.record("transaction_time", cost);
            if (cost > 500) {
                log.warn("Long transaction detected: {}ms", cost);
            }
        }
    }
}

7. 各场景配置建议

  1. Redis缓存配置(Spring Boot)
spring:
  cache:
    type: redis
    redis:
      time-to-live: 1s # 1秒过期
  datasource:
    hikari:
      isolation-level: READ_COMMITTED # 默认级别
  1. 批量处理参数(JPA)
# application.properties
spring.jpa.properties.hibernate.jdbc.batch_size=1000
spring.jpa.properties.hibernate.order_inserts=true
  1. 锁超时设置(MySQL)
SET GLOBAL innodb_lock_wait_timeout=3; -- 锁等待超时3秒

六、电商交易系统 - 隔离实战

四种隔离级别在实际业务中的应用。系统包含订单处理、库存管理、支付服务和数据报表等模块。

1. 项目结构

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── ecommerce/
│   │           ├── config/       # 配置类
│   │           ├── controller/   # 控制器
│   │           ├── model/        # 数据模型
│   │           ├── repository/   # 数据访问
│   │           ├── service/      # 业务服务
│   │           └── EcommerceApplication.java
│   └── resources/
│       ├── application.yml       # 应用配置
│       └── schema.sql            # 数据库脚本
└── test/                         # 测试类

2. READ UNCOMMITTED 应用 - 实时监控服务

@Service
@RequiredArgsConstructor
public class MonitorService {
    private final JdbcTemplate jdbcTemplate;
    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取实时订单统计(允许脏读)
     * 适用场景:管理员仪表盘显示近似实时数据
     * 优化:使用Redis缓存降低数据库压力
     */
    @Cacheable(value = "realtime_stats", key = "'order_count'", ttl = 1) // 1秒缓存
    public OrderStats getRealtimeOrderStats() {
        // 使用READ UNCOMMITTED查询未提交数据
        return jdbcTemplate.queryForObject(
            "SELECT " +
            "COUNT(*) as total, " +
            "SUM(CASE WHEN status='PAID' THEN 1 ELSE 0 END) as paid " +
            "FROM orders /* 不加锁 */", 
            (rs, rowNum) -> new OrderStats(
                rs.getInt("total"),
                rs.getInt("paid")
            )
        );
    }

    /**
     * 数据补偿任务:每小时修正一次脏数据
     */
    @Scheduled(cron = "0 0 * * * *")
    public void fixDirtyData() {
        OrderStats realStats = jdbcTemplate.queryForObject(
            "SELECT " +
            "COUNT(*) as total, " +
            "SUM(CASE WHEN status='PAID' THEN 1 ELSE 0 END) as paid " +
            "FROM orders WITH (NOLOCK)", 
            (rs, rowNum) -> new OrderStats(
                rs.getInt("total"),
                rs.getInt("paid")
            )
        );
        
        OrderStats cachedStats = (OrderStats) redisTemplate.opsForValue()
            .get("realtime_stats::order_count");
            
        if (!realStats.equals(cachedStats)) {
            redisTemplate.opsForValue().set(
                "realtime_stats::order_count",
                realStats
            );
        }
    }
}

3. READ COMMITTED 应用 - 订单服务

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;

    /**
     * 创建订单(READ COMMITTED隔离级别)
     * 适用场景:电商下单流程,需要看到已提交的库存数据
     * 优化:短事务 + 乐观锁控制
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Order createOrder(OrderRequest request) {
        // 阶段1:快速校验(非DB操作)
        validateRequest(request);
        
        // 阶段2:库存预占(新事务)
        InventoryLockResult lockResult = inventoryService.tryLockInventory(
            request.getProductId(), 
            request.getQuantity()
        );
        
        if (!lockResult.isSuccess()) {
            throw new InventoryException("库存不足");
        }

        // 阶段3:创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setStatus(OrderStatus.CREATED);
        
        Order savedOrder = orderRepository.save(order);
        
        // 阶段4:异步支付(不影响主事务)
        paymentService.processPaymentAsync(savedOrder.getId());
        
        return savedOrder;
    }

    // 验证逻辑(非事务)
    private void validateRequest(OrderRequest request) {
        if (request.getQuantity() <= 0) {
            throw new IllegalArgumentException("数量必须大于0");
        }
    }
}

4. REPEATABLE READ 应用 - 财务对账服务

@Service
@RequiredArgsConstructor
public class ReconciliationService {
    private final AccountRepository accountRepository;
    private final TransactionRepository transactionRepository;

    /**
     * 每日对账(REPEATABLE READ隔离级别)
     * 适用场景:需要事务内多次读取一致性的财务对账
     * 优化:分批处理 + 显式加锁
     */
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public ReconciliationResult dailyReconciliation(LocalDate date) {
        // 1. 锁定需要对的账户
        List<Account> accounts = accountRepository
            .findAllByIdForUpdate(getRelevantAccountIds(date));
        
        ReconciliationResult result = new ReconciliationResult();
        int batchSize = 500;
        int offset = 0;
        List<Transaction> batch;

        // 2. 分批处理交易记录
        do {
            batch = transactionRepository.findByDate(date, offset, batchSize);
            
            batch.forEach(tx -> {
                Account acc = accounts.stream()
                    .filter(a -> a.getId().equals(tx.getAccountId()))
                    .findFirst()
                    .orElseThrow();
                
                if (acc.reconcile(tx)) {
                    result.addMatched(tx);
                } else {
                    result.addMismatched(tx);
                }
            });
            
            offset += batchSize;
        } while (!batch.isEmpty());

        // 3. 更新账户状态
        accountRepository.saveAll(accounts);
        
        return result;
    }
}

5. SERIALIZABLE 应用 - 支付服务

@Service
@RequiredArgsConstructor
public class PaymentService {
    private final AccountRepository accountRepository;
    private final PaymentTransactionRepository transactionRepository;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    /**
     * 转账支付(SERIALIZABLE隔离级别)
     * 适用场景:需要绝对一致性的金融交易
     * 优化:异步队列 + 行级锁
     */
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void transfer(TransferRequest request) {
        // 1. 精确锁定两个账户(避免全表锁)
        List<Account> accounts = accountRepository
            .findAllByIdForUpdate(Arrays.asList(
                request.getFromAccountId(),
                request.getToAccountId()
            ));
        
        Account fromAccount = accounts.get(0);
        Account toAccount = accounts.get(1);

        // 2. 业务验证
        if (fromAccount.getBalance().compareTo(request.getAmount()) < 0) {
            throw new InsufficientBalanceException();
        }

        // 3. 执行转账
        fromAccount.debit(request.getAmount());
        toAccount.credit(request.getAmount());
        
        // 4. 记录交易(新事务)
        recordTransaction(request, fromAccount, toAccount);
        
        // 5. 发送通知(异步)
        kafkaTemplate.send("payment-events", 
            new PaymentEvent(request, PaymentStatus.COMPLETED));
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void recordTransaction(TransferRequest request, 
                                 Account from, Account to) {
        PaymentTransaction transaction = new PaymentTransaction();
        transaction.setFromAccount(from);
        transaction.setToAccount(to);
        transaction.setAmount(request.getAmount());
        transaction.setStatus(TransactionStatus.COMPLETED);
        transactionRepository.save(transaction);
    }
}

6. 数据库配置

-- schema.sql

-- 账户表:存储用户资金账户信息
CREATE TABLE accounts (
    id BIGINT PRIMARY KEY,                     -- 账户ID(主键)
    user_id BIGINT NOT NULL,                   -- 关联的用户ID
    balance DECIMAL(19,4) NOT NULL,            -- 当前余额(精确到4位小数)
    version INT NOT NULL DEFAULT 0,            -- 乐观锁版本号(用于并发控制)
    
    -- 约束条件
    CONSTRAINT chk_balance_non_negative CHECK (balance >= 0)  -- 余额不能为负数
) COMMENT '用户资金账户表';

-- 订单表:记录用户购买订单
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,                     -- 订单ID(主键)
    user_id BIGINT NOT NULL,                   -- 下单用户ID
    product_id BIGINT NOT NULL,                -- 商品ID
    quantity INT NOT NULL,                     -- 购买数量
    status VARCHAR(20) NOT NULL,               -- 订单状态(CREATED/PAID/SHIPPED等)
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,  -- 创建时间
    
    -- 索引
    INDEX idx_user_status (user_id, status),   -- 加速用户订单查询
    INDEX idx_created (created_at),            -- 按时间排序
    
    -- 约束条件
    CONSTRAINT chk_quantity_positive CHECK (quantity > 0)  -- 数量必须大于0
) COMMENT '用户订单表';

-- 库存表:商品库存管理
CREATE TABLE inventory (
    product_id BIGINT PRIMARY KEY,             -- 商品ID(主键)
    stock INT NOT NULL,                        -- 可用库存
    locked_stock INT NOT NULL DEFAULT 0,       -- 预占库存(下单未支付)
    version INT NOT NULL DEFAULT 0,            -- 乐观锁版本号
    
    -- 约束条件
    CONSTRAINT chk_stock_non_negative CHECK (stock >= 0),          -- 库存不能为负
    CONSTRAINT chk_locked_stock_non_negative CHECK (locked_stock >= 0),  -- 预占库存不能为负
    CONSTRAINT chk_total_stock CHECK (stock + locked_stock >= 0)   -- 总库存校验
) COMMENT '商品库存表';

-- 交易记录表:资金流水
CREATE TABLE transactions (
    id BIGINT PRIMARY KEY,                     -- 交易ID(主键)
    from_account_id BIGINT NOT NULL,           -- 转出账户
    to_account_id BIGINT NOT NULL,             -- 转入账户
    amount DECIMAL(19,4) NOT NULL,             -- 交易金额
    status VARCHAR(20) NOT NULL,               -- 交易状态(PROCESSING/SUCCESS/FAILED)
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,  -- 创建时间
    
    -- 索引
    INDEX idx_from_account (from_account_id),   -- 加速转出账户查询
    INDEX idx_to_account (to_account_id),       -- 加速转入账户查询
    INDEX idx_created_status (created_at, status),  -- 按时间和状态查询
    
    -- 约束条件
    CONSTRAINT chk_amount_positive CHECK (amount > 0),  -- 金额必须大于0
    CONSTRAINT fk_from_account FOREIGN KEY (from_account_id) REFERENCES accounts(id),
    CONSTRAINT fk_to_account FOREIGN KEY (to_account_id) REFERENCES accounts(id)
) COMMENT '资金交易流水表';

-- 初始化数据示例
INSERT INTO accounts (id, user_id, balance) VALUES 
(1001, 1, 5000.0000),  -- 用户1初始余额5000
(1002, 2, 3000.0000);  -- 用户2初始余额3000

INSERT INTO inventory (product_id, stock) VALUES
(101, 1000),  -- 商品ID 101 库存1000
(102, 500);   -- 商品ID 102 库存500

7. Spring Boot 配置

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ecommerce
    username: root
    password: password
    hikari:
      maximum-pool-size: 20
      connection-timeout: 3000
  jpa:
    show-sql: true
    properties:
      hibernate:
        jdbc:
          batch_size: 1000
        order_inserts: true
  cache:
    type: redis
    redis:
      time-to-live: 1s
  kafka:
    bootstrap-servers: localhost:9092

8. 测试用例

@SpringBootTest
class EcommerceApplicationTests {
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private MonitorService monitorService;

    @Test
    void testOrderCreate() {
        OrderRequest request = new OrderRequest(1L, 101L, 2);
        Order order = orderService.createOrder(request);
        assertNotNull(order.getId());
    }

    @Test
    void testHighConcurrencyPayment() {
        IntStream.range(0, 100).parallel().forEach(i -> {
            TransferRequest request = new TransferRequest(1000L, 1001L, 
                new BigDecimal("100.00"));
            paymentService.transfer(request);
        });
        
        Account account = accountRepository.findById(1000L).orElseThrow();
        assertTrue(account.getBalance().compareTo(BigDecimal.ZERO) >= 0);
    }

    @Test
    void testRealtimeStats() {
        OrderStats stats = monitorService.getRealtimeOrderStats();
        assertTrue(stats.getTotalOrders() >= 0);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Solomon_肖哥弹架构

你的欣赏就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值