核心数据:删除实体类型的所有对象,即清除表格

之前已经问过这个问题,但是没有任何解决方案能够满足我的应用需求。

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

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

delete from customer 

sqlite中的这个调用立即发生。 在Core1中单独浏览每一个可能需要30秒才能在iPad1上完成。

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

Dave DeLong是一位专家,几乎所有的东西,所以我觉得我告诉耶稣如何在水上行走。 当然,他的post是从2009年开始的,这是很久以前的事了。

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

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

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

因此,如果您必须执行此类大规模删除操作,我建议您调整整个数据库,以便隔离特定核心数据存储中的表。 这样您就可以删除整个商店,并可能重建您想要保留的小位。 这可能是最快的方法。

但是,如果要删除对象本身,则应遵循此模式…

在自动释放池中批量删除,并确保预先获取任何级联关系。 所有这些一起将最大限度地减少您实际进入数据库的次数,从而减少执行删除所需的时间。

在建议的方法,这归结为……

  1. 获取要删除的所有对象的ObjectIds
  2. 遍历列表,删除每个对象

如果你有级联关系,那么你会遇到很多额外的数据库访问,并且IO非常慢。 您希望最小化访问数据库的次数。

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

因此,在您的获​​取请求中,您要设置…

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

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

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

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

这将浪费大量时间,因为您将进行更多的IO操作。

现在,在您的提取完成后,您将遍历对象并删除它们。 对于大型删除,您可以看到一个数量级的加速。

此外,如果您有很多对象,请将其分解为多个批次,并在自动释放池中执行。

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

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

 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]; 

当然,您需要进行适当的error handling,但这是基本的想法。

如果你有太多要删除的数据,它会批量获取它会削弱内存。 不要获取所有属性。 预取关系以最小化IO操作。 使用autoreleasepool来防止内存增长。 修剪上下文。 在后台线程上执行任务。

如果您有一个非常复杂的图形,请确保预取整个对象图中所有实体的所有级联关系。

请注意,您的主要上下文必须处理DidSave通知,以使其上下文与删除保持同步。

编辑

谢谢。 很多好点。 除了为什么创建单独的MOC之外,所有解释都很好? 有没有删除整个数据库,但使用sqlite删除特定表中的所有行的想法? – 大卫

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

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

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


最后,让我注意一下。 根据我的经验,我发现当我遇到严重的性能问题时,通常是设计不良或实施不当造成的。 重新审视您的问题,看看您是否可以稍微重新设计系统以更好地适应此用例。

如果必须发送所有数据,可能在后台线程中查询数据库并过滤新数据,以便将数据分成三组:需要修改的对象,需要删除的对象和需要插入的对象。

这样,您只需要更改需要更改的数据库。

如果数据每次都是全新的,请考虑重构您的数据库,这些实体拥有自己的数据库(我假设您的数据库已经包含多个实体)。 这样你就可以删除文件,并重新开始使用新的数据库。 那很快。 现在,重新插入数千个对象并不会很快。

您必须跨商店手动管理任何关系。 这并不难,但它不像同一商店内的关系那样自动化。

如果我这样做,我将首先创建新数据库,然后拆除现有数据库,用新数据库替换它,然后删除旧数据库。

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

iOS 9及更高版本

使用NSBatchDeleteRequest 。 我在Core Data实体上的模拟器中测试了这个,其中有超过400,000个实例,并且删除几乎是即时的。

 // fetch all items in entity and request to delete them let fetchRequest = NSFetchRequest(entityName: "MyEntity") let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) // delegate objects let myManagedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext let myPersistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // perform the delete do { try myPersistentStoreCoordinator.executeRequest(deleteRequest, withContext: myManagedObjectContext) } catch let error as NSError { print(error) } 

请注意@Bot链接的答案以及@JodyHagins提到的答案也已更新为此方法。

真的,你唯一的选择是单独删除它们。 我用大量的物体做这个方法,它很快。 这是有人通过仅加载托管对象ID来实现它的方式,因此它可以防止任何不必要的开销并使其更快。

核心数据:删除实体所有实例的最快方法

是的,删除持久存储并从头开始是合理的。 这发生得相当快。 您可以做的是从持久性存储协调器中删除持久性存储(使用持久性存储URL),然后使用持久性存储的URL从目录文件夹中删除数据库文件。 我是使用NSFileManager的removeItemAtURL完成的。

编辑:要考虑的一件事:确保禁用/释放当前的NSManagedObjectContext实例,并停止可能正在使用同一持久存储的NSManagedObjectContext执行某些操作的任何其他线程。 如果上下文尝试访问持久性存储,您的应用程序将崩溃。

Interesting Posts