トランザクション分離レベルとは
データベース管理システム上でトランザクションが複数同時に行われた場合に、どれほどの一貫性、正確性で実行するかを4段階で定義したもの。
分離レベル
SERIALIZABLE(直列化可能)
最も独立性が高い分離レベル。
トランザクションを順番に実行(直列)するのと同じように他のトランザクションのデータ更新の影響を全く受けない。
アクセスが競合すると先のトランザクションの終了を待たなければならないため性能は低い。
REPEATABLE READ(読み取り対象のデータを常に読み取る)
SERIALIZABLEに次いで独立性が高い分離レベル。
他のトランザクションによるデータ更新の影響を受けない。
トランザクション実行中は再び対象データを読み取っても同じ値が返ってくる。
しかし、他のトランザクションによるレコードの追加・削除の影響は受けるため、「ファントムリード」と呼ばれる現象が生じることがある
READ COMMITTED(確定した最新データを常に読み取る)
3番目に独立性が高い分離レベル。
他のトランザクションがコミットした変更の影響を受ける。
REPEATABLE READ同様、ファントムリードが生じることがある。
何度も同じデータを読み込むと他のトランザクションによる更新で値が変わってしまう「ノンリピータブルリード」が生じることがある。
READ UNCOMMITTED(確定していないデータまで読み取る)
最も独立性が低い分離レベル。
他のトランザクションが引き起こすあらゆる更新・変更の影響を受ける。
READ COMMITTED同様、ファントムリード、ノンリピータブルリードが生じることがある。
処理途中や不完全な状態のデータを読む込む「ダーティリード」が生じることがある。
処理を妨げるロックは最小限で済むので4つの分離レベルの中で最も高速に動作する。
今回やること
Spring + Doma2を使っているアプリケーションでトランザクション分離レベルを「READ UNCOMMITTED」にする
確認方法
SELECT @@transaction_isolation;
やりかた①
1つのsqlファイルでSELECTの直前にSETコマンド実行
SET SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
@@transaction_isolation as iso
FROM
user
WHERE
id = /*userId*/-1
;
結果
エラーとなり実行できない
{
"error": {
"message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
"type": "internal_server_error"
}
}
やりかた②
DomaのSqlアノテーションでSETコマンドを実行してからSELECTする
@Sql("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")
@Update
int setIso();
SELECT
@@transaction_isolation as iso
FROM
user
WHERE
id = /*userId*/-1
;
結果
①と同じくエラーとなり実行できない
{
"error": {
"message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
"type": "internal_server_error"
}
}
やりかた③
別sqlファイルでSELECTの前にSETコマンド実行
public Object getUser(@NonNull Integer userId) {
int a = dao.setIso();
return dao.selectUser(userId);
}
@Update(sqlFile = true)
int setIso();
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
user.*,
@@transaction_isolation as iso
FROM
user
WHERE
id = /*userId*/-1
;
結果
①②と同じくエラーとなり実行できない
{
"error": {
"message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
"type": "internal_server_error"
}
}
やりかた④
application.ymlで設定してみる
spring:
datasource:
hikari:
transaction-isolation: TRANSACTION_READ_UNCOMMITTED
結果
やりかた⑤
Doma2のTransactionManagerを使ってみる
- Doma2のTransactionManagerのsampleを参考にDbConfigクラスなどを用意して
TransactionManager tm = new DbConfig(URL, USERNAME, PASSWORD).getTransactionManager();
tm.requiresNew(TransactionIsolationLevel.READ_UNCOMMITTED, () -> {
user.set(dao.selecrUser(userId));
});
結果
やりかた⑥
アノテーションで設定
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
結果
注意
書き込み権限があるユーザで実施しないとSETコマンドが実行できません
※自分はこれでつまづいていました。。。
まとめ
SpringのTransactionalアノテーションを使うのが一番楽そう。
SpringのTransactionTempleteも使えるかもしれないです(今回は試していません)