Ответ
SELECT FOR UPDATE — это SQL-команда, которая блокирует выбранные строки для эксклюзивного изменения текущей транзакцией, предотвращая их модификацию другими параллельными транзакциями. Это ключевой механизм для избежания состояния гонки (race condition) при конкурентных операциях чтения-изменения-записи.
Как работает:
- Транзакция A выполняет
SELECT ... FOR UPDATE. - Выбранные строки блокируются (обычно на уровне строк).
- Если транзакция B попытается выполнить
SELECT ... FOR UPDATEилиUPDATE/DELETEдля тех же строк, она будет ожидать завершения транзакции A. - Блокировка снимается при
COMMITилиROLLBACKтранзакции A.
Пример использования (Java/JDBC):
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false); // Начало транзакции
// 1. Блокируем строку для обновления
String selectSQL = "SELECT balance FROM accounts WHERE id = ? FOR UPDATE";
PreparedStatement selectStmt = conn.prepareStatement(selectSQL);
selectStmt.setInt(1, accountId);
ResultSet rs = selectStmt.executeQuery();
// 2. Читаем и изменяем данные
int newBalance = 0;
if (rs.next()) {
newBalance = rs.getInt("balance") + 100;
}
// 3. Обновляем заблокированную строку
String updateSQL = "UPDATE accounts SET balance = ? WHERE id = ?";
PreparedStatement updateStmt = conn.prepareStatement(updateSQL);
updateStmt.setInt(1, newBalance);
updateStmt.setInt(2, accountId);
updateStmt.executeUpdate();
conn.commit(); // 4. Фиксируем изменения и снимаем блокировку
} catch (SQLException e) {
conn.rollback(); // Откат также снимает блокировку
throw e;
}
Важные замечания:
- Требует транзакции: Команда имеет смысл только внутри явной транзакции (при отключенном
autocommit). - Риск взаимоблокировок (deadlock): Возникает, если две транзакции блокируют ресурсы в разном порядке. СУБД обычно обнаруживает и откатывает одну из них.
- Производительность: Избыточное использование может снизить параллелизм. Используйте только там, где это действительно необходимо для целостности данных.