SELECT column_list
FROM table_name
WHERE condition
FOR UPDATE [OF column_list] [NOWAIT | WAIT n | SKIP LOCKED];
COMMIT 或 ROLLBACK 釋放| 選項 | 行為 | 適用場景 |
|---|---|---|
| (預設) | 無限期等待直到取得鎖 | 一般 OLTP,確保最終成功 |
| NOWAIT | 立即返回錯誤 ORA-00054 | 高併發、不想等待、立即重試邏輯 |
| WAIT n | 最多等待 n 秒,超時返回 ORA-30006 | 可容忍短暫等待、避免無限阻塞 |
| SKIP LOCKED (11g+) | 跳過已鎖定列,只處理未鎖定列 | 佇列處理、批次作業、避免死結 |
Connection conn = dataSource.getConnection();
try {
// 1. 關閉自動提交,開啟交易
conn.setAutoCommit(false);
// 2. 查詢並鎖定目標列
String sql = "SELECT id, balance FROM accounts WHERE id = ? FOR UPDATE NOWAIT";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
long id = rs.getLong("id");
BigDecimal balance = rs.getBigDecimal("balance");
// 3. 執行業務邏輯 (扣款、轉帳等)
BigDecimal newBalance = balance.subtract(amount);
// 4. 更新資料
String updateSql = "UPDATE accounts SET balance = ? WHERE id = ?";
try (PreparedStatement ups = conn.prepareStatement(updateSql)) {
ups.setBigDecimal(1, newBalance);
ups.setLong(2, id);
ups.executeUpdate();
}
// 5. 提交交易,釋放鎖
conn.commit();
} else {
throw new RuntimeException("帳戶不存在");
}
}
}
} catch (SQLException e) {
// 處理鎖定超時 ORA-00054 / ORA-30006
if (e.getErrorCode() == 54 || e.getErrorCode() == 3006) {
// 重試邏輯或拋出業務異常
}
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true); // 恢復預設
conn.close();
}
-- 錯誤模式:中間夾雜複雜運算、外部 API 呼叫、使用者互動
SELECT ... FOR UPDATE;
-- 做很多事...
UPDATE ...;
COMMIT;
後果:長時間持有鎖 → 併發效能嚴重下降、死結風險增加。
-- 正確模式:單一交易內完成
BEGIN TRANSACTION;
SELECT ... FOR UPDATE;
-- 僅做必要運算
UPDATE ...;
COMMIT;
-- 表加版本號欄位
ALTER TABLE accounts ADD (version NUMBER DEFAULT 0);
-- 更新時檢查版本
UPDATE accounts
SET balance = :new_bal, version = version + 1
WHERE id = :id AND version = :old_version;
-- 若 0 筆更新 → 衝突,重新讀取重試
優點:無資料庫鎖、高併發效能佳。缺點:需應用層重試邏輯、不適用強一致性需求場景。
-- 查看被鎖定物件
SELECT o.object_name, l.session_id, l.locked_mode, l.oracle_username
FROM v$locked_object l, dba_objects o
WHERE l.object_id = o.object_id;
-- 查看等待鎖的 Session
SELECT s.sid, s.serial#, s.username, s.sql_id, w.event, w.seconds_in_wait
FROM v$session s, v$session_wait w
WHERE s.sid = w.sid AND w.event LIKE '%enq%';