行锁
MySQL 中的行锁(row-level locking)并不是单纯指写锁(write lock),而是指锁定机制的粒度。行锁可以是共享锁(也称为读锁,S锁)或排他锁(也称为写锁,X锁),具体取决于事务所使用的隔离级别以及查询类型。
- Select for Update:当执行带有
FOR UPDATE
子句的SELECT
查询时,InnoDB 会对被选中的行加上排他锁。这确保了在事务提交之前,其他事务不能修改这些行。 - Insert Intention Lock:当执行
INSERT
操作时,InnoDB 会自动为要插入的行加上意向锁。这是为了避免插入操作与其他事务的UPDATE
或DELETE
操作冲突。
for update
SELECT ... FOR UPDATE
语句用于获取行级锁,它能够确保在当前事务持有锁的情况下,其他事务无法读取或修改被锁定的行。
for update 如何工作?
当执行SELECT … FOR UPDATE时,数据库会对选中的行加上排他锁。这意味着:
- 锁的作用范围:只有当前事务可以读取或修改这些行,其他事务在试图读取或修改这些行时会被阻塞,直到当前事务结束(提交或回滚)。
- 事务提交前的状态:在当前事务提交之前,其他事务无法看到这些行的变化,因为锁会阻止其他事务对这些行的访问。
- 事务提交后的状态:一旦当前事务提交,锁会被释放,其他事务就可以读取这些行的新状态,并且可能对其进行修改。
json
select * from messages where id = #{id} and status = 'PENDING' for update
测试例子
json
/**
* 只查询 - 行锁
* 第一个事务
*
* @param id
* @return
*/
@SneakyThrows
@Transactional(rollbackFor = Exception.class)
@Override
public boolean lockQuery(Long id) {
System.out.println("lock 1 start");
MessagesDo messagesDo = this.getBaseMapper().lockQueryMessageDo(id);
System.out.println("lock 1 " + JSONUtil.toJsonStr(messagesDo));
Thread.sleep(10000L);
System.out.println("lock 1 end");
return messagesDo != null;
}
/**
* 只查询-行锁
* 第二个事务
*
* @param id
* @return
*/
@SneakyThrows
@Transactional(rollbackFor = Exception.class)
@Override
public boolean lockQuery2(Long id) {
Thread.sleep(1000L);
System.out.println("lock 2 start");
MessagesDo messagesDo = this.getBaseMapper().lockQueryMessageDo(id);
System.out.println("lock 2 " + JSONUtil.toJsonStr(messagesDo));
System.out.println("lock 2 end");
return messagesDo != null;
}
@SneakyThrows
@Test
public void lockQuery() {
ThreadUtil.execAsync(()->messagesService.lockQuery(6L));
Thread.sleep(1000L);
ThreadUtil.execAsync(()->messagesService.lockQuery2(6L));
Thread.sleep(15000L);
}
事务二 会在事务一 commit之前阻塞。
for update 非阻塞
for update skip locked
未获取锁的事务会立即返回。
json
@Select("select * from messages where id = #{id} for update skip locked")