核心数据:删除实体类型的所有对象,即清除表 [英] Core Data: delete all objects of an entity type, ie clear a table

查看:155
本文介绍了核心数据:删除实体类型的所有对象,即清除表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

之前已经问过这个问题,但是没有解决方案足以满足我的应用程序需求。



在我们设置的通信协议中,服务器发送每次执行同步时新的一组所有客户。早些时候,我们一直存储为plist。现在想使用Core Data。



可以有数千个条目。单独删除每个都需要很长时间。有没有办法删除Core Data中特定表中的所有行?

 从客户中删除

这个在sqlite中的调用立即发生。在Core Data中分别浏览每个项目在iPad1上可能需要30秒。



关闭Core Data是合理的,即删除持久存储和所有托管对象上下文,然后放入sqlite并对表执行delete命令?在此过程中没有其他活动进行,因此我不需要访问数据库的其他部分。

解决方案

Dave DeLong是一个专家,好,只是一切,所以我觉得我告诉耶稣如何在水上行走。他的帖子是从2009年,这是一个很久以前。



但是,Bot发布的链接的方法不一定是处理大型删除的最佳方式。



基本上,该帖子建议获取对象ID,然后遍历它们,在每个对象上调用delete。



问题是,当你删除一个对象时,它必须处理所有相关的关系,这可能会导致进一步获取。



如果必须像这样进行大规模删除,我建议调整整个数据库,以便可以隔离特定核心数据存储中的表。这样,你可以删除整个存储,并可能重建你想保留的小位。这可能是最快的方法。



但是,如果你想删除对象本身,你应该遵循这个模式...



在自动释放池中批量删除,并确保预先获取任何级联关系。所有这些,一起,将减少你必须实际去数据库的次数,因此,将减少执行删除所需的时间。




  1. 提取要删除的所有对象的ObjectIs

  2. 迭代整个列表,并删除每个对象

如果你有级联关系,你会遇到很多额外的数据库,IO是真的很慢。您希望尽可能减少访问数据库的次数。



虽然它最初听起来可能违反直觉,但您想要提取的数据超出了您想要删除的数量。原因是所有的数据可以在几个IO操作中从数据库获取。



所以,在你的获取请求,你想设置...



  [fetchRequest setRelationshipKeyPathsForPrefetching:@ [@relationship1,@relationship2,....,@relationship3]]; 

这些关系代表可能有级联删除规则的所有关系。



现在,当您的抓取完成后,您将拥有所有要删除的对象,以及由于这些对象被删除而被删除的对象。



如果你有一个复杂的层次结构,你想提前尽可能提前。否则,当您删除一个对象时,Core Data将要为每个对象单独获取每个关系,以便它可以管理级联删除。



这将浪费一个TON的时间,因为你将做更多的IO操作作为结果。



现在,在你的抓取完成后,你循环的对象,并删除它们。对于大型删除,你可以看到一个数量级的加速。



此外,如果你有很多对象,分成多个批次,自动释放池。



最后,在单独的后台线程中执行此操作,因此您的UI不会挂起。您可以使用单独的MOC,连接到持久存储协调器,并使主MOC处理DidSave通知以从其上下文中删除对象。



这看起来像代码,将其视为伪代码...

  NSManagedObjectContext * deleteContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateConcurrencyType]; 
//为同一个存储获取新的PSC
deleteContext.persistentStoreCoordinator = getInstanceOfPersistentStoreCoordinator();

//每次调用performBlock在自己的autoreleasepool中执行,所以我们不需要
//如果每个chunk都在一个单独的performBlock中完成,则需要显式使用
__block void(^ block)(void)= ^ {
NSFetchRequest * fetchRequest = //
//只获取要删除此迭代的对象数量
fetchRequest.fetchLimit = NUM​​_ENTITIES_TO_DELETE_AT_ONCE;
//预取所有关系
fetchRequest.relationshipKeyPathsForPrefetching = prefetchRelationships;
//不需要所有的属性
fetchRequest.includesPropertyValues = NO;
NSArray * results = [deleteContext executeFetchRequest:fetchRequest error:& error];
if(results.count == 0){
//没有为此获取任何对象
if(nil == results){
//处理错误
}
return;
}
for(MyEntity * entity in results){
[deleteContext deleteObject:entity];
}
[deleteContext save:& error];
[deleteContext reset];

//保持删除对象,直到它们都消失
[deleteContext performBlock:block];
};

[deleteContext preformBlock:block];

