Ответ 1
Дейв ДеЛонг - эксперт, ну, почти все, и поэтому я чувствую, что говорю Иисусу, как ходить по воде. Конечно, его пост с 2009 года, который был ДЛИННЫМ временем назад.
Однако подход в ссылке, размещенной Bot, необязательно является лучшим способом обработки больших удалений.
В основном, это сообщение предлагает получить идентификаторы объектов, а затем перебирать их, вызывая удаление для каждого объекта.
Проблема заключается в том, что при удалении одного объекта он также должен обрабатывать все связанные с ним отношения, что может вызвать дальнейшую выборку.
Итак, если вы должны делать крупномасштабные удаления, подобные этому, я предлагаю настроить общую базу данных, чтобы вы могли изолировать таблицы в определенных хранилищах основных данных. Таким образом, вы можете просто удалить весь магазин и, возможно, восстановить небольшие биты, которые вы хотите сохранить. Вероятно, это будет самый быстрый подход.
Однако, если вы хотите удалить сами объекты, вы должны следовать этому шаблону...
Делайте свои удаления партиями, внутри пула автозаполнения, и обязательно загружайте любые каскадные отношения. Все это вместе позволит свести к минимуму количество раз, когда вы действительно хотите перейти в базу данных, и, таким образом, уменьшите время, необходимое для выполнения вашего удаления.
В предлагаемом подходе, который сводится к...
- Извлечь ObjectIds для всех объектов, подлежащих удалению.
- Итерации по списку и удаление каждого объекта
Если у вас есть каскадные отношения, вы столкнетесь с множеством дополнительных поездок в базу данных, а IO будет очень медленным. Вы хотите минимизировать количество посещений базы данных.
Хотя вначале это может показаться нелогичным, вы хотите получить больше данных, чем вы думаете, что хотите удалить. Причина в том, что все эти данные могут быть извлечены из базы данных в нескольких операциях ввода-вывода.
Итак, в вашем запросе выборки вы хотите установить...
[fetchRequest setRelationshipKeyPathsForPrefetching:@[@"relationship1", @"relationship2", .... , @"relationship3"]];
где эти отношения представляют все отношения, которые могут иметь правило каскадного удаления.
Теперь, когда ваша выборка завершена, у вас есть все объекты, которые будут удалены, плюс объекты, которые будут удалены в результате удаления этих объектов.
Если у вас сложная иерархия, вы хотите предварительно заблаговременно выполнить предварительную выборку. В противном случае, когда вы удаляете объект, Core Data должен будет извлекать каждую взаимосвязь отдельно для каждого объекта, чтобы он мог управлять удалением каскада.
Это потеряет время TON, потому что в результате вы сделаете еще много операций ввода-вывода.
Теперь, после того, как ваша выборка завершилась, вы пройдете через объекты и удалите их. Для больших удалений вы можете увидеть порядок ускорения.
Кроме того, если у вас много объектов, разбейте его на несколько партий и сделайте это внутри пула автозапуска.
Наконец, сделайте это в отдельном фоновом потоке, чтобы ваш пользовательский интерфейс не зависел. Вы можете использовать отдельный 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];
Конечно, вам нужно выполнить соответствующую обработку ошибок, но это основная идея.
Извлеките партии, если у вас есть так много данных, чтобы удалить их, что приведет к искажению памяти. Не извлекайте все свойства. Связывание предварительной выборки для минимизации операций ввода-вывода. Используйте autoreleasepool, чтобы память не увеличивалась. Сократите контекст. Выполните задачу в фоновом потоке.
Если у вас действительно сложный граф, убедитесь, что вы предварительно выбрали все каскадные отношения для всех объектов на всем графике объектов.
Обратите внимание, что ваш основной контекст должен будет обрабатывать уведомления DidSave, чтобы сохранить его контекст в шаге с удалениями.
ИЗМЕНИТЬ
Спасибо. Много хороших моментов. Все хорошо объяснено, кроме того, зачем создавать отдельный MOC? Любые мысли о том, что вы не удаляете всю базу данных, но используя sqlite для удаления всех строк из определенной таблицы? - Дэвид
Вы используете отдельный MOC, поэтому пользовательский интерфейс не блокируется во время длительной операции удаления. Обратите внимание, что при фактической фиксации базы данных только один поток может обращаться к базе данных, поэтому любой другой доступ (например, выборка) будет блокировать любые обновления. Это еще одна причина, чтобы разбить операцию большого удаления на куски. Небольшие части работы предоставят шанс другим MOC (ов) получить доступ к магазину, не дожидаясь завершения всей операции.
Если это вызывает проблемы, вы также можете реализовать очереди приоритетов (через dispatch_set_target_queue
), но это выходит за рамки этого вопроса.
Что касается использования SQL-команд в базе данных Core Data, Apple неоднократно заявляла, что это плохая идея, и вы не должны запускать прямые SQL-команды в файле базы данных Core Data.
Наконец, позвольте мне заметить это. По моему опыту, я обнаружил, что когда у меня серьезная проблема с производительностью, это обычно является результатом плохого дизайна или неправильной реализации. Повторите свою проблему и посмотрите, можете ли вы несколько раз перепроектировать систему, чтобы лучше разместить этот вариант использования.
Если вы должны отправить все данные, возможно, запросите базу данных в фоновом потоке и отфильтруйте новые данные, чтобы разбить ваши данные на три набора: объекты, которые нуждаются в модификации, объекты, которые нуждаются в удалении, и объекты, которые должны быть вставлены.
Таким образом вы изменяете базу данных только там, где ее нужно изменить.
Если данные кажутся почти новыми каждый раз, подумайте о реструктуризации своей базы данных, где у этих объектов есть своя база данных (я предполагаю, что ваша база данных уже содержит несколько объектов). Таким образом, вы можете просто удалить файл и начать с новой базы данных. Что быстро. Теперь повторная установка нескольких тысяч объектов не будет быстрой.
Вам нужно управлять любыми отношениями вручную, через магазины. Это не сложно, но это не автоматическое, как отношения внутри одного и того же магазина.
Если бы я сделал это, я бы сначала создал новую базу данных, затем снес бы существующую, заменил ее на новую, а затем удалил бы старый.
Если вы управляете только своей базой данных с помощью этого пакетного механизма, и вам не нужно управление графами объектов, то, возможно, вы захотите использовать sqlite вместо Core Data.