10

これは以前に尋ねられたことがありますが、私のアプリのニーズに十分に対応できる解決策は説明されていません。

設定した通信プロトコルでは、サーバーは同期が実行されるたびにすべての顧客の新しいセットを送信します。以前は、plist として保存していました。Core Data を使用したいと考えています。

何千ものエントリが存在する可能性があります。個別に削除すると時間がかかります。Core Data の特定のテーブルのすべての行を削除する方法はありますか?

delete from customer

sqlite でのこの呼び出しは即座に行われます。Core Data でそれぞれを個別に処理すると、iPad1 で 30 秒かかる場合があります。

Core Data をシャットダウンすること、つまり永続ストアとすべての管理対象オブジェクト コンテキストを削除してから sqlite にドロップし、テーブルに対して削除コマンドを実行することは合理的ですか? このプロセス中は他のアクティビティは行われないため、データベースの他の部分にアクセスする必要はありません。

4

4 に答える 4

26

デイブ・デロングは、まあ、ほぼすべての専門家なので、私はイエスに水の上を歩く方法を教えているように感じます. 確かに、彼の投稿は 2009 年のもので、かなり前のことです。

ただし、ボットによって投稿されたリンクのアプローチは、大規模な削除を処理するための最良の方法であるとは限りません。

基本的に、その投稿では、オブジェクト ID をフェッチし、それらを繰り返し処理して、各オブジェクトで削除を呼び出すことを提案しています。

問題は、単一のオブジェクトを削除すると、関連するすべての関係も処理する必要があり、さらにフェッチが発生する可能性があることです。

したがって、このような大規模な削除を行う必要がある場合は、データベース全体を調整して、特定のコア データ ストアにテーブルを分離できるようにすることをお勧めします。そうすれば、ストア全体を削除して、残しておきたい小さなビットを再構築することができます. それがおそらく最速のアプローチになるでしょう。

ただし、オブジェクト自体を削除する場合は、このパターンに従う必要があります...

自動解放プール内でバッチで削除を行い、カスケードされた関係を必ずプリフェッチしてください。これらすべてを組み合わせることで、実際にデータベースにアクセスする回数が最小限に抑えられるため、削除の実行にかかる時間が短縮されます。

提案されたアプローチでは、それは...

  1. 削除するすべてのオブジェクトの ObjectId を取得します
  2. リストを反復処理し、各オブジェクトを削除します

カスケード関係がある場合、データベースへの余分なトリップが多数発生し、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 がブロックされないようにします。データベースへの実際のコミットが発生すると、1 つのスレッドのみがデータベースにアクセスできるため、他のアクセス (フェッチなど) は更新の背後でブロックされることに注意してください。これは、大規模な削除操作をチャンクに分割するもう 1 つの理由です。小さな作業は、操作全体が完了するのを待たずに、他の MOC がストアにアクセスする機会を提供します。

これにより問題が発生する場合は、(経由でdispatch_set_target_queue) プライオリティ キューを実装することもできますが、それはこの質問の範囲を超えています。

Core Data データベースでの sqlite コマンドの使用に関して、Apple はこれは悪い考えであると繰り返し述べており、Core Data データベース ファイルで直接 SQL コマンドを実行するべきではありません。


最後に、これに注意してください。私の経験では、重大なパフォーマンスの問題が発生した場合、通常は設計が不十分であるか、実装が不適切であることが原因であることがわかりました。問題を再検討し、このユースケースにより適切に対応するためにシステムをいくらか再設計できるかどうかを確認してください。

すべてのデータを送信する必要がある場合は、おそらくバックグラウンド スレッドでデータベースにクエリを実行し、新しいデータをフィルター処理して、データを 3 つのセット (変更が必要なオブジェクト、削除が必要なオブジェクト、および挿入が必要なオブジェクト) に分割します。

このようにして、変更する必要があるデータベースのみを変更します。

データが毎回ほとんど新しい場合は、これらのエンティティが独自のデータベースを持つデータベースを再構築することを検討してください (データベースには既に複数のエンティティが含まれていると想定しています)。そうすれば、ファイルを削除して、新しいデータベースからやり直すことができます。それは速いです。現在、数千のオブジェクトを再挿入するのは高速ではありません。

ストア全体で手動で関係を管理する必要があります。難しいことではありませんが、同じ店舗内の人間関係のように自動ではありません。

これを行う場合、最初に新しいデータベースを作成し、次に既存のデータベースを破棄して新しいデータベースに置き換え、古いデータベースを削除します。

このバッチ メカニズムを介してデータベースを操作するだけで、オブジェクト グラフの管理が必要ない場合は、Core Data の代わりに sqlite の使用を検討することをお勧めします。

于 2012-08-24T20:45:16.963 に答える
6

iOS 9 以降

を使用しNSBatchDeleteRequestます。400,000 を超えるインスタンスを持つ Core Data エンティティのシミュレーターでこれをテストしたところ、削除はほぼ瞬時に行われました。

// 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 が言及した回答もこのメソッドに更新されていることに注意してください。

于 2015-08-16T03:59:58.937 に答える
-1

はい、永続ストアを削除して最初からやり直すのが合理的です。これはかなり迅速に発生します。できることは、永続ストア コーディネーターから永続ストア (永続ストア URL を使用) を削除し、永続ストアの URL を使用してディレクトリ フォルダーからデータベース ファイルを削除することです。NSFileManager の removeItemAtURL を使用して実行しました。

編集:考慮すべき1つのこと:現在のNSManagedObjectContextインスタンスを無効化/解放し、同じ永続ストアを使用しているNSManagedObjectContextで何かをしている可能性のある他のスレッドを停止してください。コンテキストが永続ストアにアクセスしようとすると、アプリケーションがクラッシュします。

于 2012-08-24T17:28:10.677 に答える