From d7a1c5c4e2813223496aeaa7ad8a338037cc8ae6 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 4 Nov 2026 20:39:17 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=92=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/SmHotelMidServiceImpl.java | 48 +++++++++++++++++++ .../service/impl/SmHotelSyncServiceImpl.java | 16 ++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java index 0458dafa..b8a99801 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java @@ -2855,9 +2855,40 @@ public class SmHotelMidServiceImpl implements SmHotelMidService { *

* 唯一键 uk_product_date=(tenant_id, product_id, stay_date) 不含 del_flag,必须物理删。 * 删除区间须覆盖本次将写入的日期(含回调报文里的历史日),不能写死 LocalDate.now()。 + *

+ * 全量详情多酒店并行时,同租户唯一索引上易出现死锁,失败自动重试几次。 */ private int persistDailies(SmHotelEntity hotel, List drafts, LocalDate fromInclusive, LocalDate toExclusive) { + final int maxAttempts = 4; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return persistDailiesOnce(hotel, drafts, fromInclusive, toExclusive); + } + catch (Exception e) { + if (!isMysqlDeadlock(e) || attempt >= maxAttempts) { + throw e instanceof RuntimeException ? (RuntimeException) e + : new IllegalStateException(e.getMessage(), e); + } + long sleepMs = 80L * attempt + (long) (Math.random() * 120); + log.warn("|酒店日历落库|死锁重试|hotelId={}|code={}|attempt={}/{}|sleepMs={}|{}", + hotel == null ? null : hotel.getId(), + hotel == null ? null : hotel.getHotelCode(), attempt, maxAttempts, sleepMs, + StrUtil.maxLength(e.getMessage(), 160)); + try { + Thread.sleep(sleepMs); + } + catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("日历落库重试被中断", ie); + } + } + } + return 0; + } + + private int persistDailiesOnce(SmHotelEntity hotel, List drafts, LocalDate fromInclusive, + LocalDate toExclusive) { LocalDate delFrom = fromInclusive; LocalDate delTo = toExclusive; if (delFrom == null || delTo == null || !delFrom.isBefore(delTo)) { @@ -2933,6 +2964,23 @@ public class SmHotelMidServiceImpl implements SmHotelMidService { return toInsert.size(); } + /** 并发拉详情时,日历表 uk_product_date 易出现 InnoDB 死锁 */ + private static boolean isMysqlDeadlock(Throwable e) { + Throwable cur = e; + while (cur != null) { + String msg = cur.getMessage(); + if (msg != null && (msg.contains("Deadlock") || msg.contains("try restarting transaction"))) { + return true; + } + String name = cur.getClass().getName(); + if (name.contains("MySQLTransactionRollbackException") || name.contains("DeadlockLoser")) { + return true; + } + cur = cur.getCause(); + } + return false; + } + private Map loadRoomMap(Long hotelId) { List rows = roomMapper.selectList(Wrappers.lambdaQuery() .eq(SmHotelRoomEntity::getHotelId, hotelId)); diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java index 111bac76..84987a7b 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelSyncServiceImpl.java @@ -2023,8 +2023,20 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService { return; } String code = StrUtil.blankToDefault(h.getHotelCode(), String.valueOf(h.getId())); - String name = StrUtil.blankToDefault(h.getHotelName(), "-"); - failLines.add(code + "|" + name + "|" + StrUtil.maxLength(StrUtil.blankToDefault(reason, "未知错误"), 300)); + String name = StrUtil.blankToDefault(h.getHotelName(), "-") + .replace('|', '/') + .replace('\r', ' ') + .replace('\n', ' '); + String raw = StrUtil.blankToDefault(reason, "未知错误"); + // MyBatis 堆栈含换行会把导出 Excel 拆成多行;死锁写成短文案便于重试 + String safe; + if (raw.contains("Deadlock") || raw.contains("try restarting transaction")) { + safe = "日历落库死锁(可点失败重试)"; + } + else { + safe = raw.replace('\r', ' ').replace('\n', ' ').replace('|', '/'); + } + failLines.add(code + "|" + name + "|" + StrUtil.maxLength(safe, 300)); } private static void applyFailDetailOnly(SmHotelSyncLogEntity done, List failLines, int fail) {