背景
我们知道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 (这里是 “其他人” 可以理解为其他的线程,其他的事务)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135237.html