删除数据库中的许多记录时性能下降(Sql Server / EF5)

时间:2014-01-23 07:48:39

标签: sql sql-server database entity-framework entity-framework-5

我有使用Entity Framework 5的数据库清理作业。有一些业务规则来确定是否应该从数据库中删除实体,如果应删除一个父实体,也应删除各个相关实体。作业首先创建一个DbContext,对其执行所有其他查询和.Remove(someObj)操作。每次使用.Remove()将父项及其所有相关实体标记为删除时,我都会调用.SaveChanges()。我已经设置了db.Configuration.AutoDetectChangesEnabled = false;

我注意到性能会随着时间的推移而显着减慢。

例如,在具有5000个实体的数据库中,应删除500个,执行时间约为1:30。我应该修改规则,以便删除1000,执行时间最长为7分钟!

我试着注意不要在上下文中加载超过必要的数据,并尽量减少执行到数据库的查询数量。尽管如此,第一个100比同一个工作中的最后100个要快得多。

为什么呢?总内存使用量和对数据库执行的查询数似乎是合理的。

将工作分成较小的块(例如100),并为每个块创建一个新的DbContext,似乎没有帮助。

我还有其他可以考虑的事情吗?它可能与数据库或索引有关吗?

代码(它应该为每10个父迭代创建一个新的DbContext并在每个父代上调用.SaveChanges()):

    public void DeleteIndividuals(siteId)
    {     
        deleteShippedIndividuals(siteId, 30);
    }


    private void deleteShippedIndividuals(Guid siteId, int numberOfDaysUntilDelete)
    {
        double? numberOfDaysUntilDelete_double = Convert.ToDouble(numberOfDaysUntilDelete);

        bool cont = true;

        do
        {
            db = new TrackAndTraceEntities();
            db.Configuration.AutoDetectChangesEnabled = false;

            //Get Top level parents with status SHIPPED 
            var topLevelIndividuals = (from i in db.Individuals
                                       where i.IndividualStatusType.Id == TrackAndTraceConstants.IndividualStatusId.SHIPPED
                                       && i.Parent == null
                                       && i.SiteId == siteId
                                       && DateTime.Now > SqlFunctions.DateAdd("day", numberOfDaysUntilDelete_double, i.LastUpdate)
                                       orderby i.LastUpdate
                                       select i).Take(10).ToList();
            if (topLevelIndividuals.Count > 0)
            {
                cont = true;
            }
            else
            {
                cont = false;
            }

            //For each top level parent where has expire date is passed
            foreach (Individual topLevelIndividual in topLevelIndividuals)
            {
                deleteShippedIndividual(topLevelIndividual, numberOfDaysUntilDelete);
            }

        } while (cont);

    }

    private void deleteShippedIndividual(Individual topLevelIndividual, int numberOfDaysUntilDelete)
    {
        //Get all children on all levels in one big list
        List<Individual> allChildren = new List<Individual>();
        getAllChildren(topLevelIndividual, allChildren);

        //Iterate all children and check if all are not in status SHIPPED or have not expired
        foreach (Individual child in allChildren)
        {
            if (child.IndividualStatusType.Id != TrackAndTraceConstants.IndividualStatusId.SHIPPED)
            {
                //If structure has any child where status != SHIPPED, don't delete.
                return;
            }
            if (!(DateTime.Now > child.LastUpdate.AddDays(numberOfDaysUntilDelete)))
            {
                //If structure has any child that has not yet expired, don't delete.
                return;
            }
        }

        //Else delete entrie structure
        deleteEntireStructure(topLevelIndividual); 
    }

    private void getAllChildren(Individual parent, List<Individual> target)
    {
        var children = db.Individuals.Include("IndividualStatusType").Include("IndividualTests").Include("IndividualAdditionalNumbers").Where(i => i.Parent.Id == parent.Id).ToList();

        foreach (Individual child in children)
        {
            target.Add(child);
            getAllChildren(child, target);
        }

    }

    private void deleteEntireStructure(Individual topLevelIndividual)
    {
        try
        {
            //Structure must be deleted bottom up, so start with all leaf children
            if (topLevelIndividual.ChildIndividuals != null && topLevelIndividual.ChildIndividuals.Count > 0)
            {
                deleteLeafChildren(topLevelIndividual.ChildIndividuals.ToList());
            }

            //Finally delete the top parent
            doDeleteIndividual(topLevelIndividual);

            //And save changes
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            //Log and continue
            string shortMessage = "Failed to delete individual structure (top parent serialnumber = " + topLevelIndividual.SerialNo + ", site = " + topLevelIndividual.SiteId.ToString();
            string details = getExceptionDetails(ex);
            this.textBox1.Text += DateTime.Now + ": " + shortMessage + Environment.NewLine + details + Environment.NewLine + Environment.NewLine;
        }
    }

    private void deleteLeafChildren(List<Individual> children)
    {
        foreach (Individual child in children)
        {
            if (child.ChildIndividuals != null && child.ChildIndividuals.Count > 0)
            {
                deleteLeafChildren(child.ChildIndividuals.ToList());
            }

            doDeleteIndividual(child);

        }
    }

    private void doDeleteIndividual(Individual individual)
    {
        //Delete all additional numbers
        foreach (IndividualAdditionalNumber i in individual.IndividualAdditionalNumbers.ToList())
        {
            db.IndividualAdditionalNumbers.Remove(i);
        }
        individual.IndividualAdditionalNumbers = new List<IndividualAdditionalNumber>();

        // Delete tests
        foreach (IndividualTest i in individual.IndividualTests.ToList())
        {
            db.IndividualTests.Remove(i);
        }
        individual.IndividualTests = new List<IndividualTest>();

        db.Individuals.Remove(individual);
    }

