java中用preparedstatement执行select for update实现悲观锁,需关闭自动提交、在显式事务中执行查询并紧跟update操作,且依赖数据库行级锁支持;不同数据库语法有差异,如mysql/postgresql用for update,sql server需updlock提示。

Java 中用 PreparedStatement 执行 SELECT FOR UPDATE 实现悲观锁,核心是:在事务中执行带 FOR UPDATE 的查询语句,并确保连接不自动提交、事务正确提交或回滚。
确保数据库连接支持事务且关闭自动提交
悲观锁依赖数据库行级锁,必须在显式事务中生效。JDBC 默认开启自动提交(auto-commit=true),此时 SELECT FOR UPDATE 会立即提交并释放锁,失去锁效果。
- 调用
connection.setAutoCommit(false)关闭自动提交 - 后续所有操作(查询、更新等)都在同一事务上下文中
- 成功后调用
connection.commit();失败时调用connection.rollback()
使用 PreparedStatement 执行 SELECT FOR UPDATE 查询
PreparedStatement 能预编译 SQL、防止 SQL 注入,也支持 FOR UPDATE。注意不同数据库语法略有差异:
- MySQL:直接写
SELECT ... FROM table WHERE ... FOR UPDATE - PostgreSQL:同 MySQL,支持
FOR UPDATE,还可加SKIP LOCKED避免阻塞 - Oracle:用
SELECT ... FROM table WHERE ... FOR UPDATE(无需额外关键字) - SQL Server:不支持标准
FOR UPDATE,需用UPDLOCK提示,如SELECT ... FROM table WITH (UPDLOCK) WHERE ...
示例(MySQL/PostgreSQL):
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
String sql = "SELECT id, balance FROM account WHERE id = ? FOR UPDATE";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, accountId);
ResultSet rs = ps.executeQuery(); // 此时已加行锁
if (rs.next()) {
long currentBalance = rs.getLong("balance");
// 后续可安全更新该行,如扣款
}
}
配合 UPDATE 操作完成业务逻辑
SELECT FOR UPDATE 本身只加锁不修改数据,必须在同一个事务中紧接着执行 UPDATE(或其他 DML),否则锁会在事务结束时释放,期间可能被其他事务干扰。
- 不要在
SELECT FOR UPDATE后关闭PreparedStatement或ResultSet再执行更新——只要连接未提交,锁仍持有 - 推荐在一个 try-with-resources 块中完成查+改,或明确控制资源生命周期
- 更新语句也建议用
PreparedStatement,例如:UPDATE account SET balance = ? WHERE id = ?
注意锁范围与事务隔离级别
FOR UPDATE 锁定的是满足 WHERE 条件的行(InnoDB 下还会加间隙锁防止幻读),但实际行为受事务隔离级别影响:
- 在
REPEATABLE READ(MySQL 默认)下,会加记录锁 + 间隙锁,避免幻读 - 在
READ COMMITTED下,只锁命中行,不锁间隙,可能产生幻读 - 若 WHERE 条件无索引,可能升级为表锁,务必确保查询字段有合适索引
可通过 EXPLAIN 或数据库锁视图(如 MySQL 的 information_schema.INNODB_TRX)验证是否命中索引、锁类型是否符合预期。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










