「JDBCによる悲観ロックの落とし穴」の版間の差分
提供: tknotebook
36行: | 36行: | ||
1. Isolation Level を REPEATABLE READ 以上にする。 | 1. Isolation Level を REPEATABLE READ 以上にする。 | ||
− | 2. | + | |
+ | 2. 以下のように、カーソルを使って行を更新する。 | ||
rsA = s.executeQuery("select name, balance from account where name='A' for update"); | rsA = s.executeQuery("select name, balance from account where name='A' for update"); |
2016年7月30日 (土) 03:20時点における版
メインページ>コンピュータの部屋#Java>Java Tips
JDBCを使って悲観ロックでレコードを更新する場合、たまにこんなコードを見かけることがあります。
rsA = s.executeQuery("select name, balance from account where name='A' for update"); if (rsA.next()) { balanceA = rsA.getInt("balance"); logger.debug("balanceA = " + balanceA); } else { throw new Exception("行がありません"); } s.executeUpdate(String.format("update account set balance=%d where name='A'", balanceA - 100)); logger.debug("update A"); conn.commit();
実は Isolation level が READ COMMITED では、これは悲観ロックになっていないのです。
コードでは、
- 更新モードでカーソルを取得
- カーソルの行の値を取得に更新ロックをかける。
- 行の値を更新し排他ロックをかける。
となっていて問題なさそうに見えますが、問題点は更新ロックの寿命です。
多くのDBでは、行ロックの更新ロックは、Isolation Level が READ COMMITED の場合、カーソルの指す行のみがロックされ、 カーソルが別の行へ移ると元の行の更新ロックは解除されます。カーソルがクローズされれば当然更新ロックは残りません。
Statement オブジェクトは executeXXX を実行すると、カーソルをクローズするため、上記のコードではレコードが排他ロックするまでの わずかな隙間時間に、レコードのロックがない期間が存在するのです。これではうまく動きません。
対処方法は2つあります。
1. Isolation Level を REPEATABLE READ 以上にする。
2. 以下のように、カーソルを使って行を更新する。
rsA = s.executeQuery("select name, balance from account where name='A' for update"); if (rsA.next()) { balanceA = rsA.getInt("balance"); logger.debug("balanceA = " + balanceA); rsA.updateInt("balance", balanceA - 100); rsA.updateRow(); logger.debug("update A"); } else { throw new Exception("行がありません"); }