编辑 | blame | 历史 | 原始文档

GPS 分段里程重复插入问题修复说明

问题描述

在执行 GPS 分段里程计算时,系统抛出以下异常:

org.springframework.jdbc.UncategorizedSQLException: 
### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '9-2026-03-15 06:10:00' for key 'tb_vehicle_gps_segment_mileage.uk_vehicle_time'

根本原因

数据库表 tb_vehicle_gps_segment_mileage 定义了唯一索引 uk_vehicle_time (vehicle_id, segment_start_time),用于确保同一车辆在同一时间段的分段里程记录只有一条。

虽然在代码层面(VehicleGpsSegmentMileageServiceImpl.java 第 390 行)已经有检查逻辑:

if (isSegmentAlreadyCalculated(vehicleId, segmentStartTime, segmentGpsList)) {
    previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
    continue;
}

但在**并发场景**下,仍可能出现竞态条件(Race Condition):

  1. 线程 A 检查时间段 2026-03-15 06:10:00,发现不存在
  2. 线程 B 同时检查同一时间段,也发现不存在
  3. 线程 A 执行 INSERT
  4. 线程 B 尝试执行 INSERT,违反唯一索引约束,抛出异常

解决方案

修改 MyBatis Mapper XML 文件中的 INSERT 语句,使用 MySQL 的 ON DUPLICATE KEY UPDATE 语法:

修改文件

  • ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml

修改内容

在原有的 insertVehicleGpsSegmentMileage 方法中添加 ON DUPLICATE KEY UPDATE 子句:

<insert id="insertVehicleGpsSegmentMileage" parameterType="VehicleGpsSegmentMileage">
    INSERT INTO tb_vehicle_gps_segment_mileage
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <!-- 字段列表 -->
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
        <!-- 值列表 -->
    </trim>
    ON DUPLICATE KEY UPDATE
    vehicle_no = VALUES(vehicle_no),
    segment_end_time = VALUES(segment_end_time),
    start_longitude = VALUES(start_longitude),
    start_latitude = VALUES(start_latitude),
    end_longitude = VALUES(end_longitude),
    end_latitude = VALUES(end_latitude),
    segment_distance = VALUES(segment_distance),
    gps_point_count = VALUES(gps_point_count),
    gps_ids = VALUES(gps_ids),
    task_id = VALUES(task_id),
    task_code = VALUES(task_code),
    calculate_method = VALUES(calculate_method),
    update_time = NOW()
</insert>

技术优势

使用 ON DUPLICATE KEY UPDATE 的好处:

  1. 原子性操作:数据库层面保证插入或更新的原子性,避免并发冲突
  2. 无需额外查询:不需要先 SELECT 再决定 INSERT 还是 UPDATE
  3. 性能更优:减少一次数据库查询开销
  4. 代码简洁:不需要复杂的异常捕获和重试逻辑
  5. 数据一致性:如果发生重复,自动更新已有记录而不是报错

影响范围

  • 受影响的功能:GPS 分段里程计算
  • 受影响的表tb_vehicle_gps_segment_mileage
  • 受影响的接口:所有调用 insertVehicleGpsSegmentMileage 的方法

测试建议

  1. 并发测试:模拟多个线程同时计算同一车辆的 GPS 分段里程
  2. 重复数据测试:手动构造重复的车辆 ID + 时间段开始时间,验证是否能正确更新
  3. 回归测试:确保正常的插入功能不受影响

部署步骤

  1. 重启应用服务器即可生效
  2. 无需执行任何 SQL 脚本
  3. 无需修改数据库表结构

后续优化建议

  1. 监控日志:观察是否还有其他并发场景导致的类似问题
  2. 事务优化:对于批处理操作,考虑添加适当的事务隔离级别
  3. 锁机制:如果问题仍然存在,可以考虑使用数据库行锁或分布式锁

相关文件

  • ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml
  • ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
  • ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGpsSegmentMileage.java
  • sql/vehicle_gps_segment_mileage.sql

修复日期: 2026-03-16
修复人员: AI Assistant
问题类型: 并发数据一致性