デイブ・デロングは、まあ、ほぼすべての専門家なので、私はイエスに水の上を歩く方法を教えているように感じます. 確かに、彼の投稿は 2009 年のもので、かなり前のことです。
ただし、ボットによって投稿されたリンクのアプローチは、大規模な削除を処理するための最良の方法であるとは限りません。
基本的に、その投稿では、オブジェクト ID をフェッチし、それらを繰り返し処理して、各オブジェクトで削除を呼び出すことを提案しています。
問題は、単一のオブジェクトを削除すると、関連するすべての関係も処理する必要があり、さらにフェッチが発生する可能性があることです。
したがって、このような大規模な削除を行う必要がある場合は、データベース全体を調整して、特定のコア データ ストアにテーブルを分離できるようにすることをお勧めします。そうすれば、ストア全体を削除して、残しておきたい小さなビットを再構築することができます. それがおそらく最速のアプローチになるでしょう。
ただし、オブジェクト自体を削除する場合は、このパターンに従う必要があります...
自動解放プール内でバッチで削除を行い、カスケードされた関係を必ずプリフェッチしてください。これらすべてを組み合わせることで、実際にデータベースにアクセスする回数が最小限に抑えられるため、削除の実行にかかる時間が短縮されます。
提案されたアプローチでは、それは...
- 削除するすべてのオブジェクトの ObjectId を取得します
- リストを反復処理し、各オブジェクトを削除します
カスケード関係がある場合、データベースへの余分なトリップが多数発生し、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 の使用を検討することをお勧めします。