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. 在生产环境中:一定要有完善的状态跟踪和补偿机制

核心要点

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

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

相关推荐
Tongpao_SSDHDD18 分钟前
希捷酷鹰ST6000VX008实测解析:中小安防监控高性价比存储方案
大数据·数据库·人工智能
蓝鸟197432 分钟前
Oracle超大DMP备份文件瘦身、日志精简、磁盘空间优化实战方案日志
数据库·oracle·数据库运维·生产运维实战·oracle避坑·磁盘空间优化·oracle日志清理
金融支付架构实战指南1 小时前
CQRS + 命令模式 + 事件驱动 + 数据库持久化
数据库·ddd·命令模式·领域驱动模型
sevenll071 小时前
DocKit agentic MongoDB GUI 客户端 - 用自然语言和你的数据对话
数据库·mongodb·nosql·agent·桌面客户端
团象科技1 小时前
从一线实操案例拆解不同出海团队落地海外VPS运维独立站的路径细节
大数据·数据库·人工智能
小马爱打代码2 小时前
框架 - 组件 - 中间件:生产级参数配置指引
数据库·中间件
asdfg12589632 小时前
一文通俗理解JDBC中的核心概念+案例
java·数据库·oracle·jdbc
点灯小铭2 小时前
基于单片机与DAC0832的双路波形信号发生系统设计
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业
小陈phd2 小时前
Text2SQL智能体学习笔记(二)——NL2SQL落地的隐形基石:元数据库
数据库·笔记·学习
霸道流氓气质2 小时前
阿里云 OSS 从零到实战:概念、配置与 Spring Boot 集成指南
数据库·spring boot·阿里云