Forráskód Böngészése

perf: 修复 4 处核心 N+1 数据库查询问题

1. UserCouponServiceImpl.distributeCoupon: saveBatch 替代逐条 save,批量扣减库存
2. DataSyncServiceImpl.syncDevices/syncProducts: 批量查询+saveBatch/updateBatchById
3. OrderServiceImpl.repairOrdersMissingGoods: 批量查商品+批量删/插 OrderGoods
4. CheckinRecordServiceImpl.syncOfflineCheckins: 批量查已有+saveBatch

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 2 napja
szülő
commit
3e557aa8d4

+ 3 - 0
haha-mapper/src/main/java/com/haha/mapper/CouponTemplateMapper.java

@@ -21,6 +21,9 @@ public interface CouponTemplateMapper extends BaseMapper<CouponTemplate> {
     @Update("UPDATE t_coupon_template SET remain_count = remain_count - 1, update_time = NOW() WHERE id = #{id} AND remain_count > 0")
     int decreaseRemainCount(@Param("id") Long id);
 
+    @Update("UPDATE t_coupon_template SET remain_count = remain_count - #{count}, update_time = NOW() WHERE id = #{id} AND remain_count >= #{count}")
+    int decreaseRemainCount(@Param("id") Long id, @Param("count") int count);
+
     @Update("UPDATE t_coupon_template SET remain_count = remain_count + 1, update_time = NOW() WHERE id = #{id}")
     int increaseRemainCount(@Param("id") Long id);
 

+ 25 - 3
haha-service/src/main/java/com/haha/service/impl/CheckinRecordServiceImpl.java

@@ -217,11 +217,28 @@ public class CheckinRecordServiceImpl extends ServiceImpl<CheckinRecordMapper, C
         int failedCount = 0;
         List<String> failedIds = new ArrayList<>();
 
+        // 1. 收集所有 offlineId,批量查询已存在记录(1 次 SELECT IN)
+        List<String> offlineIds = records.stream()
+                .map(CheckinRecord::getOfflineId)
+                .filter(StringUtils::hasText).toList();
+        java.util.Set<String> existingOfflineIds = new java.util.HashSet<>();
+        if (!offlineIds.isEmpty()) {
+            List<CheckinRecord> existingRecords = checkinRecordMapper.selectList(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CheckinRecord>()
+                            .in(CheckinRecord::getOfflineId, offlineIds));
+            for (CheckinRecord r : existingRecords) {
+                if (StringUtils.hasText(r.getOfflineId())) {
+                    existingOfflineIds.add(r.getOfflineId());
+                }
+            }
+        }
+
+        // 2. 分离已存在和新记录
+        List<CheckinRecord> newRecords = new ArrayList<>();
         for (CheckinRecord record : records) {
             try {
                 if (StringUtils.hasText(record.getOfflineId())) {
-                    CheckinRecord existing = checkinRecordMapper.selectByOfflineId(record.getOfflineId());
-                    if (existing != null) {
+                    if (existingOfflineIds.contains(record.getOfflineId())) {
                         log.info("离线签到记录已存在, offlineId: {}", record.getOfflineId());
                         successCount++;
                         continue;
@@ -236,7 +253,7 @@ public class CheckinRecordServiceImpl extends ServiceImpl<CheckinRecordMapper, C
                     record.setCreateTime(LocalDateTime.now());
                 }
 
-                save(record);
+                newRecords.add(record);
                 successCount++;
             } catch (Exception e) {
                 log.error("同步离线签到记录失败: {}", e.getMessage(), e);
@@ -247,6 +264,11 @@ public class CheckinRecordServiceImpl extends ServiceImpl<CheckinRecordMapper, C
             }
         }
 
+        // 3. 批量保存新记录(1 次批量 INSERT)
+        if (!newRecords.isEmpty()) {
+            saveBatch(newRecords);
+        }
+
         Map<String, Object> result = new HashMap<>();
         result.put("success", successCount);
         result.put("failed", failedCount);

+ 134 - 109
haha-service/src/main/java/com/haha/service/impl/DataSyncServiceImpl.java

@@ -85,23 +85,66 @@ public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecor
             record.setTotalCount(deviceList.size());
             log.info("从哈哈平台获取到 {} 台设备", deviceList.size());
 
-            // 3. 遍历设备列表,同步到本地数据库
+            // 3. 批量同步设备到本地数据库
+            List<String> sns = deviceList.stream().map(DeviceInfo::getId).filter(Objects::nonNull).toList();
+
+            // 3.1 批量查询已有设备(1 次 SELECT IN)
+            Map<String, Device> existingBySn = new HashMap<>();
+            if (!sns.isEmpty()) {
+                List<Device> existingDevices = deviceService.lambdaQuery()
+                        .in(Device::getDeviceId, sns).list();
+                for (Device d : existingDevices) {
+                    existingBySn.put(d.getDeviceId(), d);
+                }
+            }
+
+            // 3.2 分类新建/更新,批量操作
+            List<Device> toInsert = new ArrayList<>();
+            List<Device> toUpdate = new ArrayList<>();
+            List<SyncLog> syncLogs = new ArrayList<>();
+
             for (DeviceInfo deviceInfo : deviceList) {
                 try {
-                    syncSingleDevice(record.getId(), deviceInfo);
+                    Device existDevice = existingBySn.get(deviceInfo.getId());
+                    if (existDevice != null) {
+                        existDevice.setName(deviceInfo.getName());
+                        existDevice.setAddress(deviceInfo.getAddress());
+                        existDevice.setStatus("1".equals(deviceInfo.getStatus()) ? DeviceConstants.STATUS_ONLINE : DeviceOnlineStatus.OFFLINE.getCode());
+                        toUpdate.add(existDevice);
+                        syncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_DEVICE, deviceInfo.getId(),
+                                deviceInfo.getName(), 1, "设备信息更新成功", null));
+                    } else {
+                        Device newDevice = new Device();
+                        newDevice.setDeviceId(deviceInfo.getId());
+                        newDevice.setName(deviceInfo.getName());
+                        newDevice.setAddress(deviceInfo.getAddress());
+                        newDevice.setStatus("1".equals(deviceInfo.getStatus()) ? DeviceConstants.STATUS_ONLINE : DeviceOnlineStatus.OFFLINE.getCode());
+                        toInsert.add(newDevice);
+                        // 先加入列表,saveBatch 后 ID 会自动回填
+                        syncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_DEVICE, deviceInfo.getId(),
+                                deviceInfo.getName(), 1, "设备新增成功", null));
+                    }
                     successCount++;
                 } catch (Exception e) {
                     failCount++;
                     String errMsg = String.format("设备 %s 同步失败: %s", deviceInfo.getId(), e.getMessage());
                     errorMsg.append(errMsg).append("; ");
                     log.error(errMsg, e);
-
-                    // 记录失败日志
-                    saveSyncLog(record.getId(), SYNC_TYPE_DEVICE, deviceInfo.getId(), 
-                               deviceInfo.getName(), 0, errMsg, JSON.toJSONString(deviceInfo));
+                    syncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_DEVICE, deviceInfo.getId(),
+                            deviceInfo.getName(), 0, errMsg, JSON.toJSONString(deviceInfo)));
                 }
             }
 
