我正在使用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 然后在的ý:
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
实际上这并不意外......对我来说意外的是 Y 在执行命令#3 时不会阻塞。一旦两个线程都查询了'Jones'
并且没有看到任何内容,我的想法显然是注定的,因为它们不可能都是正确的。
我认为SELECT ... FOR UPDATE
的重点是它在记录上设置了一个独占锁定,这会导致 Y 阻止声明#3。
问题
我错过了什么?甚至可以在JPA甚至MySQL中做我想做的事情吗?
注意:
5.5.23-log MySQL Community Server (GPL)
SERIALIZABLE
没有任何帮助。