This commit is contained in:
zjf committed 2026-11-04 20:39:17 +08:00
1 parent 47a4e4d7de
commit d7a1c5c4e2
2 files changed
+62 -2

No files matched your search

@@ -2855,9 +2855,40 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
* <p>
* 唯一键 uk_product_date=(tenant_id, product_id, stay_date) 不含 del_flag,必须物理删。
* 删除区间须覆盖本次将写入的日期(含回调报文里的历史日),不能写死 LocalDate.now()。
* <p>
* 全量详情多酒店并行时,同租户唯一索引上易出现死锁,失败自动重试几次。
*/
private int persistDailies(SmHotelEntity hotel, List<DailyDraft> 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<DailyDraft> 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<String, SmHotelRoomEntity> loadRoomMap(Long hotelId) {
List<SmHotelRoomEntity> rows = roomMapper.selectList(Wrappers.<SmHotelRoomEntity>lambdaQuery()
.eq(SmHotelRoomEntity::getHotelId, hotelId));
@@ -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<String> failLines, int fail) {