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

Dav*_*vid 10 sqlite core-data ios

之前已经提出过这个问题,但没有描述的解决方案足以满足我的应用需求.

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

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

delete from customer
Run Code Online (Sandbox Code Playgroud)

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

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

Jod*_*ins 25

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

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

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

问题是,当您删除单个对象时,它必须处理所有关联的关系,这可能会导致进一步的提取.

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

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

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

在建议的方法,归结为......

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

如果您有级联关系,那么您将遇到大量额外的数据库访问,并且IO非常慢.您希望最小化访问数据库的次数.

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

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

[fetchRequest setRelationshipKeyPathsForPrefetching:@[@"relationship1", @"relationship2", .... , @"relationship3"]];
Run Code Online (Sandbox Code Playgroud)

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

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

如果您有一个复杂的层次结构,则希望尽可能提前预取.否则,当您删除对象时,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];
Run Code Online (Sandbox Code Playgroud)

当然,您需要进行适当的错误处理,但这是基本的想法.

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

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

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

编辑

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

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

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

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


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

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

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

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

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

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

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


Sur*_*gch 6

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)
}
Run Code Online (Sandbox Code Playgroud)

请注意,@ Boot关联的答案和@JodyHagins提到的答案也已更新为此方法.