+            // 3.3 批量保存/更新(各 1 次批量操作)
+            if (!toInsert.isEmpty()) {
+                deviceService.saveBatch(toInsert);
+            }
+            if (!toUpdate.isEmpty()) {
+                deviceService.updateBatchById(toUpdate);
+            }
+            // 3.4 批量保存同步日志
+            saveSyncLogBatch(syncLogs);
+
             // 4. 更新同步记录
             record.setSuccessCount(successCount);
             record.setFailCount(failCount);
@@ -132,45 +175,11 @@ public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecor
         }
     }
 
-    /**
-     * 同步单个设备
-     */
-    private void syncSingleDevice(Long recordId, DeviceInfo deviceInfo) {
-        log.debug("同步设备: {} - {}", deviceInfo.getId(), deviceInfo.getName());
-
-        // 查询本地是否已存在该设备
-        Device existDevice = deviceService.getDeviceBySn(deviceInfo.getId());
-
-        if (existDevice != null) {
-            // 更新设备信息
-            existDevice.setName(deviceInfo.getName());
-            existDevice.setAddress(deviceInfo.getAddress());
-            existDevice.setStatus("1".equals(deviceInfo.getStatus()) ? DeviceConstants.STATUS_ONLINE : DeviceOnlineStatus.OFFLINE.getCode());
-            deviceService.updateById(existDevice);
-
-            // 记录成功日志
-            saveSyncLog(recordId, SYNC_TYPE_DEVICE, deviceInfo.getId(), 
-                       deviceInfo.getName(), 1, "设备信息更新成功", null);
-        } else {
-            // 新增设备
-            Device newDevice = new Device();
-            newDevice.setDeviceId(deviceInfo.getId());
-            newDevice.setName(deviceInfo.getName());
-            newDevice.setAddress(deviceInfo.getAddress());
-            newDevice.setStatus("1".equals(deviceInfo.getStatus()) ? DeviceConstants.STATUS_ONLINE : DeviceOnlineStatus.OFFLINE.getCode());
-            deviceService.save(newDevice);
-
-            // 记录成功日志
-            saveSyncLog(recordId, SYNC_TYPE_DEVICE, deviceInfo.getId(), 
-                       deviceInfo.getName(), 1, "设备新增成功", null);
-        }
-    }
-
     /**
      * 保存同步日志
      */
