⍟ select for update
在查询语句后面增加for update
,数据库会在查询过程中给数据库表增加排他锁
// 加上事务就是为了 for update 的锁可以一直生效到事务执行结束
// 默认回滚的是 RunTimeException
@Transactional(rollbackFor = Exception.class)
public String singleLock() throws Exception {
log.info("我进入了方法!");
DistributeLock distributeLock = distributeLockMapper.
selectDistributeLock("demo");
if (distributeLock==null) {
throw new Exception("分布式锁找不到");
}
log.info("我进入了锁!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "我已经执行完成!";
}
这段代码实现了一个基于数据库的分布式锁,使用了 SELECT ... FOR UPDATE 语句获取行级锁,并通过加上事务来保证锁的有效性。
具体来说,为了保证 SELECT ... FOR UPDATE 所获取的行级锁可以生效到事务结束,使用了 @Transactional
注解将整个方法置于事务中。此外,在出现异常时,如果异常类型不是 RuntimeException 或其子类,则会回滚事务。
在方法内部,使用 distributeLockMapper.selectDistributeLock("demo")
方法执行 SELECT ... FOR UPDATE 语句获取行级锁。如果获取失败,则会一直等待,直到获取到行级锁为止。如果获取成功,则会继续执行业务逻辑,并在最后返回执行结果。
需要注意的是,由于在高并发场景下,同时获取锁的请求可能会导致数据库连接数过多,因此需要控制 SELECT ... FOR UPDATE 的使用次数,避免过度占用数据库资源。另外,在出现锁等待的情况时,需要合理设置等待时间,避免长时间挂起线程影响系统性能。
总之,基于数据库的分布式锁是一种比较常见的锁实现方式,通过使用 SELECT ... FOR UPDATE 等 SQL 语句来实现行级锁,保证了数据的一致性和可靠性。但是,在使用时需要注意控制数据库连接数和优化 SQL 查询语句,以提高系统的并发性能和可用性。
2.直接维护一张锁表
public class MySQLDistributedLock {
private JdbcTemplate jdbcTemplate;
/**
* 尝试获取分布式锁
*
* @param lockName 锁名称
* @param timeout 超时时间,单位毫秒
* @return 是否获取成功
*/
public boolean tryLock(String lockName, long timeout) {
long startTime = System.currentTimeMillis();
while (true) {
String now = Long.toString(System.currentTimeMillis());
int numRows = jdbcTemplate.update("INSERT INTO distributed_lock(lock_name, create_time) " +
"VALUES (?, ?) " +
"ON DUPLICATE KEY UPDATE create_time=create_time",
lockName, now);
if (numRows > 0) {
// 获取到了锁
return true;
}
if ((System.currentTimeMillis() - startTime) >= timeout) {
// 超时未获取到锁
return false;
}
try {
Thread.sleep(10); // 等待一段时间后再进行尝试
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
/**
* 释放分布式锁
*
* @param lockName 锁名称
*/
public void unlock(String lockName) {
jdbcTemplate.update("DELETE FROM distributed_lock WHERE lock_name=?", lockName);
}
// ...
}
在这个示例代码中,我们使用了Spring JDBC来操作MySQL数据库。其中,tryLock()方法用于尝试获取分布式锁,如果获取到了锁则返回true,如果超时未获取到锁则返回false;unlock()方法用于释放分布式锁。在tryLock()方法中,我们使用了MySQL的INSERT INTO ... ON DUPLICATE KEY UPDATE语法,它会在指定的表中插入一条记录,如果该记录已经存在,则将其更新。如果INSERT操作成功,则表示获取到了锁;否则,继续等待一段时间后再进行尝试。需要注意的是,在SQL语句中必须为表添加UNIQUE约束,否则无法实现锁的唯一性。
综上所述,通过维护一张锁表来实现分布式锁的代码比较简单,但是由于需要频繁访问数据库,性能较差,且容易出现死锁和其他并发问题。因此,在实际应用中,最好使用专门的分布式锁方案,例如Redis、Zookeeper等。