Skip to content

行锁

MySQL 中的行锁(row-level locking)并不是单纯指写锁(write lock),而是指锁定机制的粒度。行锁可以是共享锁(也称为读锁,S锁)或排他锁(也称为写锁,X锁),具体取决于事务所使用的隔离级别以及查询类型。

  • Select for Update:当执行带有 FOR UPDATE 子句的 SELECT 查询时,InnoDB 会对被选中的行加上排他锁。这确保了在事务提交之前,其他事务不能修改这些行
  • Insert Intention Lock:当执行 INSERT 操作时,InnoDB 会自动为要插入的行加上意向锁。这是为了避免插入操作与其他事务的 UPDATEDELETE 操作冲突。

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")