解决Salesforce事务中两个Queueable Job同时运行的报错问题
解决方案:解决Salesforce Queueable任务数量限制问题
你遇到的"Too many queueable jobs added to the queue: 2"错误,核心原因是同一个同步执行上下文(比如触发器)中添加了超过1个Queueable任务。以下是几种针对性的解决方案:
方案一:合并两个Queueable任务
将第三方系统同步和Schedule记录创建的逻辑合并到同一个Queueable类中,只在触发器中enqueue一次,避免同时添加多个Queueable。
合并后的Queueable示例代码
public class CombinedSyncAndScheduleQueueable implements Queueable { private Map<Id, OrderItem> orderItemMap; public CombinedSyncAndScheduleQueueable(Map<Id, OrderItem> orderItemMap) { this.orderItemMap = orderItemMap; } public void execute(QueueableContext context) { // 1. 执行第三方系统同步逻辑(替换为你原第三方Queueable的代码) syncWithThirdParty(orderItemMap); // 2. 处理Schedule记录创建 createScheduleRecords(orderItemMap); } private void syncWithThirdParty(Map<Id, OrderItem> orderItems) { // 第三方同步逻辑,比如HTTP请求、数据转换等 // ... } private void createScheduleRecords(Map<Id, OrderItem> orderItemMap) { List<Schedule__c> scheduleRecordList = new List<Schedule__c>(); // 查询完整的OrderItem字段(触发器传入的记录可能缺少部分字段) List<OrderItem> orderItems = [SELECT Id, Order.Id, UnitPrice, Quantity, Pacing__c, OL_Left_To_Deliver__c, Line_Item_Start_Date__c, Line_Item_End_Date__c FROM OrderItem WHERE Id IN :orderItemMap.keySet()]; for(OrderItem oli : orderItems) { Date startDate = oli.Line_Item_Start_Date__c; Date endDate = oli.Line_Item_End_Date__c; Integer monthDiff = startDate.monthsBetween(endDate); if (endDate.day() > startDate.day()) monthDiff++; Decimal deliveryQuantity; Decimal toBeUpdatedQuantity; Integer deliveryRemainder; if(oli.Pacing__c == 'Even') { deliveryQuantity = oli.OL_Left_To_Deliver__c / monthDiff; deliveryRemainder = math.mod(Integer.valueOf(oli.OL_Left_To_Deliver__c), monthDiff); } else if(oli.Pacing__c == 'Front Load') { deliveryQuantity = oli.OL_Left_To_Deliver__c; } for(Integer i=0; i<monthDiff; i++) { if(oli.Pacing__c == 'Even') { if(deliveryRemainder == 0) { toBeUpdatedQuantity = deliveryQuantity; } else { toBeUpdatedQuantity = Math.floor(deliveryQuantity); if(i == 1) { toBeUpdatedQuantity += deliveryRemainder; } } } else if(oli.Pacing__c == 'Front Load') { toBeUpdatedQuantity = i == 0 ? deliveryQuantity : 0; } Schedule__c sc = new Schedule__c(); sc.Delivery_Date__c = startDate.addMonths(i).toStartOfMonth(); // 按月份递增调整日期 sc.Schedule_Status__c = 'Live'; sc.Schedule_Type__c = 'Monthly Forecast'; sc.Order_Product__c = oli.Id; sc.Order__c = oli.Order.Id; sc.Unit_Price__c = oli.UnitPrice; sc.Quantity__c = toBeUpdatedQuantity; scheduleRecordList.add(sc); } } if(!scheduleRecordList.isEmpty()) { insert scheduleRecordList; } } }
触发器中调用方式
// 触发器逻辑:当指定字段更新到目标值时执行 Map<Id, OrderItem> targetOrderItems = new Map<Id, OrderItem>(); for(OrderItem oli : Trigger.new) { OrderItem oldOli = Trigger.oldMap.get(oli.Id); // 替换为你的特定字段和指定值 if(oli.Pacing__c == 'Even' && oldOli.Pacing__c != 'Even') { targetOrderItems.put(oli.Id, oli); } } if(!targetOrderItems.isEmpty()) { System.enqueueJob(new CombinedSyncAndScheduleQueueable(targetOrderItems)); }
方案二:调整Batch逻辑,移除额外Queueable
原Batch代码中在execute方法内多次enqueue Queueable是冗余的,直接在Batch中插入Schedule记录即可(Batch的execute上下文DML限制为10000条,足够处理批量数据)。
修改后的Batch代码
global class CreateScheduleRecords implements Database.Batchable<sObject> { Map<Id, OrderItem> IdOrderItemMapBatch = new Map<Id, OrderItem>(); global CreateScheduleRecords(Map<Id, OrderItem> IdOrderItemMap) { IdOrderItemMapBatch = IdOrderItemMap; } global Database.QueryLocator start(Database.BatchableContext BC) { return Database.getQueryLocator([SELECT Id, Order.Id, UnitPrice, Quantity, Pacing__c, OL_Left_To_Deliver__c, Line_Item_Start_Date__c, Line_Item_End_Date__c FROM OrderItem WHERE Id in :IdOrderItemMapBatch.keySet()]); } global void execute(Database.BatchableContext BC, List<OrderItem> orderProductList) { List<Schedule__c> scheduleRecordList = new List<Schedule__c>(); // 每个批次单独维护列表,避免跨批次累积 try { for(OrderItem oli : orderProductList) { Date startDate = oli.Line_Item_Start_Date__c; Date endDate = oli.Line_Item_End_Date__c; Integer monthDiff = startDate.monthsBetween(endDate); if (endDate.day() > startDate.day()) monthDiff++; Decimal deliveryQuantity; Decimal toBeUpdatedQuantity; Integer deliveryRemainder; if(oli.Pacing__c == 'Even') { deliveryQuantity = oli.OL_Left_To_Deliver__c / monthDiff; deliveryRemainder = math.mod(Integer.valueOf(oli.OL_Left_To_Deliver__c), monthDiff); } else if(oli.Pacing__c == 'Front Load') { deliveryQuantity = oli.OL_Left_To_Deliver__c; } for(Integer i=0; i<monthDiff; i++) { if(oli.Pacing__c == 'Even') { toBeUpdatedQuantity = Math.floor(deliveryQuantity); if(deliveryRemainder == 0) { toBeUpdatedQuantity = deliveryQuantity; } else if(i == 1) { toBeUpdatedQuantity += deliveryRemainder; } } else if(oli.Pacing__c == 'Front Load') { toBeUpdatedQuantity = i == 0 ? deliveryQuantity : 0; } Schedule__c sc = new Schedule__c(); sc.Delivery_Date__c = startDate.addMonths(i).toStartOfMonth(); sc.Schedule_Status__c = 'Live'; sc.Schedule_Type__c = 'Monthly Forecast'; sc.Order_Product__c = oli.Id; sc.Order__c = oli.Order.Id; sc.Unit_Price__c = oli.UnitPrice; sc.Quantity__c = toBeUpdatedQuantity; scheduleRecordList.add(sc); } } if(!scheduleRecordList.isEmpty()) { insert scheduleRecordList; // 直接插入,无需额外Queueable } } catch(Exception e) { System.debug('Error creating Schedule records: ' + e.getMessage() + ' Line: ' + e.getLineNumber()); } } global void finish(Database.BatchableContext BC) { // 批次完成后的额外操作(如通知、日志)可在此实现 } }
触发器中调用方式
Map<Id, OrderItem> targetOrderItems = new Map<Id, OrderItem>(); for(OrderItem oli : Trigger.new) { OrderItem oldOli = Trigger.oldMap.get(oli.Id); if(oli.Pacing__c == 'Even' && oldOli.Pacing__c != 'Even') { targetOrderItems.put(oli.Id, oli); } } if(!targetOrderItems.isEmpty()) { // 1. 第三方同步Queueable(仅添加1个) System.enqueueJob(new ThirdPartySyncQueueable(targetOrderItems)); // 2. 执行Batch(Batch是异步任务,不占用当前上下文的Queueable配额) Database.executeBatch(new CreateScheduleRecords(targetOrderItems)); }
方案三:链式调用Queueable
如果必须分开执行两个任务,可以先enqueue第三方同步的Queueable,在其执行完成后再链式触发Schedule创建的Queueable,避免在同一上下文添加多个任务。
链式Queueable示例
public class ThirdPartySyncQueueable implements Queueable { private Map<Id, OrderItem> orderItemMap; public ThirdPartySyncQueueable(Map<Id, OrderItem> orderItemMap) { this.orderItemMap = orderItemMap; } public void execute(QueueableContext context) { // 执行第三方同步逻辑 syncWithThirdParty(orderItemMap); // 同步完成后,触发Schedule创建 System.enqueueJob(new ScheduleCreationQueueable(orderItemMap)); } private void syncWithThirdParty(Map<Id, OrderItem> orderItems) { // 第三方同步逻辑 // ... } } public class ScheduleCreationQueueable implements Queueable { private Map<Id, OrderItem> orderItemMap; public ScheduleCreationQueueable(Map<Id, OrderItem> orderItemMap) { this.orderItemMap = orderItemMap; } public void execute(QueueableContext context) { // 复用Schedule创建逻辑(同方案一的createScheduleRecords方法) List<Schedule__c> scheduleRecordList = new List<Schedule__c>(); List<OrderItem> orderItems = [SELECT Id, Order.Id, UnitPrice, Quantity, Pacing__c, OL_Left_To_Deliver__c, Line_Item_Start_Date__c, Line_Item_End_Date__c FROM OrderItem WHERE Id IN :orderItemMap.keySet()]; for(OrderItem oli : orderItems) { // Schedule创建逻辑 // ... } if(!scheduleRecordList.isEmpty()) { insert scheduleRecordList; } } }
触发器中调用方式
if(!targetOrderItems.isEmpty()) { System.enqueueJob(new ThirdPartySyncQueueable(targetOrderItems)); }
额外优化点
- 日期逻辑修正:原代码中
Delivery_Date__c固定为当月月初,建议改为startDate.addMonths(i).toStartOfMonth(),确保每个Schedule记录对应正确的月份。 - 类级变量风险:原Batch中
scheduleRecordList是类级变量,会跨批次累积数据,建议移到execute方法内部,每个批次单独处理。
内容的提问来源于stack exchange,提问作者Akshay Vasu
相关产品推荐
相关产品推荐

