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

时间:2012-08-24 17:20:51

标签: ios sqlite core-data

以前曾经问过这个问题,但没有一个解决方案能够满足我的应用需求。

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

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

delete from customer

sqlite中的这个调用立即发生。在核心数据中单独浏览每一个在iPad1上可能需要30秒。

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

4 个答案:

答案 0 :(得分:25)

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

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

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

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

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

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

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

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

  1. 获取要删除的所有对象的ObjectIds
  2. 遍历列表,删除每个对象
  3. 如果您有级联关系,那么您将遇到大量额外的数据库访问,并且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];
    

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

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

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

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

    修改

      

    感谢。很多好点。除了,为什么创造了   单独的MOC?关于不删除整个数据库的任何想法,但是   使用sqlite删除特定表中的所有行? - 大卫

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

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

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


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

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

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

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

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

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

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

答案 1 :(得分: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)
}

请注意@Bot链接到的the answer以及@JodyHagins提到的{{3}}也已更新为此方法。

答案 2 :(得分:1)

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

Core Data: Quickest way to delete all instances of an entity

答案 3 :(得分:-1)

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

完成的

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