我看到的行为看起来像是在数据库本身上设置了READPAST提示。
摩擦:我不认为这是可能的。
我们有表foo(id int主键标识,名称varchar(50)not null unique);
我有几个线程,基本上
id = select id from foo where name = ?
if id == null
insert into foo (name) values (?)
id = select id from foo where name = ?
每个线程负责插入自己的名称(没有两个线程尝试同时插入相同的名称)。客户端是java。
READ_COMMITTED_SNAPSHOT为ON,事务隔离专门设置为READ COMMITTED,使用Connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
症状是,如果一个线程正在插入,则另一个线程无法看到它的行 - 甚至是在应用程序启动之前提交给数据库的行 - 并尝试插入,但会获得重复键异常来自名称上的唯一索引。
在这里扔我一块骨头?
答案 0 :(得分:1)
你处于错误的隔离级别。请记住快照隔离级别会发生什么。如果一个事务正在进行更改,则没有其他并发事务会看到该事务。期。其他事务只会在您提交后才会看到您的更改,但前提是它们在您提交后才会启动。解决方案是使用不同的隔离级别。在事务中包装您的语句并设置SET TRANSACTION LEVEL SERIALIZABLE。这将确保您的其他并发事务就像它们都是串行运行一样工作,这就是您在这里想要的。
答案 1 :(得分:0)
听起来你没有把select和insert插入到一个事务中?
作为解决方案,您可以:
insert into foo (col1,col2,col3) values ('a','b','c')
where not exists (select * from foo where col1 = 'a')
在此之后,如果可以检查是否插入了行,@@rowcount
将为1。
答案 2 :(得分:0)
SELECT SCOPE_IDENTITY()
应该在这里诀窍......
再加上像之前提到的海报一样的交易。
答案 3 :(得分:0)
这个故事的寓意在我的博客文章"You can't hold onto nothing"中得到了充分的解释,但这个版本的简短版本就是你要使用HOLDLOCK提示。我使用的模式:
INSERT INTO dbo.Foo(Name)
SELECT TOP 1
@name AS Name
FROM (SELECT 1 AS FakeColumn) AS FakeTable
WHERE NOT EXISTS (SELECT * FROM dbo.Foo WITH (HOLDLOCK)
WHERE Name=@name)
SELECT ID FROM dbo.Foo WHERE Name=@name