楽観的ロックの場合、あるトランザクション内で更新候補として SELECT で選択した行を、他のトランザクションが更新することをブロックすることはありません。

(逆に、それをブロックするのを「悲観的ロック」というわけです。「楽観的ロック」と「悲観的ロック」の違いについては「楽観的ロックと悲観的ロック」を見てみてください)

つまり、更新候補として選択した行は、ひょっとしたら他のトランザクションですでに更新されてしまっている可能性があるわけです。

ここでそのまま更新を行ってしまえば、他のトランザクションをそれと知らずに上書き更新してしまっていることになります。

たとえば、先に始まっていたトランザクション(TX A)の中で口座に 100万入っているという情報を SELECT で得たとしましょう。

後に始まったトランザクション(TX B)がその口座から 50万引き出した、つまり口座の残高を 50万に UPDATE してトランザクション(TX B)を終了したとします。

ここで先のトランザクション(TX A)が 30万引き出して、口座の残高を 70万として更新できてしまうと、結果はめちゃくちゃになってしまいますね。

TX A:
SELECT balance
  FROM Account
 WHERE customer = 123456
(balance は 100)
TX B:
SELECT balance
  FROM Account
 WHERE customer = 123456
(balance は 100)
TX B:
UPDATE Account 
   SET balance = 50 
 WHERE customer = 123456
(balance は 50)
TX A:
UPDATE Account 
   SET balance = 70 
 WHERE customer = 123456
(balance は 50 なのに 70)

これを「ロスト・アップデート」といいます。

TX B の更新がまったく失われてしまうからですね。

「楽観的ロック」でこうした「楽観的」なコーディングをしていると、データがめちゃくちゃになってしまいかねないわけです。

この「ロスト・アップデート」を避けるために、SELECT で選択した行が、UPDATE を行う時に SELECT で選択した時から更新されていないかどうかを確認する、ということを行います。

たとえば、行が更新されたときのタイムスタンプ値をテーブルに持ち、SELECT で取得し、それをトランザクションの中の一時変数で格納しておいて更新時に比較する、といったやり方ですね。

こんなかんじ ↓ になります。

TX A:
SELECT balance,
       row_timestamp 
  FROM Account
 WHERE customer = 123456
(balance は 100、row_timestamp は以前この行が更新された時の値)
TX B:
SELECT balance,
       row_timestamp
  FROM Account
 WHERE customer = 123456
(balance は 100、row_timestamp は以前この行が更新された時の値)
TX B:
UPDATE Account 
   SET balance = 50, 
       row_timestamp = CURRENT_TIMESTAMP
 WHERE customer = 123456
   AND row_timestamp = :tx_b_variable
(:tx_b_variable の値は SELECT 時の row_timestamp の値なので WHERE 条件は OK。
この更新が成功した時点で row_timestamp の値はこの UPDATE 実行時のタイムスタンプになる)
TX A:
UPDATE Account 
   SET balance = 70, 
       row_timestamp = CURRENT_TIMESTAMP
 WHERE customer = 123456
   AND row_timestamp = :tx_b_variable
(:tx_a_variable の値は SELECT 時の row_timestamp の値なので WHERE 条件で不一致になる)

TX B の更新は成功しますが、TX A の更新は行われません。

UPDATE の時に行が見つからないことで、SELECT した時の行からもうすでに誰かに UPDATE されてしまっていることがわかるわけです。