web-dev-qa-db-ja.com

Android)でデータをメモリとデータベースに同時に保持するためのベストプラクティス

大量のデータ( "customers"、 "products"、 "orders" ...)を持つAndroidアプリを設計しており、毎回SQLiteにクエリを実行する必要はありません。レコードが必要です。データベースへのクエリはできるだけ避けたいので、特定のデータを常にメモリに保持することにしました。

私たちの最初のアイデアは、2つの単純なクラスを作成することです。

  1. 「MemoryRecord」:基本的にオブジェクトの配列(string、int、double、datetimeなど)、テーブルレコードからのデータ、およびこれらのデータをこれから出し入れするためのすべてのメソッドを含むクラス。アレイ。

  2. 「MemoryTable」:基本的に[Key、MemoryRecord]のマップと、このマップを操作し、データベースにレコードを挿入/更新/削除するためのすべてのメソッドを含むクラス。

これらのクラスは、データベースにあるあらゆる種類のテーブルから派生します。もちろん、上記にリストされていない他の便利な方法もありますが、現時点では重要ではありません。

そのため、アプリを起動するときに、これらのクラスを使用してSQLiteデータベースからメモリにこれらのテーブルを読み込みます。データを変更する必要があるたびに、メモリを変更して、すぐにデータベースに投稿します。

しかし、私たちはあなたからの助け/アドバイスを求めています。そのようなことを実装するために、より単純または効率的な何かを提案できますか?それとも、すでに私たちのためにそれを行っているいくつかの既存のクラスですか?

私はあなたたちが私に見せようとしていることを理解しています、そして私はそれをありがとうございます。

ただし、2000レコードのテーブルがあり、それらのレコードをリストする必要があるとします。それぞれについて、他の30個のテーブル(1000レコードのテーブル、10レコードのテーブル)にクエリを実行して、リストに追加情報を追加する必要があります。これは、「飛行中」です(ご存知のとおり、非常に高速である必要があります)。現時点で)。

ここで、「これらすべての「結合」を使用してメインクエリを作成し、必要なものをすべて1つのステップで実行するだけです。データベースが適切に設計されている場合など、SQLiteは非常に高速になります...」と言います。

OK、しかしこのクエリは非常に複雑で確実になりますが、SQLiteは非常に高速ですが、「遅すぎる」でしょう(私が確認したように、2〜4秒で、これは私たちにとって許容できる時間ではありません)。

もう1つの複雑な点は、ユーザーの操作に応じて、関連するテーブルが同じではないため、すべてのレコードを「再クエリ」する必要があり、別のテーブルのセットと「再結合」する必要があることです。

したがって、代替手段は、結合なしでメインレコードのみを取得し(これは、ユーザーが何をしたり、望んでいるかに関係なく変更されません)(これは非常に高速です!)、データが必要になるたびに他のテーブルにクエリを実行することです。 10レコードしかないテーブルでは、同じレコードを何度もフェッチすることに注意してください。この場合、SQLiteが高速であっても、一種の「メモリキャッシュ」からレコードを取得するよりも、クエリ、カーソル、フェッチなどのコストが常に高くなるため、時間の無駄です。すべてのデータを常にメモリに保持するのではなく、頻繁にクエリを実行する一部のテーブルだけを保持する予定であることを明確にしておきます。

そして、最初の質問に行き着きました。これらのレコードを「キャッシュ」するための最良の方法は何ですか? 「なぜデータをキャッシュする必要があるのか​​」ではなく、それに焦点を当てて議論するのが本当に好きです。

30
Christian

プラットフォーム上のアプリの大部分(連絡先、メール、Gmail、カレンダーなど)はこれを行いません。これらのいくつかは、潜在的に大量のデータを含む非常に複雑なデータベーススキーマを持っており、これを行う必要はありません。あなたが提案していることは、あなたに巨大な痛みを引き起こし、明確な利益はありません。

最初に、効率的なクエリを実行できるようにデータベースとスキーマの設計に集中する必要があります。データベースアクセスが遅いと私が考えることができる主な理由は2つあります。

  • あなたは本当に複雑なデータスキーマを持っています。
  • 非常に大量のデータがあります。

大量のデータがある場合、とにかくすべてをメモリに保持する余裕はないので、これは行き止まりです。複雑な構造がある場合は、どちらの場合も、パフォーマンスを向上させるためにそれらを最適化することでメリットが得られます。どちらの場合も、データベーススキーマが優れたパフォーマンスの鍵となります。

実際にスキーマを最適化することは少し難しいかもしれません(そして私はそれについての専門家ではありません)が、注意すべきいくつかのことは、クエリする行にインデックスを正しく作成すること、結合を設計して効率的なパスを取ることなどです。 。この分野であなたを助けることができる人はたくさんいると確信しています。

また、プラットフォームのデータベースのいくつかのソースを調べて、優れたパフォーマンスを実現するための設計方法のアイデアを得ることができます。たとえば、連絡先データベース(特に2.0以降)は非常に複雑で、さまざまな種類のクエリを含む比較的大きなデータや拡張可能なデータセットで優れたパフォーマンスを提供するために多くの最適化が行われています。

更新:

これは、データベースの最適化がいかに重要であるかを示す良い例です。 Androidのメディアプロバイダーデータベースでは、プラットフォームの新しいバージョンでスキーマが大幅に変更され、いくつかの新機能が追加されました。既存のメディアデータベースを新しいスキーマに変更するためのアップグレードコードは、実行に8分以上かかる場合があります。

エンジニアは、実際のテストデータベースのアップグレード時間を8分から8秒に短縮する最適化を行いました。 60倍のパフォーマンスの向上。

この最適化は何でしたか?

これは、アップグレードの時点で、アップグレード操作で使用される重要な列に一時的なインデックスを作成することでした。 (そして、完了したら削除します。)したがって、この60倍のパフォーマンスの向上は、アップグレード中に使用される列の1つにインデックスを作成するために必要な時間も含まれています。

SQLiteは、自分が何をしているのかを知っていれば、非常に効率的である可能性があるものの1つです。また、使い方に注意を払わないと、パフォーマンスが悪くなる可能性があります。ただし、パフォーマンスの問題が発生している場合は、SQLiteの使用方法を改善することで修正できるので安全です。

66
hackbod

もちろん、メモリキャッシュの問題は、データベースとの同期を維持する必要があることです。データベースへのクエリは実際には非常に高速であることがわかりました。ここで事前に最適化している可能性があります。さまざまなデータセットを使用してクエリで多くのテストを実行しましたが、10〜20ミリ秒以上かかることはありません。

もちろん、それはすべてデータの使用方法によって異なります。 ListViewsは、多数の行を処理するように非常によく最適化されています(5000の範囲でテストしましたが、実際の問題はありません)。

メモリキャッシュを使用する場合は、内容が変更されたときにデータベースにキャッシュを通知させてから、キャッシュを更新することができます。そうすれば、誰でもキャッシュについて知らなくてもデータベースを更新できます。また、データベース上にContentProviderを構築する場合、registerContentObserverを使用して登録すると、ContentResolverを使用して変更を通知できます。

5
dhaag23