1 个答案:

答案 0 :(得分:0)

唯一可能发生的事情是上下文中包含太多关于被修改的实体的信息,这些实体执行的次数越来越多。

确保在每次迭代时初始化上下文,并确保旧上下文超出范围,以便GC可以收集它。

我建议的是,你会绕过“正常”方式删除实体并使用类似的东西

context.Database.ExecuteSqlCommand("DELETE FROM "+table+" WHERE something="+value);

虽然您必须进行所有适当的检查,并确保以正确的顺序执行查询。

上述方法还确保您可以执行一个查询,而不是每行执行一次查询。

尝试这样的事情(我有类似的问题,这个解决方案运作得很好)

 class deleter
 {
     DbContext context = new DbContext();

     private void ExecuteInIterations(){
          List<int> idsToDelete = context.ItemsToDelete.Where(......).Select(x=>x.Id).ToList();
          for(/*Number of items*/)
          {
               context = new DbContext();
               ExecuteOneIteration(idsToDelete,startIndex,endIndex);
          }
     }

     private void ExecuteOneIteration(List<int> idsToDelete, int start, int end){
           // Do whatever in here
     }
 }

看到您的代码后,我建议进行以下更改

private void deleteShippedIndividuals(Guid siteId, int numberOfDaysUntilDelete)
{
    double? numberOfDaysUntilDelete_double = Convert.ToDouble(numberOfDaysUntilDelete);

    bool cont = true;

    do
    {
        using(var db = new TrackAndTraceEntities())
        {
            db.Configuration.AutoDetectChangesEnabled = false;

            //Get Top level parents with status SHIPPED 
            var topLevelIndividuals = (from i in db.Individuals
                                   where i.IndividualStatusType.Id == TrackAndTraceConstants.IndividualStatusId.SHIPPED
                                   && i.Parent == null
                                   && i.SiteId == siteId
                                   && DateTime.Now > SqlFunctions.DateAdd("day", numberOfDaysUntilDelete_double, i.LastUpdate)
                                   orderby i.LastUpdate
                                   select i).Take(10).ToList();

            if (topLevelIndividuals.Count > 0)
                cont = true;
            else 
                cont = false;

            //For each top level parent where has expire date is passed
            foreach (Individual topLevelIndividual in topLevelIndividuals)
            {
                deleteShippedIndividual(topLevelIndividual, numberOfDaysUntilDelete);
            }

        } while (cont);
    }

}