原理及实现逻辑
乐观锁是一种并发控制机制,它假设事务冲突的概率比较小,因此不对共享资源进行加锁,而是通过版本号或时间戳等方式来检测冲突。在乐观锁的实现中,每个事务在执行更新时,会检查数据是否被其他事务修改。如果数据没有被修改,则允许更新;如果数据已经被修改,则通常会回滚事务,让应用层处理更新冲突。
在关系数据库中,乐观锁通常通过以下两种方式实现:
-
版本号(Version Number): 给每一条记录增加一个版本号字段。每次更新时,版本号加一。在更新时检查数据库中的记录版本号与更新前取得的版本号是否一致,如果一致,则允许更新;否则,表示数据已经被其他事务修改,更新失败。
-
时间戳(Timestamp): 给每一条记录增加一个时间戳字段,记录最后的修改时间。在更新时,检查数据库中的记录的时间戳是否和更新前取得的时间戳一致,如果一致,则允许更新;否则,表示数据已经被其他事务修改,更新失败。
这里我们使用版本号实现乐观锁,在主函数中创建一个线程池,分别提交五次更新请求
在请求的过程中,我们先获得了当前这条数据的版本id,然后将版本id的值得传入我们的更新语句
"UPDATE test SET value = ?, version = version + 1,age = age+1 WHERE id = ? AND version = ?
当这条数据被其他线程修改后,我们在应用层会报错,这样,就实现了多线程环境下仅允许一个线程对数据库程序的修改
代码
数据库
create table test
(
id int not null
primary key,
value varchar(255) null,
version int null,
age int null
);
INSERT INTO sqltool.test (id, value, version, age) VALUES (1, 'New value 2', 3, 2);
应用
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DatabaseUpdater {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/sqltool";
private static final String JDBC_USER = "root";
private static final String JDBC_PASSWORD = "shangyi";
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++){
final int recordId = 1;
final String newValue = "New value " + (i+1);
executorService.submit(()->{
updateDatabaseField(recordId,newValue);
});
}
// updateDatabaseField(1, "New Value");
}
private static void updateDatabaseField(int recordId, String newValue) {
Connection connection = null;
try {
// Establish database connection
connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
connection.setAutoCommit(false); // Start a transaction
// Check the current version of the record
int currentVersion = getCurrentVersion(connection, recordId);
// Perform the update using optimistic locking
updateRecord(connection, recordId, newValue, currentVersion);
// Commit the transaction
connection.commit();
} catch (SQLException e) {
// Handle exceptions, possibly rollback the transaction
if (connection != null) {
try {
connection.rollback();
} catch (SQLException rollbackException) {
rollbackException.printStackTrace();
}
}
e.printStackTrace();
} finally {
// Close the connection
if (connection != null) {
try {
connection.close();
} catch (SQLException closeException) {
closeException.printStackTrace();
}
}
}
}
private static int getCurrentVersion(Connection connection, int recordId) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT version FROM test WHERE id = ?")) {
preparedStatement.setInt(1, recordId);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getInt("version");
}
}
}
return -1; // Return a default value or handle appropriately
}
private static void updateRecord(Connection connection, int recordId, String newValue, int currentVersion) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(
"UPDATE test SET value = ?, version = version + 1,age = age+1 WHERE id = ? AND version = ?")) {
preparedStatement.setString(1, newValue);
preparedStatement.setInt(2, recordId);
preparedStatement.setInt(3, currentVersion);
int rowsUpdated = preparedStatement.executeUpdate();
if (rowsUpdated == 0) {
// No rows were updated, indicating a potential conflict
throw new SQLException("Optimistic locking failure");
}
}
}
}
源码链接
Optimistic_lock_Java/ at master · ShangyiAlone/Optimistic_lock_Java (github.com)