尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

踩坑实录:读写分离导致批量删除逻辑问题

踩坑实录:读写分离导致批量删除逻辑问题
📅 发布时间:2026/7/8 17:32:49

原始代码实现

@Override public void clearExpireOrder(LocalDate expireDate) { log.info("开始清理过期订单, 过期日期: {}", expireDate); int batchSize = 200; long totalDeletedCount = 0; while (true) { List<ExposureOrder> expireOrders = this.list(Wrappers.<ExposureOrder>lambdaQuery() .select(ExposureOrder::getId).le(ExposureOrder::getAdEndDate, expireDate.atStartOfDay()) .last("LIMIT " + batchSize)); if (expireOrders == null || expireOrders.isEmpty()) { break; } List<Long> ids = expireOrders.stream().map(ExposureOrder::getId).collect(Collectors.toList()); int deletedCount = this.removeByIds(ids) ? ids.size() : 0; totalDeletedCount += deletedCount; log.info("本批次删除 {} 条过期订单, 累计删除 {} 条", deletedCount, totalDeletedCount); if (deletedCount < batchSize) { break; } } log.info("过期订单清理完成, 总计删除 {} 条数据, 过期日期: {}", totalDeletedCount, expireDate); }

这是一个清理过期订单的定时任务,传入一个过期时间,每次查询前200个过期订单并根据ID进行删除。然而,在生产环境执行后,发现仍有过期订单存在。查询日志显示:

5月 15, 2026 @ 03:00:01.850 开始清理过期订单, 过期日期: 2026-04-15 5月 15, 2026 @ 03:00:01.850 本批次删除 200 条过期订单, 累计删除 200 条 5月 15, 2026 @ 03:00:01.850 本批次删除 0 条过期订单, 累计删除 200 条 5月 15, 2026 @ 03:00:01.850 过期订单清理完成, 总计删除 200 条数据, 过期日期: 2026-04-15

从功能上看,先查询后删除的方式虽然不是最佳方案,但代码逻辑上确实看不出明显问题。经过反复分析,仍未能找出错误所在。

问题发现

突然注意到数据库连接地址为prod-bd-traffic-mysql-shm-01.rwlb.rds.aliyuncs.com:3306,这看起来像是阿里云的代理链接地址。经检查,我们的数据库配置为一主一从,并启用了数据库代理的读写分离功能。

阿里云RDS的数据库代理地址开启读写分离功能后,会根据执行的SQL自动将请求分发到RDS的主节点或从节点,实现读写分离。回到原始代码,问题出在以下执行流程:

  1. 执行查询操作(路由到从节点)
  2. 执行删除操作(路由到主节点)
  3. 再次执行查询操作(仍然路由到从节点,但此时从节点尚未同步主节点的删除数据)
  4. 第二次查询与第一次查询结果相同
  5. 执行删除操作,受影响行数为0
  6. 认为已没有需要删除的数据,跳出循环

问题解决方案

找到问题根源后,修改代码策略,不再采用先查询后删除的方式,而是直接执行DELETE语句:

@Override public void clearExpireOrder(LocalDate expireDate) { log.info("开始清理过期订单, 过期日期: {}", expireDate); int batchSize = 100; int maxBatch = 1000; int currentBatch = 0; long totalDeletedCount = 0; while (true) { LambdaUpdateWrapper<ExposureOrder> w = Wrappers.<ExposureOrder>lambdaUpdate() .le(ExposureOrder::getAdEndDate, expireDate) .last(" LIMIT " + batchSize); int deletedCount = this.getBaseMapper().delete(w); totalDeletedCount += deletedCount; log.info("本批次删除 {} 条过期订单, 累计删除 {} 条", deletedCount, totalDeletedCount); if (deletedCount == 0) { break; } else if (currentBatch > maxBatch) { log.warn("本次清理超过最大处理批次, 退出清理"); throw new RuntimeException("本次清理超过最大处理批次, 退出清理"); } else { try { Thread.sleep(200); } catch (InterruptedException e) { log.error("线程中断, {}", e.getMessage()); } } currentBatch ++; } log.info("过期订单清理完成, 总计删除 {} 条数据, 过期日期: {}", totalDeletedCount, expireDate); }

相关新闻

  • Cursor破解终极指南:如何完全免费使用AI编程助手Pro功能
  • Unity开发Oculus Quest虚拟键盘:官方集成与自定义方案全解析
  • DeepSEED 2023 合成启动子设计实战:3类任务成功率提升与序列多样性分析

最新新闻

  • 豆包视频去水印零工具方案:浏览器原生JS三步实现
  • 航天器遥测数据异常检测实战:基于 PyTorch Geometric 实现 MAG 模型,窗口大小 50 步长 1
  • SeaTunnel 2.3.12生产实践:从配置即代码到稳定CDC的工程化落地
  • Cursor如何实现产品-设计-开发的AI协作闭环
  • Google Maps API+Gemini多模态解析实战(企业级POI洞察引擎深度拆解)
  • Java Web版学生选课系统源码包:含完整MVC结构、MySQL建表脚本与Tomcat部署说明

日新闻

  • PROPKA 3深度解析:蛋白质pKa预测的实战指南与算法原理
  • 微信小程序 globalData 监听:基于 Object.defineProperty 的 3 种实现方案对比
  • MySQL 8.0 数据清洗实战:3类异常值识别与 UPDATE/DELETE 批量处理

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号