Intro
AndroidのSQLite3のLockとTransaction Immediate/Exclusiveについてまとめた.
基礎となる知識としては こちら を参照.
Locking And Concurrency In SQLite3
Oracle等のDBMSでは”行ロック”など細かくロック粒度を制御できるが, SQLiteではこれができない.
SQLiteのロック粒度は”データベース単位”のみである. このためRead/Writeロック取得~解放までの間, 他のトランザクションはデータベースにアクセスできない. (このことは”データベースを分割する/しない”の判断基準の1つにはなりそう)
AndroidではSQLiteのトランザクションモードとしてImmediateとExclusiveをAPIで指定できる. 特に指定しない場合はExclusiveモードとなる.
Transaction Immediate
ロック取得中は他トランザクションの書き込みを制限し, 読み込みを許可するmode.
Exclusive modeより緩い分離レベルであるためトランザクションの並列化を促進でき, パフォーマンス面で期待できる. ただし, Phantom Readが発生する可能性を考慮する必要がある.
IMMEDIATE.
トランザクション開始時にRESERVEDロックを取得する. BEGIN IMMEDIATEによりRESERVEDロックが取得されると, 他のデータベースコネクションはデータベースに書き込んだり, BEGIN IMMEDIATE or BEGIN EXCLUSIVEを実行することができなくなるが, データベースからの読み込みを継続することはできる.
ANSI/ISO SQL標準ではREPEATABLE_READ
に相当する.
アプリケーションがデータベースへアクセスするのに書き込み専用トランザクション と 読み込み専用トランザクションのようにRead/Writeを分離できるのであればImmediate modeは有効に作用する.
Immediate modeではPhantom Readが発生するため, 次のようなシーケンスでは一貫性が損なわれる.
Transaction Exclusive
ロック取得中は他トランザクションの読み込み/書き込みを制限するmode.
Immediateより厳しい分離レベルであるためトランザクションの直列化が促進され, データの一貫性が向上する. ただしパフォーマンスはImmediateに劣る可能性が高い.
EXCLUSIVE
トランザクション開始時にEXCLUSIVEロックを取得する. BEGIN EXCLUSIVEによりEXCLUSIVEロックが取得されると, READ_UNCOMMITTEDの接続を除いた全てのデータベースコネクションからデータを読み込むことができなくなる. データベースへの書き込みについては例外なくトランザクションの完了まで許可されない.
ANSI/ISO SQL標準ではSERIALIZABLE
に相当する.
データベースに対して完全なデータの一貫性が求められる場合はExclusive modeを指定する.
Implements
クライアントからTransaction modeを指定するにはSQLiteDataBase
クラスを使用する.
SQLiteDataBase.beginTransaction()
SQLiteDataBase.beginTransactionWithListener(SQLiteTransactionListener)
SQLiteDataBase.beginTransactionNonExclusive()
SQLiteDataBase.beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)
beginTransactionNonExclusive/beginTransactionWithListenerNonExclusiveはAPI Level11で追加されたAPI.
beginTransaction/beginTransactionWithListenerで開始されたトランザクションはExclusive modeで,
beginTransactionNonExclusive/beginTransactionWithListenerNonExclusiveで開始されたトランザクションはImmediate modeで動作する.
// Transaction Exclusive mode
db.beginTransaction();
try {
// do something.
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
// Transaction Immediate mode
db.beginTransactionNonExclusive();
try {
// do something.
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
SQLiteDatabaseLockedException
ロックされているデータベースに対し, SQLiteDatabase経由でクエリを実行した場合の挙動は下記.
SQLiteDatabase.query
ロックが取得できるまでトランザクションは待機される. API Level16以降は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がスローされる.
SQLiteDatabase.beginTransaction
SQLiteDatabaseLockedExceptionがスローされる.
Reference
File Locking And Concurrency In SQLite Version 3
以上.