Android APIを使用している場合:
SQLite でのロックはファイル レベルで行われ、異なるスレッドや接続からの変更のロックが保証されます。したがって、複数のスレッドがデータベースを読み取ることができますが、書き込みは 1 つしかできません。
SQLite でのロックの詳細については、SQLiteのドキュメントを参照してください。ただし、OS Android が提供する API に最も関心があります。
2 つの同時スレッドによる書き込みは、単一のデータベース接続と複数のデータベース接続の両方から行うことができます。データベースに書き込むことができるスレッドは 1 つだけなので、次の 2 つのバリアントがあります。
- 1 つの接続の 2 つのスレッドから書き込みを行う場合、一方のスレッドは他方のスレッドが書き込みを完了するのを待機します。
- 異なる接続の 2 つのスレッドから書き込むと、エラーが発生します。すべてのデータがデータベースに書き込まれず、アプリケーションが SQLiteDatabaseLockedException で中断されます。アプリケーションが常に SQLiteOpenHelper のコピーを 1 つだけ (開いている接続のみ) 持つ必要があることが明らかになりました。そうしないと、いつでも SQLiteDatabaseLockedException が発生する可能性があります。
単一の SQLiteOpenHelper でのさまざまな接続
SQLiteOpenHelper には、データベースgetReadableDatabase()とgetWritableDatabase( ) へのアクセスを提供する 2 つのメソッドがあり、それぞれデータの読み取りと書き込みを行うことは誰もが知っています。ただし、ほとんどの場合、実際の接続は 1 つです。さらに、それは 1 つの同じオブジェクトです。
SQLiteOpenHelper.getReadableDatabase()==SQLiteOpenHelper.getWritableDatabase()
これは、データが読み取られるメソッドの使用に違いがないことを意味します。ただし、文書化されていない、より重要な別の問題があります。クラス SQLiteDatabase 内には、変数 mLock という独自のロックがあります。オブジェクト SQLiteDatabase のレベルで書き込みをロックします。読み取りおよび書き込み用の SQLiteDatabase のコピーが 1 つしかないため、データの読み取りもブロックされます。トランザクションで大量のデータを書き込むと、より顕著に表示されます。
最初の起動時に大量のデータ ( BLOB を含む約 7000 行) をバックグラウンドでダウンロードしてデータベースに保存するアプリケーションの例を考えてみましょう。データがトランザクション内に保存される場合、保存には約 1 秒かかります。45 秒かかりますが、読み取りクエリのいずれかがブロックされているため、ユーザーはアプリケーションを使用できません。データが小さな部分に保存されている場合、更新プロセスはかなり長い時間 (10 ~ 15 分) 引き延ばされますが、ユーザーは制限や不便なしにアプリケーションを使用できます。「両刃の剣」 – 高速または便利。
次のメソッドが追加されたため、Google は SQLiteDatabase 機能に関連する問題の一部を既に修正しています。
beginTransactionNonExclusive() – 「IMMEDIATE モード」でトランザクションを作成します。
yieldIfContendedSafely() – 他のスレッドによるタスクの完了を許可するために、一時的にトランザクションを捕捉します。
isDatabaseIntegrityOk() – データベースの整合性をチェックします
詳細については、ドキュメントを参照してください。
ただし、古いバージョンの Android では、この機能も必要です。
ソリューション
最初のロックをオフにして、どのような状況でもデータを読み取れるようにする必要があります。
SQLiteDatabase.setLockingEnabled(false);
内部クエリ ロックを使用してキャンセル – Java クラスのロジック レベル (SQLite に関するロックとは関係ありません)
SQLiteDatabase.execSQL(“PRAGMA read_uncommitted = true;”);
キャッシュからのデータの読み取りを許可します。実際、分離のレベルを変更します。このパラメータは、接続ごとに新たに設定する必要があります。多数の接続がある場合、このコマンドを呼び出す接続のみに影響します。
SQLiteDatabase.execSQL(“PRAGMA synchronous=OFF”);
データベースへの書き込み方法を変更します – 「同期」なし。このオプションを有効にすると、システムに予期せぬ障害が発生したり、電源がオフになったりすると、データベースが破損する可能性があります。ただし、SQLite のドキュメントによると、オプションがアクティブ化されていない場合、一部の操作は 50 倍速く実行されます。
残念ながら、Android ではすべてのPRAGMAがサポートされているわけではありません。たとえば、「<strong>PRAGMA locked_mode = NORMAL」や「<strong>PRAGMA journal_mode = OFF」などはサポートされていません。PRAGMA データを呼び出そうとすると、アプリケーションは失敗します。
メソッドsetLockingEnabledのドキュメントでは、このメソッドは、データベースでのすべての作業が単一のスレッドから行われることが確実な場合にのみ使用することをお勧めします。一度に 1 つのトランザクションのみが保持されることを保証する必要があります。また、デフォルト トランザクション (排他的トランザクション) の代わりに、即時トランザクションを使用する必要があります。古いバージョンの Android (API 11 未満) では、Java ラッパーを介して即時トランザクションを作成するオプションはありませんが、SQLite はこの機能をサポートしています。即時モードでトランザクションを初期化するには、次の SQLite クエリをデータベースに対して直接実行する必要があります。たとえば、execSQL メソッドを使用します。
SQLiteDatabase.execSQL(“すぐにトランザクションを開始”);
トランザクションは直接クエリによって初期化されるため、同じ方法で終了する必要があります。
SQLiteDatabase.execSQL(“コミットトランザクション”);
次に、必要なタイプのトランザクションを開始および終了する TransactionManager を実装する必要があります。TransactionManager の目的は、変更に関するすべてのクエリ (挿入、更新、削除、DDL クエリ) が同じスレッドから発生することを保証することです。
これが将来の訪問者に役立つことを願っています!!!