以前曾经问过这个问题,但没有一个解决方案能够满足我的应用需求。
在我们设置的通信协议中,每次执行同步时,服务器都会向下发送一组新客户。早些时候,我们一直存储为plist。现在想要使用Core Data。
可能有数千个条目。单独删除每一个需要很长时间。有没有办法删除Core Data中特定表中的所有行?
delete from customer
sqlite中的这个调用立即发生。在核心数据中单独浏览每一个在iPad1上可能需要30秒。
关闭Core Data是合理的,即删除持久性存储和所有托管对象上下文,然后放入sqlite并对表执行delete命令?在此过程中没有其他活动正在进行,因此我不需要访问数据库的其他部分。
答案 0 :(得分:25)
Dave DeLong是一位专家,嗯,几乎所有的东西,所以我觉得我告诉耶稣如何在水上行走。当然,他的帖子是从2009年开始的,这是很久以前的事了。
但是,Bot发布的链接中的方法不一定是处理大型删除的最佳方法。
基本上,该帖子建议获取对象ID,然后遍历它们,在每个对象上调用delete。
问题在于,当您删除单个对象时,它必须处理所有关联的关系,这可能会导致进一步的提取。
因此,如果您必须执行此类大规模删除操作,我建议您调整整个数据库,以便隔离特定核心数据存储中的表。这样你就可以删除整个商店,并可能重建你想要保留的小位。这可能是最快的方法。
但是,如果要删除对象本身,则应遵循此模式...
在自动释放池中批量删除,并确保预先获取任何级联关系。所有这些一起将最大限度地减少您实际进入数据库的次数,从而减少执行删除所需的时间。
建议的方法,归结为......
如果您有级联关系,那么您将遇到大量额外的数据库访问,并且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)
使用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执行某些操作的任何其他线程。如果上下文试图访问持久存储,您的应用程序将崩溃。