12、新增数据到DB的几大方案

声明

  • 本文内容由AI工具生成
  • 生成工具:[DeepSeek]
  • 生成时间:[2025年12月15日]
  • 版权声明:本文遵循CC BY-NC 4.0协议



项目场景:

假设我要新增 1万条数据,如果 前面5000条,都是成功,并且也提交了,那我后面的数据怎么办?


这是一个非常经典的批量数据处理中的中间状态问题,涉及到事务控制、幂等性和错误处理策略。让我详细分析几种解决方案:

一、核心问题分析

你的场景本质上是:部分数据已提交,后续数据如何处理? 这需要根据业务需求来决定。

二、解决方案(按业务需求分类)

方案A:要求强一致性(要么全成功,要么全失败)

如果1万条数据是一个业务整体,需要保证原子性:

java 复制代码
// 伪代码示例:使用事务控制保证原子性
@Transactional(rollbackFor = Exception.class)
public void batchInsert(List<Data> dataList) {
    try {
        // 全部成功后才提交
        for (Data data : dataList) {
            insertData(data);
        }
        // 只有所有数据都插入成功,才会提交事务
    } catch (Exception e) {
        // 任何一条失败,全部回滚
        throw new RuntimeException("批量插入失败,已全部回滚", e);
    }
}

关键点

  • 使用数据库事务,在方法级别控制
  • 任何一条失败,整个事务回滚
  • 前5000条不会提前提交

方案B:允许部分成功(业务容忍部分失败)

如果数据相对独立,可以接受部分成功:

java 复制代码
public void batchInsertWithPartialSuccess(List<Data> dataList) {
    List<Data> successList = new ArrayList<>();
    List<Data> failedList = new ArrayList<>();
    
    for (int i = 0; i < dataList.size(); i++) {
        try {
            // 逐条插入,每条独立事务
            dataMapper.insert(dataList.get(i));
            successList.add(dataList.get(i));
            
            // 可以每N条记录一次进度
            if (i % 1000 == 0) {
                log.info("已成功插入 {} 条数据", i + 1);
            }
        } catch (Exception e) {
            // 记录失败的数据
            failedList.add(dataList.get(i));
            log.error("第{}条数据插入失败: {}", i + 1, e.getMessage());
            // 继续处理下一条
        }
    }
    
    // 处理结果
    log.info("批量插入完成:成功 {} 条,失败 {} 条", 
             successList.size(), failedList.size());
    
    // 对失败的数据可以进行重试或记录到文件
    if (!failedList.isEmpty()) {
        retryFailedData(failedList);
    }
}

方案C:分批提交(折中方案)

java 复制代码
public void batchInsertByChunks(List<Data> dataList, int chunkSize) {
    List<List<Data>> chunks = splitIntoChunks(dataList, chunkSize);
    List<Data> allFailed = new ArrayList<>();
    
    for (int i = 0; i < chunks.size(); i++) {
        try {
            // 每个分片一个事务
            insertChunk(chunks.get(i));
            log.info("第 {} 批数据({}条)插入成功", 
                    i + 1, chunks.get(i).size());
        } catch (Exception e) {
            // 当前分片失败,记录但继续下一个分片
            allFailed.addAll(chunks.get(i));
            log.error("第 {} 批数据插入失败", i + 1);
        }
    }
    
    // 处理失败的数据
    handleFailedData(allFailed);
}

三、生产环境最佳实践

1. 使用Spring Batch等批处理框架

yaml 复制代码
# Spring Batch配置示例
spring:
  batch:
    job:
      enabled: true
    initialize-schema: always

2. 实现幂等性设计

java 复制代码
// 使用唯一约束或业务主键保证幂等
@Table(uniqueConstraints = {
    @UniqueConstraint(columnNames = {"businessKey1", "businessKey2"})
})
public class YourEntity {
    // 确保相同业务数据不会重复插入
}

3. 设计状态跟踪机制

java 复制代码
// 1. 创建批处理任务记录
public class BatchTask {
    private Long id;
    private String taskId;          // 任务唯一ID
    private Integer totalCount;     // 总数据量
    private Integer successCount;   // 成功数量
    private Integer failedCount;    // 失败数量
    private String status;          // 状态:PROCESSING/SUCCESS/PARTIAL_SUCCESS/FAILED
    private LocalDateTime startTime;
    private LocalDateTime endTime;
}

// 2. 记录每条数据的处理状态
public class DataProcessRecord {
    private Long id;
    private Long batchTaskId;       // 关联批处理任务
    private String dataId;          // 原始数据ID
    private String status;          // 状态:PENDING/SUCCESS/FAILED
    private String errorMsg;        // 错误信息
    private Integer retryCount;     // 重试次数
}

4. 完整处理流程

java 复制代码
public class BatchDataProcessor {
    