当然,你需要做适当的错误处理,但这是基本的想法。

如果您有这么多数据要删除,它会削弱内存,批量提取。
不要获取所有属性。
预取关系以最小化IO操作。
使用autoreleasepool保持内存不断增长。
删除上下文。
在后台线程上执行任务。



如果你有一个非常复杂的图形,请确保预取所有对象的所有级联关系



注意,您的主上下文必须处理DidSave通知,以保持其上下文与删除步骤一致。



编辑


感谢。很多好点。所有解释清楚,为什么要创建
单独的MOC?任何想法,不删除整个数据库,但
使用sqlite删除特定表中的所有行? - David


您使用单独的MOC,以便在长删除操作发生时UI不会被阻止。注意,当实际提交到数据库时,只有一个线程可以访问数据库,所以任何其他访问(如抓取)将阻止任何更新。这是将大型删除操作分成块的另一个原因。小工作将为其他MOC访问商店提供一些机会,而不必等待整个操作完成。



如果这导致问题,您也可以实现优先级队列(通过 dispatch_set_target_queue ),但这超出了此问题的范围。



在Core Data数据库上使用sqlite命令,Apple一再表示这是一个坏主意,您不应该在Core Data数据库文件上运行直接SQL命令。






最后,让我注意这一点。根据我的经验,我发现,当我有严重的性能问题,它通常是由于设计差或执行不当。重新审视您的问题,看看是否可以重新设计系统,以便更好地适应此用例。



如果您必须发送所有数据,后台线程并过滤新数据,因此您将数据分成三组:需要修改的对象,需要删除的对象和需要插入的对象。





如果数据几乎是全新的,那么请考虑重组你的数据库,这些实体有自己的数据库数据库(我假设你的数据库已经包含多个实体)。这样你可以只删除文件,并重新开始一个新的数据库。这很快。现在,重新插入几千个对象不会快。



您必须手动管理商店之间的任何关系。这不难,但它不是自动的,如同一个商店内的关系。



如果我这样做,我会先创建新的数据库,然后拆掉现有的数据库,请替换为新的,然后删除旧的。



如果您只通过此批处理机制操作您的数据库,并且不需要对象图管理,那么也许你想考虑使用sqlite而不是Core Data。


This has been asked before, but no solution described that is fast enough for my app needs.

In the communications protocol we have set up, the server sends down a new set of all customers every time a sync is performed. Earlier, we had been storing as a plist. Now want to use Core Data.

There can be thousands of entries. Deleting each one individually takes a long time. Is there a way to delete all rows in a particular table in Core Data?

delete from customer

This call in sqlite happens instantly. Going through each one individually in Core Data can take 30 seconds on an iPad1.

Is it reasonable to shut down Core Data, i.e. drop the persistence store and all managed object contexts, then drop into sqlite and perform the delete command against the table? No other activity is going on during this process so I don't need access to other parts of the database.

解决方案

Dave DeLong is an expert at, well, just about everything, and so I feel like I'm telling Jesus how to walk on water. Granted, his post is from 2009, which was a LONG time ago.

However, the approach in the link posted by Bot is not necessarily the best way to handle large deletes.

Basically, that post suggests to fetch the object IDs, and then iterate through them, calling delete on each object.

The problem is that when you delete a single object, it has to go handle all the associated relationships as well, which could cause further fetching.

So, if you must do large scale deletes like this, I suggest adjusting your overall database so that you can isolate tables in specific core data stores. That way you can just delete the entire store, and possibly reconstruct the small bits that you want to remain. That will probably be the fastest approach.

However, if you want to delete the objects themselves, you should follow this pattern...

Do your deletes in batches, inside an autorelease pool, and be sure to pre-fetch any cascaded relationships. All these, together, will minimize the number of times you have to actually go to the database, and will, thus, decrease the amount of time it takes to perform your delete.

In the suggested approach, which comes down to...

  1. Fetch ObjectIds of all objects to be deleted
  2. Iterate through the list, and delete each object

If you have cascade relationships, you you will encounter a lot of extra trips to the database, and IO is really slow. You want to minimize the number of times you have to visit the database.

While it may initially sound counterintuitive, you want to fetch more data than you think you want to delete. The reason is that all that data can be fetched from the database in a few IO operations.

So, on your fetch request, you want to set...

[fetchRequest setRelationshipKeyPathsForPrefetching:@[@"relationship1", @"relationship2", .... , @"relationship3"]];

