15

sqlite FAQ から、私はそれを知っていました:

複数のプロセスが同時に同じデータベースを開くことができます。複数のプロセスが同時に SELECT を実行できます。ただし、いつでもデータベースに変更を加えることができるプロセスは 1 つだけです。

したがって、私が理解している限り、次のことができます:1)複数のスレッドからdbを読み取る(SELECT) 2)複数のスレッドからdbを読み取り(SELECT)、単一のスレッドから書き込む(CREATE、INSERT、DELETE)

しかし、リーダーがライターをブロックせず、ライターがリーダーをブロックしないため、より多くの同時実行性を提供するWrite-Ahead Loggingについて読みました。読み取りと書き込みは同時に進行できます。

最後に、指定された場合、見つけたときに完全に混乱しました:

SQLITE_LOCKED エラーが発生するその他の理由は次のとおりです。

  • SELECT ステートメントがまだ保留中に、テーブルまたはインデックスを CREATE または DROP しようとしています。
  • 同じテーブルで SELECT がアクティブなときにテーブルに書き込もうとしています。
  • sqlite が設定されていない場合、マルチスレッド アプリケーションで同じテーブルに対して同時に 2 つの SELECT を実行しようとしています。
  • DB ファイルでの fcntl(3,F_SETLK 呼び出しが失敗します。これは、たとえば NFS ロックの問題が原因である可能性があります。この問題の 1 つの解決策は、DB を mv してからコピーし直して、新しい Inode 値を持つようにすることです。

では、いつロックを回避する必要があるのか​​ を明確にしたいと思いますか?2 つの異なるスレッドから同時に読み取りと書き込みを行うことはできますか? ありがとう。

4

3 に答える 3

10

Android APIを使用している場合:

SQLite でのロックはファイル レベルで行われ、異なるスレッドや接続からの変更のロックが保証されます。したがって、複数のスレッドがデータベースを読み取ることができますが、書き込みは 1 つしかできません。

SQLite でのロックの詳細については、SQLiteのドキュメントを参照してください。ただし、OS Android が提供する API に最も関心があります。

2 つの同時スレッドによる書き込みは、単一のデータベース接続と複数のデータベース接続の両方から行うことができます。データベースに書き込むことができるスレッドは 1 つだけなので、次の 2 つのバリアントがあります。

  1. 1 つの接続の 2 つのスレッドから書き込みを行う場合、一方のスレッドは他方のスレッドが書き込みを完了するのを待機します。
  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 クエリ) が同じスレッドから発生することを保証することです。

これが将来の訪問者に役立つことを願っています!!!

于 2016-08-29T20:27:36.723 に答える
5

SQLite に固有ではない:

1) アプリケーション レベルでロックの競合が発生する状況を適切に処理するコードを記述します。これが「不可能」になるようにコードを書いたとしても。トランザクションの再試行を使用し (つまり、SQLITE_LOCKED は、「再試行」または「待機して再試行」として解釈される多くのコードの 1 つになる可能性があります)、これをアプリケーション レベルのコードと調整します。考えてみると、SQLITE_LOCKED を取得することは、単にロックされているために試行がハングするよりも優れています。他のことを行うことができるからです。

2) ロックを取得します。ただし、複数購入する場合は注意が必要です。アプリケーション レベルのトランザクションごとに、必要なすべてのリソース (ロック) を一貫した (つまり、アルファベット順?) 順序で取得して、データベースでロックが取得されたときにデッドロックが発生しないようにします。データベースがデッドロックを確実かつ迅速に検出し、例外をスローする場合は、これを無視できる場合があります。他のシステムでは、デッドロックを検出せずにハングアップする可能性があります。これにより、ロックを正しく取得するための努力が絶対に必要になります。

ロックに関する事実に加えて、最初から計画された同時マージとロールバックを使用して、データとメモリ内構造を設計するように努める必要があります。データ競合の結果がすべての注文に対して良い結果をもたらすようにデータを設計できる場合、その場合はロックを処理する必要はありません。良い例は、値を読み取って新しい値を送信して更新するのではなく、現在の値を知らずにカウンターをインクリメントすることです。セットに追加する場合も同様です (つまり、行の挿入がどの順序で行われたかは問題にならないような行の追加)。

優れたシステムは、ある有効な状態から次の有効な状態にトランザクション的に移行することを想定しており、例外は (インメモリ コードであっても) 次の状態に移行する試みを中止するものと考えることができます。無視するか再試行するかを選択できます。

于 2013-06-27T17:39:42.873 に答える