我正在使用 JPA/Hibernate 和 MySQL 使用 Java 应用程序,并试图理解正确的方法来做一些看起来应该很简单的事情。
目标
目标是:允许并行运行的两个线程(当然,每个线程都有自己的事务)查询,然后如果没有找到,则将唯一值插入到数据库中,并且这样做不会引发任何异常由 MySQL 提供。
所以每个线程首先查询记录是否存在,如果不存在,则插入该记录,然后完成。听起来很简单。
理论
我认为可以通过设置查询来消除异常的可能性LockModeType.PESSIMISTIC_WRITE
,因为这将转换为SELECT .. FOR UPDATE
,这意味着第一个线程的查询将在记录上设置一个独占写入锁,因此第二个线程的查询将阻塞,直到第一个线程提交,然后第二个线程将唤醒并能够看到新记录。
在直接使用 MySQL 之后,我意识到我缺少一些基本的东西,因为第二个线程实际上在其查询期间没有阻塞。
实践
我创建了一个这样的表:
CREATE TABLE `test`.`A` (
`id` BIGINT( 20 ) NOT NULL ,
`name` VARCHAR( 255 ) NOT NULL ,
PRIMARY KEY ( `id` ) ,
UNIQUE (`name`)
) ENGINE = InnoDB;
然后在两个单独的窗口X和Y中执行以下命令,一次执行一个命令,首先在X中,然后在Y中:
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2 BEGIN;
3 SELECT * FROM A WHERE `name`='Jones' FOR UPDATE;
4 INSERT INTO A (`name`) VALUES ('Jones');
5 COMMIT;
执行此测试时, X和Y中的每个语句都立即被接受且无错误,语句 #3 在两者中都返回零行,直到在Y中的命令 #4 之后发生错误:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
实际上这并不意外......(对我来说)意外的是,当我执行命令 #3时Y不会阻塞。一旦两个线程都查询并且什么也没看到,我的想法显然注定要失败,因为它们不可能都是正确的。'Jones'
我认为它的全部意义SELECT ... FOR UPDATE
在于它在记录上设置了一个排他锁,这应该导致Y在语句 #3 上阻塞。
问题
我错过了什么?甚至可以在 JPA 甚至 MySQL 中做我想做的事吗?
笔记:
- MySQL版本是
5.5.23-log MySQL Community Server (GPL)
- 更改为
SERIALIZABLE
没有任何帮助。