Основные данные: удалить все объекты типа сущности, т.е. Очистить таблицу

Об этом было задано ранее, но ни одно из описанных решений не достаточно быстро для моего приложения.

В коммуникационном протоколе, который мы установили, сервер отправляет новый набор всех клиентов каждый раз, когда выполняется синхронизация. Раньше мы хранили как plist. Теперь вы хотите использовать Core Data.

Здесь могут быть тысячи записей. Удаление каждого из них в отдельности занимает много времени. Есть ли способ удалить все строки в конкретной таблице в Core Data?

delete from customer

Этот вызов в sqlite происходит мгновенно. Прохождение каждого из них по отдельности в Core Data может занять 30 секунд на iPad1.

Можно ли отключить основные данные, т.е. отбросить хранилище персистентности и все контексты управляемых объектов, а затем перейти в sqlite и выполнить команду delete в таблице? Во время этого процесса не происходит никакой другой активности, поэтому мне не нужен доступ к другим частям базы данных.

Ответ 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.

Ответ 2

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 также был обновлен до этого метода.

Ответ 3

Действительно, ваш единственный вариант - удалить их отдельно. Я делаю этот метод с тонны объектов, и это довольно быстро. Это способ, которым кто-то делает это, только загружая идентификатор управляемого объекта, чтобы он предотвращал ненужные накладные расходы и делал это быстрее.

Основные данные: самый быстрый способ удалить все экземпляры объекта

Ответ 4

Да, разумно удалить постоянное хранилище и начать с нуля. Это происходит довольно быстро. Вы можете удалить постоянное хранилище (с постоянным адресом хранилища) из координатора постоянного хранилища, а затем использовать URL-адрес сохраняющегося хранилища для удаления файла базы данных из папки вашего каталога. Я сделал это с помощью NSFileManager removeItemAtURL.

Изменить: нужно учитывать: обязательно отключите/освободить текущий экземпляр NSManagedObjectContext и остановите любой другой поток, который может что-то делать с NSManagedObjectContext, который использует тот же постоянный магазин. Ваше приложение выйдет из строя, если контекст попытается получить доступ к постоянному хранилищу.