where those relationships represent all the relationships that may have a cascade delete rule.

Now, when your fetch is complete, you have all the objects that are going to be deleted, plus the objects that will be deleted as a result of those objects being deleted.

If you have a complex hierarchy, you want to prefetch as much as possible ahead of time. Otherwise, when you delete an object, Core Data is going to have to go fetch each relationship individually for each object so that it can managed the cascade delete.

This will waste a TON of time, because you will do many more IO operations as a result.

Now, after your fetch has completed, then you loop through the objects, and delete them. For large deletes you can see an order of magnitude speed up.

In addition, if you have a lot of objects, break it up into multiple batches, and do it inside an auto release pool.

Finally, do this in a separate background thread, so your UI does not pend. You can use a separate MOC, connected to a persistent store coordinator, and have the main MOC handle DidSave notifications to remove the objects from its context.

WHile this looks like code, treat it as pseudo-code...

NSManagedObjectContext *deleteContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateConcurrencyType];
// Get a new PSC for the same store
deleteContext.persistentStoreCoordinator = getInstanceOfPersistentStoreCoordinator();

// Each call to performBlock executes in its own autoreleasepool, so we don't
// need to explicitly use one if each chunk is done in a separate performBlock
__block void (^block)(void) = ^{
    NSFetchRequest *fetchRequest = //
    // Only fetch the number of objects to delete this iteration
    fetchRequest.fetchLimit = NUM_ENTITIES_TO_DELETE_AT_ONCE;
    // Prefetch all the relationships
    fetchRequest.relationshipKeyPathsForPrefetching = prefetchRelationships;
    // Don't need all the properties
    fetchRequest.includesPropertyValues = NO;
    NSArray *results = [deleteContext executeFetchRequest:fetchRequest error:&error];
    if (results.count == 0) {
        // Didn't get any objects for this fetch
        if (nil == results) {
            // Handle error
        }
        return;
    }
    for (MyEntity *entity in results) {
        [deleteContext deleteObject:entity];
    }
    [deleteContext save:&error];
    [deleteContext reset];

    // Keep deleting objects until they are all gone
    [deleteContext performBlock:block];
};

[deleteContext preformBlock:block];

Of course, you need to do appropriate error handling, but that's the basic idea.

Fetch in batches if you have so much data to delete that it will cripple memory. Don't fetch all the properties. Prefetch relationships to minimize IO operations. Use autoreleasepool to keep memory from growing. Prune the context. Perform the task on a background thread.

If you have a really complex graph, make sure you prefetch all the cascaded relationships for all entities in your entire object graph.

Note, your main context will have to handle DidSave notifications to keep its context in step with the deletions.

EDIT

Thanks. Lots of good points. All well explained except, why create the separate MOC? Any thoughts on not deleting the entire database, but using sqlite to delete all rows from a particular table? – David

You use a separate MOC so the UI is not blocked while the long delete operation is happening. Note, that when the actual commit to the database happens, only one thread can be accessing the database, so any other access (like fetching) will block behind any updates. This is another reason to break the large delete operation into chunks. Small pieces of work will provide some chance for other MOC(s) to access the store without having to wait for the whole operation to complete.

If this causes problems, you can also implement priority queues (via dispatch_set_target_queue), but that is beyond the scope of this question.

As for using sqlite commands on the Core Data database, Apple has repeatedly said this is a bad idea, and you should not run direct SQL commands on a Core Data database file.


Finally, let me note this. In my experience, I have found that when I have a serious performance problem, it is usually a result of either poor design or improper implementation. Revisit your problem, and see if you can redesign your system somewhat to better accommodate this use case.

If you must send down all the data, perhaps query the database in a background thread and filter the new data so you break your data into three sets: objects that need modification, objects that need deletion, and objects that need to be inserted.

This way, you are only changing the database where it needs to be changed.

If the data is almost brand new every time, consider restructuring your database where these entities have their own database (I assume your database already contains multiple entities). That way you can just delete the file, and start over with a fresh database. That's fast. Now, reinserting several thousand objects is not going to be fast.

You have to manage any relationships manually, across stores. It's not difficult, but it's not automatic like relationships within the same store.

If I did this, I would first create the new database, then tear down the existing one, replace it with the new one, and then delete the old one.

If you are only manipulating your database via this batch mechanism, and you do not need object graph management, then maybe you want to consider using sqlite instead of Core Data.

这篇关于核心数据:删除实体类型的所有对象,即清除表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