    public ProcessResult processBatchData(List<Data> dataList) {
        // 1. 创建批处理任务记录
        BatchTask task = createBatchTask(dataList.size());
        
        // 2. 分批处理(比如每1000条一个批次)
        List<DataChunkResult> chunkResults = new ArrayList<>();
        List<List<Data>> chunks = splitIntoChunks(dataList, 1000);
        
        for (int i = 0; i < chunks.size(); i++) {
            DataChunkResult result = processChunk(task.getId(), chunks.get(i), i);
            chunkResults.add(result);
            
            // 3. 实时更新任务进度
            updateTaskProgress(task.getId(), chunkResults);
            
            // 4. 可以设置暂停点(比如每处理完一批休息一下)
            if (needPause(i)) {
                pauseProcessing(task.getId());
                break; // 暂停,下次可以从断点继续
            }
        }
        
        // 5. 生成处理报告
        return generateReport(task.getId(), chunkResults);
    }
    
    // 支持从断点继续
    public void resumeBatchTask(Long taskId) {
        BatchTask task = getTask(taskId);
        List<Data> remainingData = getUnprocessedData(taskId);
        processBatchData(remainingData);
    }
}

四、针对你的具体场景建议

如果已经发生了前5000条成功的情况:

  1. 先别动已成功的数据
  2. 分析失败原因
    • 是程序bug还是数据问题?
    • 是数据库连接超时还是唯一键冲突?
  3. 根据原因选择处理方式
java 复制代码
// 场景1:程序异常导致中断,数据本身没问题
public void resumeInsert(List<Data> remainingData) {
    // 直接从第5001条开始继续
    for (int i = 5000; i < remainingData.size(); i++) {
        try {
            insertData(remainingData.get(i));
        } catch (DuplicateKeyException e) {
            // 如果是重复数据,跳过或更新
            handleDuplicateData(remainingData.get(i));
        }
    }
}

// 场景2:数据格式有问题导致后面失败
public void handleDataQualityIssue(List<Data> allData) {
    // 1. 先校验所有数据
    List<Data> validData = validateData(allData);
    
    // 2. 删除已插入的有问题的数据(如果需要)
    if (needCleanup) {
        deleteProblematicData(allData.subList(0, 5000));
    }
    
    // 3. 重新插入所有有效数据
    batchInsertWithTransaction(validData);
}

五、防患于未然的设计原则

  1. 先校验,再插入

    java 复制代码
    // 预处理阶段
    ValidationResult result = validateAllData(dataList);
    if (result.hasErrors()) {
        // 提前处理问题数据
        return;
    }
  2. 使用消息队列异步处理

    java 复制代码
    // 将数据发送到消息队列,消费者逐条处理
    dataList.forEach(data -> {
        rabbitTemplate.convertAndSend("data.insert.queue", data);
    });
  3. 实现断点续传机制

    • 记录每个数据的处理状态
    • 支持从任意位置重新开始

六、总结建议

对于你目前的项目学习,我建议:

  1. 先实现最简单的版本:全部成功或全部失败(方案A)
  2. 逐步增加复杂度:添加分批提交和失败重试
  3. 学习成熟框架:了解Spring Batch的实现原理
  4. 在生产环境中:一定要有完善的状态跟踪和补偿机制

核心要点

  • 业务需求决定技术方案
  • 永远考虑幂等性和数据一致性
  • 记录详细日志,方便问题排查
  • 设计可恢复的系统,而不是永不失败的系统

你遇到这个问题,说明你已经从单纯的功能实现,开始思考更深入的系统设计了,这是很好的进步!在实际工作中,这类问题非常常见,处理得好能大大提升系统的健壮性。

相关推荐
wangbing112515 小时前
平台介绍-开放API后台微服务
数据库·微服务·架构
高一要励志成为佬15 小时前
【数据库】第三章 关系数据库标准语言SQL
数据库·sql
尽兴-15 小时前
MySQL执行UPDATE语句的全流程深度解析
数据库·mysql·innodb·dba·存储引擎·update
MXM_77715 小时前
laravel 并发控制写法-涉及资金
java·数据库·oracle
进阶的小名15 小时前
[超轻量级消息队列(MQ)] Redis 不只是缓存:我用 Redis Stream 实现了一个 MQ(自定义注解方式)
数据库·spring boot·redis·缓存·消息队列·个人开发
列御寇15 小时前
MongoDB分片集群——分片键(Shard Keys)概述
数据库·mongodb
oMcLin15 小时前
如何在Ubuntu 22.04 LTS上通过配置ZFS存储池,提升高吞吐量数据库的读写性能与可靠性?
linux·数据库·ubuntu
Cx330❀16 小时前
脉脉平台深度测评:【AI创作者xAMA】从职场社交到AI创作赋能
数据库·人工智能·脉脉
f***241116 小时前
Bug侦探团:破解技术悬案的秘密武器
数据库
Li_76953216 小时前
Redis 进阶(八)—— 分布式锁
数据库·redis·分布式