关于用select-for-update语句产生锁住记录不让其他事务(线程)修改的问题

背景

我们知道update库表里的一条数据会产生行锁(以mysql innodb为例),提交事务后才会释放行锁,在行锁阶段别的事务无法去修改同一条记录。

在 Navicat 和命令行中,执行完这条update的 SQL 就立即提交事务。但是在Java程序里,用 @Transactional 修饰的方法只有执行完毕后才会提交事务。

除了update 语句,select for update 语句也是可以产生行锁的(也不一定是行锁,也可能表锁),可以用于临时锁定一行记录不被修改。

今天研究的课题就是研究下 select for update 的细节

代码

直接上代码

  • Rest 接口
@RestController
public class TestRest {
    @Autowired
    @Qualifier("testService1Impl")
    private TestService testService1;
    @Autowired
    @Qualifier("testService2Impl")
    private TestService testService2;
    @Autowired
    @Qualifier("testService3Impl")
    private TestService testService3;
    @Autowired
    @Qualifier("testService4Impl")
    private TestService testService4;

    @GetMapping("/testSelectForUpdate1")
    public Object testSelectForUpdate1(@RequestParam Integer id) {
        return testService1.selectForUpdate(id);
    }
    @GetMapping("/update1")
    public String update1(@RequestParam Integer id) {
        String remark = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
        return "affected:" + testService1.update(id, remark);
    }


    @GetMapping("/testSelectForUpdate2")
    public Object testSelectForUpdate2(@RequestParam Integer id) {
        return testService2.selectForUpdate(id);
    }
    @GetMapping("/update2")
    public String update2(@RequestParam Integer id) {
        String remark = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
        return "affected:" + testService2.update(id, remark);
    }


    @GetMapping("/testSelectForUpdate3")
    public Object testSelectForUpdate3(@RequestParam Integer id) {
        return testService3.selectForUpdate(id);
    }
    @GetMapping("/update3")
    public String update3(@RequestParam Integer id) {
        String remark = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
        return "affected:" + testService3.update(id, remark);
    }


    @GetMapping("/testSelectForUpdate4")
    public Object testSelectForUpdate4(@RequestParam Integer id) {
        return testService4.selectForUpdate(id);
    }
    @GetMapping("/update4")
    public String update4(@RequestParam Integer id) {
        String remark = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
        return "affected:" + testService4.update(id, remark);
    }
}
  • Service实现类

其他 3 个Service实现类分别是:方法都去掉 @Transactional,去掉其中一个方法的 @Transactional

@Service
public class TestService1Impl implements TestService {
    @Autowired
    private TestMapper testMapper;

    @Transactional
    @Override
    public MyTable selectForUpdate(int id) {
        MyTable myTable = testMapper.selectForUpdate(id);
        int totalSecs = 20;
        for (int i = 0; i < totalSecs; i++) {
            try {
                System.out.println(String.format("=== 进度 %s / %s ===", (i+1), totalSecs));
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("===== END =======");
        return myTable;
    }

    @Transactional
    @Override
    public int update(int id, String remark) {
        System.out.println(String.format("~~~ update %s BEGIN ~~~~ ", id));
        int i = testMapper.update(id, remark);
        System.out.println(String.format("~~~ update %s END ~~~~ ", id));

        return i;
    }
}
  • Mapper
@Mapper
@Repository
public interface TestMapper {
    @Select("select * from my_table where id=#{id} for update")
    MyTable selectForUpdate(@Param("id") int id);


    @Update("update my_table set remark=#{remark} where id=#{id}")
    int update(@Param("id") int id, @Param("remark") String remark);
}
  • 建表,随便差一条记录
CREATE TABLE my_table (
  id int4 NOT NULL,
  remark varchar(255),
  remark2 varchar(255)
);
INSERT INTO my_table VALUES (1, '测试', '测试');

测试方法和结论

测试方法

先调用 /testSelectForUpdateN 方法,再调用 /updateN,成对进行调用,N是1-4。观察打印结果

结论

select for update 语句是可以锁住记录的。
细节:要求select for update 的方法(即selectForUpdate(int id))上加 @Transactional。至于update接口可以不加。

这是因为要保证 selectForUpdate(int id) 拥有较长时间处于锁住的状态就必须加,如果不加,执行完里面的 testMapper.selectForUpdate(id) 就直接提交事务了,这么短的时间你调用 update(int id, String remark) 没办法观察出其阻塞。

总结起来就是:

  • selectForUpdate(int id) 方法可以不加 @Transactional ,自然就有行锁的功能,这是数据库维护的
  • 但是,如果你想保证 selectForUpdate(int id) 整个方法期间锁定的这行数据无法被其他人修改,就必须加 @Transactional (这里是 “其他人” 可以理解为其他的线程,其他的事务)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值