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

核心要点

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

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

相关推荐
小陈工4 小时前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
科技小花8 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸8 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain8 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希9 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神9 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员9 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java9 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿10 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴10 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存