java jdbc 实现悲观锁的核心做法是在事务开启后,执行带有 for update 子句的 sql 查询语句。这会锁定数据库中被选中的行,直到当前事务提交或回滚,其他事务在尝试修改这些行时会阻塞等待。具体操作时,需确保数据库连接关闭了自动提交(setautocommit(false)),并在代码块结束时显式调用 commit() 或 rollback()。

如何配置连接以支持手动事务控制
要实现悲观锁,第一步是获取一个不受自动提交机制干扰的数据库连接。默认情况下,JDBC 连接通常是自动提交的,这意味着每条 SQL 语句都会立即生效并释放锁,这无法实现跨多步操作的行级锁定。因此,必须通过 Connection 对象将自动提交模式设置为 false。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class PessimisticLockExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test_db";
String user = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// 1. 关闭自动提交,开始事务
conn.setAutoCommit(false);
long targetId = 101;
int newBalance = 500;
// 2. 执行带 FOR UPDATE 的查询以获取悲观锁
String selectSql = "SELECT balance FROM accounts WHERE id = ? FOR UPDATE";
try (PreparedStatement pstmt = conn.prepareStatement(selectSql)) {
pstmt.setLong(1, targetId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
int currentBalance = rs.getInt("balance");
System.out.println("Current Balance: " + currentBalance);
// 3. 业务逻辑处理...
// 假设这里有一些复杂的计算或校验
if (currentBalance >= newBalance) {
// 4. 更新数据
String updateSql = "UPDATE accounts SET balance = ? WHERE id = ?";
try (PreparedStatement updatePstmt = conn.prepareStatement(updateSql)) {
updatePstmt.setInt(1, newBalance - 100); // 假设扣除100
updatePstmt.setLong(2, targetId);
updatePstmt.executeUpdate();
}
// 5. 提交事务,释放锁
conn.commit();
System.out.println("Transaction committed successfully.");
} else {
// 余额不足,回滚事务
conn.rollback();
System.out.println("Insufficient balance, transaction rolled back.");
}
}
} catch (Exception e) {
// 发生异常时回滚
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述代码展示了完整的流程:首先建立连接并禁用自动提交,接着使用 PreparedStatement 执行 SELECT ... FOR UPDATE。如果查询成功返回结果,说明该行已被当前事务独占锁定。随后进行业务逻辑判断,若满足条件则执行更新并提交事务;若不满足或发生异常,则回滚事务。这种模式确保了在同一时刻,只有一个事务能修改被锁定的行,其他尝试访问同一行的事务将被阻塞,直到锁被释放。
如何处理并发冲突与超时情况
当多个事务同时尝试对同一行数据加悲观锁时,数据库会协调这些请求。大多数关系型数据库(如 MySQL InnoDB)默认会让后续的事务阻塞等待,直到前一个事务释放锁。然而,在高并发场景下,长时间等待可能导致性能问题甚至死锁。因此,合理设置事务隔离级别和处理超时机制至关重要。

可以通过调整数据库会话的 innodb_lock_wait_timeout 参数来限制等待时间,或者在应用层捕获 SQLException 并识别特定的错误码(如 MySQL 的 1205 错误表示锁等待超时)。一旦检测到超时,应用可以决定重试、取消操作或记录日志。此外,保持事务尽可能短小,避免在持有锁的情况下执行耗时操作(如网络请求或复杂计算),也是减少冲突的有效手段。在实际开发中,建议结合业务需求选择合适的隔离级别,通常 READ COMMITTED 或 REPEATABLE READ 足以支持悲观锁的使用,而无需升级到更严格的 SERIALIZABLE 级别,后者虽然能彻底避免并发问题,但会显著降低系统吞吐量。
FOR UPDATE 在不同数据库方言中的差异
虽然 FOR UPDATE 是 SQL 标准的一部分,但在不同数据库系统中,其具体行为和可选参数可能有所不同。例如,在 PostgreSQL 中,可以使用 FOR UPDATE NOWAIT 来立即返回错误而不是等待锁,或者使用 FOR UPDATE SKIP LOCKED 跳过已被锁定的行。这些特性在处理队列任务或批量处理时非常有用,可以避免不必要的等待。
在 Oracle 数据库中,除了标准的 FOR UPDATE,还支持 FOR UPDATE OF column_name 语法,允许指定只锁定特定列所属的行,尽管在行级锁实现的数据库中,这通常没有实际区别,因为锁粒度已经是行级别的。此外,Oracle 也支持 NOWAIT 和 SKIP LOCKED 选项,用法与 PostgreSQL 类似。开发者在使用时应查阅对应数据库的文档,以确保正确使用这些高级特性,并根据具体的业务场景选择最合适的锁策略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