-    private void saveSyncLog(Long recordId, String syncType, String dataId, 
-                            String dataName, Integer status, String message, String detail) {
+    private SyncLog buildSyncLog(Long recordId, String syncType, String dataId,
+                                  String dataName, Integer status, String message, String detail) {
         SyncLog syncLog = new SyncLog();
         syncLog.setRecordId(recordId);
         syncLog.setSyncType(syncType);
@@ -180,7 +189,15 @@ public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecor
         syncLog.setMessage(message);
         syncLog.setDetail(detail);
         syncLog.setCreateTime(LocalDateTime.now());
-        syncLogMapper.insert(syncLog);
+        return syncLog;
+    }
+
+    private void saveSyncLogBatch(List<SyncLog> logs) {
+        if (logs != null && !logs.isEmpty()) {
+            for (SyncLog log : logs) {
+                syncLogMapper.insert(log);
+            }
+        }
     }
 
     @Override
@@ -297,28 +314,96 @@ public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecor
                     break;
                 }
 
-                // 3. 遍历商品列表,同步到本地数据库
+                // 3. 批量同步本页商品到本地数据库
                 for (Map<String, Object> productData : listData) {
                     totalCount++;
                     String code = productData.get("code") != null ? productData.get("code").toString() : null;
                     if (code != null) {
                         hahaCodes.add(code);
                     }
+                }
+
+                // 3.1 收集本页所有商品编码,批量查询已有商品(1 次 SELECT IN)
+                List<String> pageCodes = listData.stream()
+                        .map(p -> p.get("code") != null ? p.get("code").toString() : null)
+                        .filter(Objects::nonNull).toList();
+                Map<String, Product> existingByCode = new HashMap<>();
+                if (!pageCodes.isEmpty()) {
+                    List<Product> existingProducts = productService.listByCodes(pageCodes);
+                    for (Product p : existingProducts) {
+                        existingByCode.put(p.getCode(), p);
+                    }
+                }
+
+                // 3.2 分类新建/更新
+                List<Product> toInsert = new ArrayList<>();
+                List<Product> toUpdate = new ArrayList<>();
+                List<SyncLog> pageSyncLogs = new ArrayList<>();
+
+                for (Map<String, Object> productData : listData) {
+                    String code = productData.get("code") != null ? productData.get("code").toString() : null;
+                    String name = productData.get("name") != null ? productData.get("name").toString() : "";
                     try {
-                        syncSingleProduct(record.getId(), productData);
+                        if (code == null || code.isEmpty()) {
+                            throw new BusinessException(400, "商品编码为空");
+                        }
+
+                        Product existProduct = existingByCode.get(code);
+                        if (existProduct != null) {
+                            existProduct.setName(name);
+                            existProduct.setBarcode(productData.get("bar_code") != null ? productData.get("bar_code").toString() : null);
+                            existProduct.setType(productData.get("type") != null ? productData.get("type").toString() : null);
+                            existProduct.setPic(productData.get("pic") != null ? productData.get("pic").toString() : null);
+                            existProduct.setPlanogram(productData.get("planogram") != null ? productData.get("planogram").toString() : null);
+                            existProduct.setApplyStatus(productData.get("apply_status") != null ? productData.get("apply_status").toString() : null);
+                            existProduct.setCostPrice(parseDouble(productData.get("cost_price")));
+                            existProduct.setRetailPrice(parseDouble(productData.get("cprice")));
+                            existProduct.setCustomerId(productData.get("customer_id") != null ? productData.get("customer_id").toString() : null);
+                            existProduct.setSyncStatus(SyncConstants.STATUS_SYNCED);
+                            existProduct.setSyncTime(LocalDateTime.now());
+                            existProduct.setUpdateTime(LocalDateTime.now());
+                            toUpdate.add(existProduct);
+                            pageSyncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_PRODUCT, code, name, 1, "商品信息更新成功", null));
+                        } else {
+                            Product newProduct = new Product();
+                            newProduct.setProductId(productData.get("product_id") != null ? productData.get("product_id").toString() : null);
+                            newProduct.setCode(code);
+                            newProduct.setName(name);
+                            newProduct.setBarcode(productData.get("bar_code") != null ? productData.get("bar_code").toString() : null);
+                            newProduct.setType(productData.get("type") != null ? productData.get("type").toString() : null);
+                            newProduct.setPic(productData.get("pic") != null ? productData.get("pic").toString() : null);
+                            newProduct.setPlanogram(productData.get("planogram") != null ? productData.get("planogram").toString() : null);
+                            newProduct.setApplyStatus(productData.get("apply_status") != null ? productData.get("apply_status").toString() : null);
+                            newProduct.setCostPrice(parseDouble(productData.get("cost_price")));
+                            newProduct.setRetailPrice(parseDouble(productData.get("cprice")));
+                            newProduct.setCustomerId(productData.get("customer_id") != null ? productData.get("customer_id").toString() : null);
+                            newProduct.setSyncStatus(SyncConstants.STATUS_SYNCED);
+                            newProduct.setSyncTime(LocalDateTime.now());
+                            newProduct.setIsDeleted(CommonConstants.NOT_DELETED);
+                            newProduct.setCreateTime(LocalDateTime.now());
+                            newProduct.setUpdateTime(LocalDateTime.now());
+                            toInsert.add(newProduct);
+                            pageSyncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_PRODUCT, code, name, 1, "商品新增成功", null));
+                        }
                         successCount++;
                     } catch (Exception e) {
                         failCount++;
-                        String name = productData.get("name") != null ? productData.get("name").toString() : "unknown";
                         String errMsg = String.format("商品 %s(%s) 同步失败: %s", code != null ? code : "unknown", name, e.getMessage());
                         errorMsg.append(errMsg).append("; ");
                         log.error(errMsg, e);
-
-                        // 记录失败日志
-                        saveSyncLog(record.getId(), SYNC_TYPE_PRODUCT, code != null ? code : "unknown", name, 0, errMsg, JSON.toJSONString(productData));
+                        pageSyncLogs.add(buildSyncLog(record.getId(), SYNC_TYPE_PRODUCT, code != null ? code : "unknown", name, 0, errMsg, JSON.toJSONString(productData)));
                     }
                 }
 
