读写分离导致批量删除逻辑问题
原始代码实现Override public void clearExpireOrder(LocalDate expireDate) { log.info(开始清理过期订单, 过期日期: {}, expireDate); int batchSize 200; long totalDeletedCount 0; while (true) { ListExposureOrder expireOrders this.list(Wrappers.ExposureOrderlambdaQuery() .select(ExposureOrder::getId).le(ExposureOrder::getAdEndDate, expireDate.atStartOfDay()) .last(LIMIT batchSize)); if (expireOrders null || expireOrders.isEmpty()) { break; } ListLong 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的主节点或从节点实现读写分离。回到原始代码问题出在以下执行流程执行查询操作路由到从节点执行删除操作路由到主节点再次执行查询操作仍然路由到从节点但此时从节点尚未同步主节点的删除数据第二次查询与第一次查询结果相同执行删除操作受影响行数为0认为已没有需要删除的数据跳出循环问题解决方案找到问题根源后修改代码策略不再采用先查询后删除的方式而是直接执行DELETE语句Override public void clearExpireOrder(LocalDate expireDate) { log.info(开始清理过期订单, 过期日期: {}, expireDate); int batchSize 100; int maxBatch 1000; int currentBatch 0; long totalDeletedCount 0; while (true) { LambdaUpdateWrapperExposureOrder w Wrappers.ExposureOrderlambdaUpdate() .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); }