最近在工作过程中,用JPA的时候有一个注解。是
@Lock(value = LockModeType.PESSIMITIC_READ)
BizDistributeLock findFirstByBizName(String bizName);
此外,还有一个
LockModeType.PESSIMISTIC_WRITE
看语义,是悲观读锁。
那么,需求是这样的
有两张表,A表有数据做插入操作。可能会有多个用户并发操作。B表是工具表,该表只有一个字段,用来控制串行操作。代码实现之前,先调用了上面的查询,再执行后面正式的逻辑,目的是想让第二个人调用接口后在第一步执行查询B表操作的时候,卡在那里,进入不到后面的正式逻辑。
这里的目的是没有问题的,我的疑惑发生在用PESSIMISTIC_WRITE还是PESSIMISTIC_READ。我拿着百度查到的资料,和领导讨论了好久,领导查资料的能力还是强,给我发了下图,这下我就明白了。虽然JPA区分了PESSIMISTIC_WRITE和PESSIMISTIC_READ,但是实际上没啥区别,只不过从语义上来说还是用PESSIMISTIC_WRITE更合适。
那么关于数据库的锁,这里也做下笔记。从18年开始接触数据库,已经两年多了,然而对于数据库的锁机制还是很混乱,有必要组织下知识点,好好梳理一下。
FOR UPDATE 是排他锁
所谓的乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
在乐观锁中,我们有3种常用的做法来实现:
2.1 比对法
第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
2.2 版本戳
第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
2.3 timestamp型
第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。