+                // 3.3 批量保存/更新本页商品(各 1 次批量操作)
+                if (!toInsert.isEmpty()) {
+                    productService.saveBatch(toInsert);
+                }
+                if (!toUpdate.isEmpty()) {
+                    productService.updateBatchById(toUpdate);
+                }
+                saveSyncLogBatch(pageSyncLogs);
+
                 // 检查是否还有更多数据
                 Object totals = result.get("totals");
                 int total = totals != null ? Integer.parseInt(totals.toString()) : 0;
@@ -383,66 +468,6 @@ public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecor
         }
     }
 
-    /**
-     * 同步单个商品
-     */
-    private void syncSingleProduct(Long recordId, Map<String, Object> productData) {
-        String code = productData.get("code") != null ? productData.get("code").toString() : null;
-        String name = productData.get("name") != null ? productData.get("name").toString() : "";
-        
-        log.debug("同步商品: {} - {}", code, name);
-
-        if (code == null || code.isEmpty()) {
-            throw new BusinessException(400, "商品编码为空");
-        }
-
-        // 查询本地是否已存在该商品
-        Product existProduct = productService.getProductByCode(code);
-
-        if (existProduct != null) {
-            // 更新商品信息
-            existProduct.setName(name);
-            existProduct.setBarcode(productData.get("bar_code") != null ? productData.get("bar_code").toString() : null);
-            existProduct.setType(productData.get("type") != null ? productData.get("type").toString() : null);
-            existProduct.setPic(productData.get("pic") != null ? productData.get("pic").toString() : null);
-            existProduct.setPlanogram(productData.get("planogram") != null ? productData.get("planogram").toString() : null);
-            existProduct.setApplyStatus(productData.get("apply_status") != null ? productData.get("apply_status").toString() : null);
-            existProduct.setCostPrice(parseDouble(productData.get("cost_price")));
-            existProduct.setRetailPrice(parseDouble(productData.get("cprice")));
-            existProduct.setCustomerId(productData.get("customer_id") != null ? productData.get("customer_id").toString() : null);
-            existProduct.setSyncStatus(SyncConstants.STATUS_SYNCED);
-            existProduct.setSyncTime(LocalDateTime.now());
-            existProduct.setUpdateTime(LocalDateTime.now());
-            productService.updateById(existProduct);
-
-            // 记录成功日志
-            saveSyncLog(recordId, SYNC_TYPE_PRODUCT, code, name, 1, "商品信息更新成功", null);
-        } else {
-            // 新增商品
-            Product newProduct = new Product();
-            newProduct.setProductId(productData.get("product_id") != null ? productData.get("product_id").toString() : null);
-            newProduct.setCode(code);
-            newProduct.setName(name);
-            newProduct.setBarcode(productData.get("bar_code") != null ? productData.get("bar_code").toString() : null);
-            newProduct.setType(productData.get("type") != null ? productData.get("type").toString() : null);
-            newProduct.setPic(productData.get("pic") != null ? productData.get("pic").toString() : null);
-            newProduct.setPlanogram(productData.get("planogram") != null ? productData.get("planogram").toString() : null);
-            newProduct.setApplyStatus(productData.get("apply_status") != null ? productData.get("apply_status").toString() : null);
-            newProduct.setCostPrice(parseDouble(productData.get("cost_price")));
-            newProduct.setRetailPrice(parseDouble(productData.get("cprice")));
-            newProduct.setCustomerId(productData.get("customer_id") != null ? productData.get("customer_id").toString() : null);
-            newProduct.setSyncStatus(SyncConstants.STATUS_SYNCED);
-            newProduct.setSyncTime(LocalDateTime.now());
-            newProduct.setIsDeleted(CommonConstants.NOT_DELETED);
-            newProduct.setCreateTime(LocalDateTime.now());
-            newProduct.setUpdateTime(LocalDateTime.now());
-            productService.save(newProduct);
-
-            // 记录成功日志
-            saveSyncLog(recordId, SYNC_TYPE_PRODUCT, code, name, 1, "商品新增成功", null);
-        }
-    }
-
     /**
      * 解析Double值
      */

