由于所有的主要数据库都支持此功能,Hibernate提供了一个NOWAIT选项,可以在不同数据库上调用这个功能,而且不影响代码的数据库可移植性。
01
—
事务获取锁时被阻塞
关系数据库就像状态机一样运行,数据库事务将数据库从一个一致状态更改为另一个一致状态。如果出现数据一致性问题,数据库系统必须能够成功回滚所有未提交的更改,并将所有已经修改的记录还原到其之前的一致状态。即使大多数关系数据库系统使用 MVCC(多版本并发控制)机制来协调读写操作,每当执行 UPDATE 或 DELETE 操作时,仍会采用悲观锁定。每当我们对给定的表记录执行 UPDATE 或 DELETE 语句时,关系数据库系统都会获取并持有该记录的独占锁,直到当前事务以提交或回滚结束,如下图所示。
Alice的UPDATE锁定了表记录,因此当 Bob 想要使用FOR UPDATE子句获取锁时,他的锁获取请求将阻塞,直到 Alice 的交易结束或锁获取超时。使用 SELECT 查询的FOR UPDATE子句可以模拟相同的行为,如下图所示:
通过获取并保持独占锁直到事务结束,关系数据库系统避免了脏写,从而保证了事务的原子性。
02
—
SQL NOWAIT
为了避免SQL 语句在获取锁时被阻塞 ,我们可以使用 NOWAIT 子句,如下图所示:
现在,在获取锁时,该语句将立即抛出锁获取失败而不是阻塞,因此您可以捕获异常并继续执行其他操作,过段时间后再重新尝试获取锁。不同的数据库系统的NOWAIT 子句并不相同,见下表:
数据库 | 独占锁的NOWAIT子句 |
Oracle | FOR UPDATE NOWAIT |
SQL Server | WITH (UPDLOCK,HOLDLOCK,ROWLOCK,NOWAIT) |
PostgreSQL | FOR NO KEY UPDATE NOWAIT |
MySQL | FOR UPDATE NOWAIT |
幸运的是,在使用 JPA 和 Hibernate 时,开发人员无需编写针对特定数据库的SQL语句即可获取正确的NOWAIT 子句,因为框架会根据底层的数据库生成正确的SQL 语法。因此,开发人员只需在获取行级锁时使用LockOptions.NO_WAIT选项即可,如以下示例所示:
Post post = entityManager.find( Post.class, postId, LockModeType.PESSIMISTIC_WRITE, Map.of( SpecHints.HINT_SPEC_LOCK_TIMEOUT, LockOptions.NO_WAIT ));
就是这么简单!