2014/10/31

Android: SQLite3 LockとTransaction Immediate/Exclusive

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が発生するため, 次のようなシーケンスでは一貫性が損なわれる.

Created with Raphaël 2.1.0Transaction ATransaction AShared ResourceShared ResourceTransaction BTransaction BBEGIN TRANSACTIONBEGIN TRANSACTIONREAD ALL (x)DELETE (x)INSERT (y)COMMIT TRANSACTIONREAD ALL (y)Expected xBut was y-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

以上.