+ 40 - 18
haha-service/src/main/java/com/haha/service/impl/OrderServiceImpl.java

@@ -1189,33 +1189,49 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         List<Order> orders = this.list(wrapper);
         int fixed = 0;
 
+        // 1. 收集所有商品编码,批量查询(1 次 SELECT IN)
+        java.util.Set<String> allCodes = new java.util.HashSet<>();
         for (Order order : orders) {
             String items = order.getItems();
-            if (items == null || items.isEmpty()) {
-                continue;
+            if (items == null || items.isEmpty()) continue;
+            JSONArray skuList = JSON.parseArray(items);
+            if (skuList == null || skuList.isEmpty()) continue;
+            for (int i = 0; i < skuList.size(); i++) {
+                String code = skuList.getJSONObject(i).getString("code");
+                if (code != null && !code.isEmpty()) allCodes.add(code);
+            }
+        }
+        Map<String, com.haha.entity.Product> productMap = new HashMap<>();
+        if (!allCodes.isEmpty()) {
+            List<com.haha.entity.Product> products = productService.listByCodes(allCodes);
+            for (com.haha.entity.Product p : products) {
+                productMap.put(p.getCode(), p);
             }
+        }
+
+        // 2. 收集所有要删除的 orderId,批量删除旧记录(1 次 DELETE IN)
+        // 同时构建所有 OrderGoods
+        List<Long> orderIdsToClear = new ArrayList<>();
+        List<OrderGoods> allGoods = new ArrayList<>();
+
+        for (Order order : orders) {
+            String items = order.getItems();
+            if (items == null || items.isEmpty()) continue;
 
             try {
                 JSONArray skuList = JSON.parseArray(items);
-                if (skuList == null || skuList.isEmpty()) {
-                    continue;
-                }
+                if (skuList == null || skuList.isEmpty()) continue;
 
-                // 先删除已有商品记录(可能是之前占位写入的零售价数据),以最新金额重写
-                orderGoodsMapper.delete(
-                        new LambdaQueryWrapper<OrderGoods>().eq(OrderGoods::getOrderId, order.getId()));
+                orderIdsToClear.add(order.getId());
 
-                // 计算每个商品的单价:用订单实付金额(或订单金额)均摊
                 BigDecimal orderAmount = order.getPaidAmount() != null
                         && order.getPaidAmount().compareTo(BigDecimal.ZERO) > 0
                         ? order.getPaidAmount()
                         : order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO;
 
-                // 计算总商品数量,按数量比例分配金额
                 int totalQty = 0;
                 for (int i = 0; i < skuList.size(); i++) {
-                    JSONObject sku = skuList.getJSONObject(i);
-                    Integer number = sku.getInteger("number");
+                    Integer number = skuList.getJSONObject(i).getInteger("number");
                     totalQty += (number != null ? Math.abs(number) : 1);
                 }
 
@@ -1227,12 +1243,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                     JSONObject sku = skuList.getJSONObject(i);
                     String code = sku.getString("code");
                     Integer number = sku.getInteger("number");
-                    if (code == null || code.isEmpty()) {
-                        continue;
-                    }
+                    if (code == null || code.isEmpty()) continue;
 
                     int qty = number != null ? Math.abs(number) : 1;
-                    com.haha.entity.Product product = productService.getProductByCode(code);
+                    com.haha.entity.Product product = productMap.get(code);
 
                     OrderGoods goods = new OrderGoods();
                     goods.setOrderId(order.getId());
@@ -1252,8 +1266,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                         goods.setProductName(product.getName());
                         goods.setPic(product.getPic());
                     }
-
-                    orderGoodsMapper.insert(goods);
+                    allGoods.add(goods);
                 }
 
                 fixed++;
@@ -1264,6 +1277,15 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
             }
         }
 
