RDB(Oracle)の機能の一つに、排他(ロック)がある。
レコードをロックするには、SELECT文でFOR UPDATEを指定する。[2009-01-19]
SELECT * FROM emp WHERE empno=7902 FOR UPDATE;
FOR UPDATEでは、自分がロックしたい対象を 他のトランザクションがロックしている場合に、待つかどうかを指定するオプションがある。
WAITだと、相手がロック解除(コミットまたはロールバック)するまで無期限に待つ。
NOWAITだと、即座に戻る。(ORA-00054「リソース・ビジー、NOWAITが指定されていました。」が発生する)
省略時はWAIT(無期限に待つ)。
また、WAITにはタイムアウト時間を秒単位で指定できる。(Oracle9i以降らしい)
タイムアウトするとORA-30006「リソース・ビジー;
WAITタイムアウトの期限に達しました。」が発生する。
SELECT * FROM emp WHERE empno=7902 FOR UPDATE WAIT; SELECT * FROM emp WHERE empno=7902 FOR UPDATE NOWAIT; SELECT * FROM emp WHERE empno=7902 FOR UPDATE WAIT 10;
※JDBC(JavaからのDB接続)でタイムアウト時間をsetQueryTimeout()で指定していてそれがタイムアウトした場合、ORA-01013「ユーザーによって現行の操作の取消しが要求されました。」が発生する。
同一レコードに対して2つのトランザクションが更新系の処理を行おうとすると、先にSQLを実行した側が
そのレコードに対して排他を獲得する(ロックする)。[2007-09-15]
後からSQLを実行した側は、ロックが解除(先に実行されたトランザクションがコミットまたはロールバック)されるまで停止(待機)する。(NOWAITを指定していれば待たずに終了する)
ロックされている間も、(FOR UPDATE以外の)SELECTは普通に行える。(変更前の状態が読み出せる)
上記で「更新系の処理」と書いたものは、具体的にはUPDATE・DELETE・SELECT FOR UPDATE・INSERT等。
1.先に実行したSQL | 2.後に実行したSQL | 3.排他中に他トランザクションから読み込み | 4A.先トランザクションがコミット | 4B.先トランザクションがロールバック | ||
---|---|---|---|---|---|---|
UPDATE UPDATE |
update テーブル |
update テーブル |
ロックされる | 更新前のデータが読める。 | 後から実行していたSQLも正常に実行され、上書きされて後のデータの状態になる。 | 後から実行したSQLが正常に実行される。 |
DELETE DELETE |
delete from テーブル |
delete from テーブル |
ロックされる | 削除前のデータが読める。 | 後から実行していたSQLも正常に実行される。 (既に消えているので0件削除で正常終了) |
後から実行したSQLが正常に実行される。 |
INSERT INSERT |
insert into テーブル |
insert into テーブル |
ロックされる | 挿入前のデータ(すなわちデータが無い状態)が読める。 | 後から実行していたSQLが一意制約違反(ORA-00001)のエラーになる。 | 後から実行したSQLが正常に実行される。 |
INSERT UPDATE |
insert into テーブル |
update テーブル |
ロックされない | |||
UPDATE INSERT |
update テーブル |
insert into テーブル |
ロックされない |
INSERT-UPDATEの組み合わせでは、お互いにその時点でSELECTできる情報に対してしか更新しない。
INSERTデータはコミット前は自トランザクションからしか見えないので、排他の対象にならない(他トランザクションの更新対象にもならない)。
INSERT-INSERTでは、コミット前は他トランザクションからSELECTできる情報ではないが、排他はされる。
タイミングとして重要な事は、
ちなみに排他とは関係ないが、Oracleで複数のレコードにsysdateを使って日時を入れてまとめてコミットする場合、各レコードの日時はずれることがある。
PostgreSQLで同様のことをした場合は全レコードの日時に同じ値が入る。