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

核心要点

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

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

相关推荐
欢喜躲在眉梢里6 小时前
基于 openFuyao 社区的无硬件 UB 开发实战指南
运维·数据库·人工智能·vscode·ai·开发工具·go开发
蜂蜜黄油呀土豆6 小时前
MySQL 一行记录是如何存储的?—— 从磁盘文件到 InnoDB 行格式的完整拆解
数据库·mysql·表空间·innodb
光羽隹衡6 小时前
SQL中表删除与表修改
数据库·sql
l1t6 小时前
苏旭晖先生写的纯SQL求解Advent of Code 2025第9题 最大矩形面积 第2部分
数据库·sql·算法·计算几何·duckdb·advent of code
是垚不是土6 小时前
基于Blackbox Exporter的网络服务黑盒监控体系实践
网络·数据库·安全·http·微服务·prometheus
青w韵6 小时前
Claude 高级工具使用解析:从上下文优化到程序化调用的工程实践
数据库·windows
Hello.Reader7 小时前
Flink SQL DROP 语句安全删除 Catalog / Table / DB / View / Function / Model
数据库·sql·flink
luluoluoa7 小时前
科研业绩SCI、EI、会议、CSCD、CSSCI、中文核心、普刊介绍
数据库
liuzhilongDBA7 小时前
论文精读|DBAIOps
数据库·postgresql·dbaiops