+        // 3. 批量删除旧记录 + 批量插入新记录(共 2 次批量操作)
+        if (!orderIdsToClear.isEmpty()) {
+            orderGoodsMapper.delete(
+                    new LambdaQueryWrapper<OrderGoods>().in(OrderGoods::getOrderId, orderIdsToClear));
+        }
+        if (!allGoods.isEmpty()) {
+            orderGoodsService.saveBatch(allGoods);
+        }
+
         return fixed;
     }
 

+ 11 - 3
haha-service/src/main/java/com/haha/service/impl/UserCouponServiceImpl.java

@@ -197,6 +197,8 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
         distributeMapper.insert(distribute);
 
         int successCount = 0;
+        List<UserCoupon> couponBatch = new ArrayList<>(targetUserIds.size());
+
         for (Long userId : targetUserIds) {
             try {
                 UserCoupon userCoupon = new UserCoupon();
@@ -216,14 +218,20 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
                     userCoupon.setValidEndTime(now.plusDays(template.getValidDays()));
                 }
 
-                this.save(userCoupon);
-                templateMapper.decreaseRemainCount(dto.getTemplateId());
+                couponBatch.add(userCoupon);
                 successCount++;
             } catch (Exception e) {
-                log.error("发放优惠券失败: userId={}, templateId={}", userId, dto.getTemplateId(), e);
+                log.error("构建优惠券失败: userId={}, templateId={}", userId, dto.getTemplateId(), e);
             }
         }
 
+        // 批量保存(1 次批量 INSERT 替代 N 次逐条 INSERT)
+        if (!couponBatch.isEmpty()) {
+            this.saveBatch(couponBatch);
+        }
+        // 批量扣减库存(1 次 UPDATE 替代 N 次逐条 UPDATE)
+        templateMapper.decreaseRemainCount(dto.getTemplateId(), successCount);
+
         distribute.setSuccessCount(successCount);
         distributeMapper.updateById(distribute);