You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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));
}

额外优化点

  1. 日期逻辑修正:原代码中Delivery_Date__c固定为当月月初,建议改为startDate.addMonths(i).toStartOfMonth(),确保每个Schedule记录对应正确的月份。
  2. 类级变量风险:原Batch中scheduleRecordList是类级变量,会跨批次累积数据,建议移到execute方法内部,每个批次单独处理。

内容的提问来源于stack exchange,提问作者Akshay Vasu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 02:37:03