!この記事は古くなっています. 更新版は下記をご覧ください.!
Android: SQLite3 LockとTransaction Immediate/Exclusive
http://yuki312.blogspot.jp/2014/10/android-sqlite3-locktransaction.html
SQLiteのロック機構と、Transactionで使用できるロック2種の選択基準についての考察。
●SQLiteのロック
SQLiteのロック単位は"データベース単位"。このため、ロックを1つ取得すると同データベース上にある全てのテーブルに影響がある。
Oracle等の巨大なDBMSでは"行ロック"なんてものがあったりして細かくロックを制御できるが、軽量なSQLiteは"データベース単位"でのロックのみサポートしている。
そのため、長い時間ロックし続けると他テーブルになかなかアクセスできない状態になる。
全く問題にならないケースもあるが、"データベースを分割するかしないか"の判断基準の1つにはなりそう。
●Transaction Immediate
Transactionで指定できるロック種別の1つがImmediate。Immediateロックモードのポイントは下記
・ロック中、他ユーザはデータの読み取りはできても書き込みはできない
長所は、ロック中でも他ユーザがデータを読めるため"ロック解除されるまで待たなくて良い"こと。
逆に"読めてしまうことによる弊害"がある場合はExclusiveロックの使用を検討する。
Immediateロックではマズいケースは下記のような場合かな?あまり良い例が思いつかない。
- アプリの設定値を保持したテーブルがある
- アプリの設定値は他モジュールから参照される
- アプリの設定値は可能な限り最新値を返す
- アプリの設定値の更新には多少時間が掛かる
ただし、設定値の更新は多少時間がかかる(4)。
Immediateロックでは、更新中(ロック中)にデータベースを参照されると古い値が返されるのでNG(3)。
そのため、参照もできなくするExclusiveロックを掛ける。
●Transaction Exclusive
Transactionで指定できるもう1つのロック種別がExclusive。Exclusiveロックモードのポイントは下記
・ロック中、他ユーザはデータの読み書きができない
長所は、"ロック中に読み書きできるのは自ユーザのみ"なこと。
逆に"他ユーザの読み取りを待たせてしまうことによる弊害"がある場合はImmediateロックの使用を検討する。
Exclusiveロックだとマズいケースは下記のような場合かな?
- とあるデータAは多数のユーザから頻繁に読み取りアクセスされる
- とあるデータAを読み取るアプリは高い応答性が求められる
- データ更新中に並行してデータ参照されても平気
そのため、Exclusiveロックが長くなればなるほど、データを参照したいアプリの応答性を損なう原因となり兼ねない(2)。
(3)があてはまるならImmediateロックで問題ないかな?
# 上記2つのケースがImmediate/Exclusiveロックの考え方として正しいのか自信なし。
# もっと良いケースがあれば後日修正します。
●ソースコード
AndroidでのTransactionはSQLiteDataBaseクラスを使用します。基本的な使い方は割愛。下記サイトで詳しく取り上げられています。
AABlog:Androidアプリ開発 SQLiteデータベースを使用する(トランザクション)
AndroidでTransactionのロック種別を指定するにはSQLiteDataBaseクラスを使用します。
使用するのは下記のAPI
- SQLiteDataBase.beginTransaction()
- SQLiteDataBase.beginTransactionWithListener(SQLiteTransactionListener)
- SQLiteDataBase.beginTransactionNonExclusive()
- SQLiteDataBase.beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)
beginTransaction/beginTransactionWithListenerで開始されたトランザクションは「Exclusiveモード」で、
beginTransactionNonExclusive/beginTransactionWithListenerNonExclusiveで開始されたトランザクションは「Immediateモード」でデータベースをロックします。
// 「Exclusiveモード」でロック db.beginTransaction(); try { // do something. db.setTransactionSuccessful(); } finally { db.endTransaction(); } // 「Immediateモード」でロック db.beginTransactionNonExclusive(); try { // do something. db.setTransactionSuccessful(); } finally { db.endTransaction(); }簡単ですね。
●SQLiteDatabaseLockedException
ロックされているデータベースに対してSQLiteDatabase経由でクエリを実行した場合、、、・SQLiteDatabase.query
ロックが取得できるまで待ちます。
API Lv16(JellyBean)以降はCancellationSignalを使ってクエリをキャンセルすることが出来ます。
⇒ContentResolver.query()
⇒CancellationSignal
・SQLiteDatabase.insert
SQLiteDatabaseがSQLiteDatabaseLockedExceptionをキャッチし、呼出し元に-1を返します。
public long insert(String table, String nullColumnHack, ContentValues values) { try { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + values, e); return -1; } }・SQLiteDatabase.insertOrThrow
SQLiteDatabaseLockedExceptionが投げられます。
public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); }
・SQLiteDatabase.update
SQLiteDatabaseLockedExceptionが投げられます。
・SQLiteDatabase.delete
SQLiteDatabaseLockedExceptionが投げられます。
また、トランザクション開始のbeginTransactionでも同様。
・SQLiteDatabase.beginTransaction
SQLiteDatabaseLockedExceptionが投げられます。
以上です。