41. 如何在MySQL中使用FOR UPDATE来实现行级锁定?在Java中如何使用?
大约 3 分钟
在MySQL中,FOR UPDATE
是用于实现行级锁定的一种方式。当使用SELECT ... FOR UPDATE
语句时,数据库会对查询到的行加上排他锁(也叫写锁),这意味着其他事务不能修改这些行,直到当前事务提交或回滚。FOR UPDATE
通常用于在事务中确保数据的一致性,防止脏写和并发更新问题。
1. 在MySQL中使用FOR UPDATE
实现行级锁定
以下是一个在MySQL中使用FOR UPDATE
实现行级锁定的基本示例:
-- 启动事务
START TRANSACTION;
-- 查询并锁定id为1的记录
SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- 在此期间,其他事务尝试更新或删除该行都会被阻塞
-- 更新操作
UPDATE products SET quantity = quantity - 1 WHERE id = 1;
-- 提交事务,释放锁
COMMIT;
在这个示例中:
SELECT ... FOR UPDATE
查询会锁定id = 1
的那一行记录。- 其他事务在这条事务提交之前,如果尝试对相同的行执行更新或删除操作,会被阻塞,直到该事务结束(提交或回滚)。
- 锁定的范围只针对查询返回的行。如果没有行返回,则没有行被锁定。
2. 在Java中使用FOR UPDATE
实现行级锁定
在Java中,你可以通过JDBC来执行FOR UPDATE
语句,从而在代码中实现行级锁定。下面是一个使用JDBC实现的完整示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class RowLevelLockingExample {
public static void main(String[] args) {
Connection connection = null;
try {
// 1. 获取数据库连接
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/your_database",
"username",
"password");
// 2. 设置事务为手动提交
connection.setAutoCommit(false);
// 3. 执行带FOR UPDATE的查询,锁定所选行
PreparedStatement selectStmt = connection.prepareStatement("SELECT * FROM products WHERE id = ? FOR UPDATE");
selectStmt.setInt(1, 1);
ResultSet rs = selectStmt.executeQuery();
if (rs.next()) {
int quantity = rs.getInt("quantity");
System.out.println("Current Quantity: " + quantity);
// 4. 更新操作
PreparedStatement updateStmt = connection.prepareStatement("UPDATE products SET quantity = ? WHERE id = ?");
updateStmt.setInt(1, quantity - 1);
updateStmt.setInt(2, 1);
updateStmt.executeUpdate();
System.out.println("Quantity updated successfully.");
// 5. 提交事务
connection.commit();
}
} catch (SQLException e) {
e.printStackTrace();
try {
if (connection != null) {
// 出错时回滚事务
connection.rollback();
}
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException closeEx) {
closeEx.printStackTrace();
}
}
}
}
解释
- 获取连接:首先,我们通过
DriverManager
获取数据库连接。 - 设置手动提交:调用
connection.setAutoCommit(false)
将事务管理设为手动提交。 - 执行查询并加锁:
PreparedStatement selectStmt = connection.prepareStatement("SELECT * FROM products WHERE id = ? FOR UPDATE");
这行代码执行查询并锁定结果行。 - 更新数据:在查询结果后,我们执行更新操作,确保在事务期间其他事务不能更改数据。
- 提交事务:更新完成后,调用
connection.commit()
提交事务,并释放锁定。
关键点
- 锁定范围:
FOR UPDATE
只会锁定查询返回的行。如果查询没有返回任何行,则不会加锁。 - 阻塞行为:在
FOR UPDATE
锁定的行被占用时,其他事务的更新或删除请求会被阻塞,直到锁被释放(即当前事务提交或回滚)。 - 事务提交:确保在完成必要的操作后,提交事务以释放锁,否则可能会导致死锁或其他事务的阻塞。
使用FOR UPDATE
实现行级锁定可以有效地防止并发操作导致的数据不一致,但需要注意性能问题,尤其是在高并发环境下。