需要帮助克服Apex超出CPU时间限制错误

时间:2014-08-04 19:39:50

标签: salesforce apex-code visualforce apex force.com

我正在开发一个模块,它会在插入/更新时重复删除联系人记录,但是我遇到了超出Apex CPU时间限制的错误。我理解为了克服它,我们需要稍微优化代码,但在下面的代码块中,似乎很少有优化它的范围。任何帮助将不胜感激。

在帐户上我们有一个多重名单,我们可以从中选择字段,根据这些字段定义此帐户下联系人的唯一性。差异账户可能有所不同。以下代码是触发器的处理程序类的一部分,其中我们有一个旧的联系人列表的映射,其中帐户ID为关键字(mapOfAccountIdWithItsContact),并且具有新联系人列表和帐户作为键的地图(newContactWithAccountMap)我们迭代这些映射我们在triiger中获得联系人的帐户集。我们有一个地图可以用来确定每个帐户的联系唯一性字段(mapOfAccountWithFilters)。

以下是代码段:

for(String accountId : accountIdSet){
        if(newContactWithAccountMap.get(accountId) != null){
            for(Contact newContact : newContactWithAccountMap.get(accountId)){
                for(Contact oldContact : mapOfAccountIdWithItsContact.get(accountId)){
                    //Check for duplication only in the respective account, also this should not apply on insertion of Office contact
                    matchingContactFound = false;
                    if(oldContact.id != newContact.id){  //while insert, newContact's id will be null and while update it will verify that it is not matching itself with its old record. 
                        for(String filterFieldName : mapOfAccountWithFilters.get(accountId)){
                            if(oldContact.get(filterFieldName) == newContact.get(filterFieldName)){
                                matchingContactFound = true;
                                //If match is found update last de duplication date to today on old contact
                                oldContact.Last_De_Duplication_Date__c = System.Today();
                                oldContactsToUpdateSet.add(oldContact);
                            }else{
                                matchingContactFound = false;
                                break; //get another "old contact"
                            }
                        }
                    }
                    if(matchingContactFound){                               
                        //stop it from being inserted
                        duplicateContactSet.add(newContact.Id);
                        //newContact.addError('Contact cannot be inserted because a contact is already present based on the Master Target Identifier at client level.');
                        break; //get another "new contact"
                    }                       
                }
            }
        }
    }  

任何帮助避免4循环或替代方法将非常感激。提前谢谢。

2 个答案:

答案 0 :(得分:4)

好问题!

