如何处理Apex批处理中SerialBatchApexRangeChunkHandler内部Salesforce错误
Database.Stateful的批处理因验证错误卡住的问题 我之前在处理Salesforce批更新时也碰到过几乎一模一样的情况——加了Database.Stateful就卡在处理状态,日志报Internal Salesforce.com Error,去掉就正常,但又必须用Stateful来收集错误发邮件。结合踩过的坑和最佳实践,给你几个可行的解决方向:
1. 捕获所有异常,不让未处理异常终止Chunk
批处理的每个Chunk如果抛出未捕获的异常,Salesforce会尝试重试,但如果是验证类的DML异常(比如字段过长),重试也没用,反而会导致Chunk卡住。关键是在execute方法里把每个记录的处理都用try-catch包裹,把错误信息收集到Stateful变量里,而不是让异常冒泡出去。
示例代码片段:
public class AccountUpdateBatch implements Database.Batchable<SObject>, Database.Stateful { // 用Stateful变量收集错误信息 public List<String> errorLogs = new List<String>(); public Database.QueryLocator start(Database.BatchableContext bc) { return Database.getQueryLocator([SELECT Id, Name FROM Account WHERE ...]); } public void execute(Database.BatchableContext bc, List<Account> scope) { for(Account acc : scope) { try { // 你的更新逻辑,比如修改字段值 acc.Name = 'Updated ' + acc.Name; update acc; } catch(DmlException dmlEx) { // 捕获DML验证错误,记录详细信息 for(Integer i = 0; i < dmlEx.getNumDml(); i++) { errorLogs.add(String.format('Account ID: {0} | Error: {1}', new List<String>{acc.Id, dmlEx.getDmlMessage(i)})); } } catch(Exception ex) { // 捕获其他意外错误 errorLogs.add(String.format('Account ID: {0} | Unexpected Error: {1}', new List<String>{acc.Id, ex.getMessage()})); } } } public void finish(Database.BatchableContext bc) { // 发送错误邮件的逻辑 if(!errorLogs.isEmpty()) { Messaging.SingleEmailMessage errorMail = new Messaging.SingleEmailMessage(); errorMail.setToAddresses(new List<String>{'notify@yourdomain.com'}); errorMail.setSubject('Account Batch Update Errors'); errorMail.setPlainTextBody('Batch completed with errors:\n\n' + String.join(errorLogs, '\n')); try { Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{errorMail}); } catch(Exception mailEx) { // 即使邮件发送失败,也要确保finish方法正常完成,避免批处理卡住 System.debug('Failed to send error email: ' + mailEx.getMessage()); // 可选:把邮件错误记录到自定义日志对象 } } } }
2. 控制Stateful变量的内存占用
如果批处理要处理大量记录,错误信息累积太多会导致堆内存溢出,这也是引发Internal Error的常见原因。可以做这些优化:
- 分批次发送错误邮件:在
execute方法结尾,如果errorLogs有内容,就先发送一次邮件,然后清空列表,避免内存里存太多错误信息。 - 用自定义对象存储错误:如果错误量很大,不要存在内存里,而是插入自定义的
Batch_Error_Log__c记录,最后在finish方法里查询这些记录再发邮件,或者直接让用户从列表视图查看。
3. 调小批处理的Chunk大小
默认的Chunk是200条记录,如果你处理的记录里错误率很高,每个Chunk产生的错误信息会很多,加上Stateful变量的存储,容易触发内存限制。尝试把Chunk调小,比如:
Database.executeBatch(new AccountUpdateBatch(), 50); // 每个Chunk处理50条
更小的Chunk能降低每个执行周期的内存压力,减少内部错误的概率。
4. 排查自动化逻辑的隐藏异常
有时候问题不是出在批处理本身,而是Account的触发器、流程、验证规则或者Flow里有未处理的异常。你可以:
- 暂时禁用Account相关的触发器,测试批处理是否正常运行,如果正常,再逐个排查触发器里的异常处理。
- 检查验证规则是否有复杂逻辑,导致更新时抛出异常但没有被正确捕获。
最后总结
核心思路就是不让任何未处理的异常终止批处理Chunk,同时控制Stateful变量的内存使用,确保批处理的每个阶段(start/execute/finish)都能正常完成,即使有错误也只是被记录下来,而不是导致整个批处理卡住。
内容的提问来源于stack exchange,提问作者Sailajoy Das