但如果没有看到更多代码,很难说些什么......

  1. 您是如何将其缩小到这个特定的片段,是否进行了任何计时测试(调试日志?在开发者控制台中设置断点)?
  2. 如何填充变量以及它们的类型(我更愿意看到Map<Id, List<Contact>等,而不是试图从描述中弄清楚)
  3. 也许您正在查询过多的数据,也许预先进行一些更智能的过滤可以大大缩短执行时间...例如,您有关于的评论,这不应用于插入Office联系人< / em> - 在你输入这个方法之前,你是否已经过滤了这个?

  4. 也许预先准备一点会有帮助吗?

    要考虑的另一件事是多重列表中有多少个字段?您是否考虑过提前做好准备?例如,在每个插入物和每次更新都会保存&#34;重要字段中的值&#34;在联系人的帮助Text(255)字段中,我们将其称为&#34;重复类别/细分/标记&#34;或类似的东西。将该字段标记为外部id(它不必是唯一的,ext.id足以将其编入索引)。

    然后你应该可以非常快速地选择帐户ID和那个&#34;类别&#34;火柴。如果字段值相同或长度为255,则表示它被截断,您必须运行精确的逐字段比较。但是,如果没有相同的&#34;类别&#34; - 你可以安全地说没有重复。

    这样的事情必须经过精心设计(触发联系人,但也可以触发#34;重新计算&#34;每次字段定义更改时此字段...以及是否可以指出联系其他记录(比如用户),那么您也需要保护自己免受用户更改......)但我说绝对值​​得一试。你甚至不必支持所有领域。先说一下&amp;姓氏,电子邮件,电话,地址之一。这应该已经成为了一个巨大的帮助&#34; bucketing&#34;他们然后只从正确的桶中查询。


    代码审查;)

    缓存您的来电

    您有很多地图get来电。你在循环中调用System.today()(我会理解System.now ......但今天?)。检查this interesting short video并尽可能地拥有更多局部变量和更少的脚本语句。对于给定的帐户,字段列表没有变化 - 所以为什么每次迭代都会从地图中痛苦地获取它。

    检查逻辑

    if(newContactWithAccountMap.get(accountId) != null) - 这绝不会发生。如果没有联系人,那么&#34;&#34; acc被插入 - 无论如何你在做什么。你在这里过度保护还是实际需要这种检查(这会暗示一些更大的问题)。即使对于私人联系人(使用AccountId = null),您也不应该获取联系人并为不受影响的帐户构建地图。

    所以在第一次迭代中,我做了类似的事情(这是一个小改进,但谁知道,可能有所帮助):

    for(String accountId : accountIdSet){
        List<String> fields =  mapOfAccountWithFilters.get(accountId);  // it's always same for that Account, isn't it?
        for(Contact newContact : newContactWithAccountMap.get(accountId)){
            for(Contact oldContact : mapOfAccountIdWithItsContact.get(accountId)){
                if(oldContact.id != newContact.id){
                    Boolean allFieldsMatch = true;
                    for(String fieldName : fields){
                        if(allFieldsMatch &= (oldContact.get(fieldName) == newContact.get(fieldName))){
                            oldContactsToUpdateSet.add(oldContact);
                        }else{
                            break;
                        }
                    }
                    if(allFieldsMatch) {
                        duplicateContactSet.add(newContact.Id);
                        break;
                    }
                }
            }
        }
    }
    Date today = System.today();
    for(Contact c : oldContactsToUpdateSet){
        c.Last_De_Duplication_Date__c = today;
    }
    

    第二遍

    如果它没有帮助 - 你可能会尝试在这里加入我的想法。让我们说你已经将3个联系人插入到已经拥有10的帐户中了。内循环中的3 * 10 = 30个比较(他们都是全新的,所以比较的诀窍旧的和新的联系人的ID没有帮助。

    但是,如果你准备某种类似这样的复合键&#34; bucket&#34;我已经谈过你了,真的把它弄平了。

    for(String accountId : accountIdSet){
        List<String> fields =  mapOfAccountWithFilters.get(accountId);
        Set<String> oldContactBuckets = new Set<String>();
        /* I'm cheating here a bit. 
            All I want to know is whether there was a match. I don't care with which Contact.
            If you care - you'd have to convert this Set<String> to Map<String, Set<Id>> for example.
            Looks like you do care because you're setting this Last_De_Duplication_Date__c
            but I'll leave it as exercise for the reader :P
        */
        for(Contact oldContact : mapOfAccountIdWithItsContact.get(accountId)){
            oldContactBuckets.add(buildKey(oldContact, fields));
        }
        for(Contact newContact : newContactWithAccountMap.get(accountId)){
            String key = buildKey(newContact, fields);
            if(oldContactBuckets.contains(key)){
                duplicateContactSet.add(newContact.Id);
            }
        }
    }
    
    private String buildKey(Contact c, List<String> fields){
        List<String> temp = new List<String>();
        for(String fieldName : fields){
            temp.add(String.valueOf(c.get(fieldName)));
        }
        return String.join(temp, '\n'); // pick a field separator that's unlikely to appear in your real data. Tab maybe?
    }
    

    这将首先构建10个密钥,然后与传入数据的3个密钥进行比较。意思是只有13个等同于你的&#34;最里面的&#34;循环。

    如果这对您的情况没有帮助 - 您可以考虑将其重写为批量顶点我猜...

答案 1 :(得分:0)

部分答案可能是将此作为批处理过程。运行批处理代码时,处理的每个单独批处理记录都有自己的时间限制。批量顶点here还有更多内容。

您不会显示SOQL查询或了解帐户可能拥有的联系人数量。帐户和联系人的总数也很有用。没有这些,就很难确定问题所在。您可能需要查看SOQL优化。例如,像“not equals”(“!=”)这样的运算符效率低下。关于这个主题还有更多可以说的内容,你可以阅读它here

另一种可能性是您可以使用一个查询替换多个查询。在类似的情况下,我可以编写代码,每个站点执行一次查询(自定义对象)。这将限制我100个站点。相反,我对所有网站进行了查询(我们远远少于50,000限制),然后创建了一个地图,通过唯一的密钥存储查询结果。