views:

154

answers:

4

I'm seeing behavior which looks like the READPAST hint is set on the database itself.

The rub: I don't think this is possible.

We have table foo (id int primary key identity, name varchar(50) not null unique);

I have several threads which do, basically

id = select id from foo where name = ?
if id == null
    insert into foo (name) values (?)
id = select id from foo where name = ?

Each thread is responsible for inserting its own name (no two threads try to insert the same name at the same time). Client is java.

READ_COMMITTED_SNAPSHOT is ON, transaction isolation is specifically set to READ COMMITTED, using Connection.setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED );

Symptom is that if one thread is inserting, the other thread can't see it's row -- even rows which were committed to the database before the application started -- and tries to insert, but gets a duplicate-key-exception from the unique index on name.

Throw me a bone here?

A: 

Sounds like you're not wrapping the select and insert into a transaction?

As a solution, you could:

insert into foo (col1,col2,col3) values ('a','b','c')
where not exists (select * from foo where col1 = 'a')

After this, @@rowcount will be 1 if can check if a row was inserted.

Andomar
good suggestion, but the the initial select fails, even for rows which have been in the database since before the application boots.We're looking for ways that a row can be hidden from an select statement.
dg
@dg: Rows can't be hidden from select statements
Andomar
A: 
SELECT SCOPE_IDENTITY()

should do the trick here...

plus wrapping into a transaction like previous poster mentioned.

Greco
that would be more efficient, for sure, but the exception occurs on insert, because the first select didn't find the row it was looking for. SCOPE_IDENTITY would work if the insert succeeded.
dg
+1  A: 

You're at the wrong isolation level. Remember what happens with the snapshot isolation level. If one transaction is making a change, no other concurrent transactions see that transaction. Period. Other transactions only will see your changes once you have committed, but only if they START after your commit. The solution to this is to use a different isolation level. Wrap your statements in a transaction and SET TRANSACTION LEVEL SERIALIZABLE. This will ensure that your other concurrent transactions work as if they were all run serially, which is what you seem to want here.

Dave Markle
that's a good thought, although I'm not sure how it jives with what we're seeing: rows which were in the database before the application boots are no longer visible. In other words, the transaction whose rows I'm trying to see are not the other thread's transaction, they are rows from a transaction long committed.
dg
Then I think you have a bigger, more fundamental problem. I don't think that what you think is going on is actually going on. It's not a case of hints or isolation levels if you're sure that a transaction is committed and a reader can't see the committed row. It's either being changed by somebody somehow or it never actually was committed. Try using a transaction log viewer like Red-Gate's stuff if you really need to see what's going on.
Dave Markle
@Dave - Thanks, you're absolutely right. We had a deeper problem that I interpreted in the wrong way. We couldn't read the values for certain rows, but we could for others. The fact that "bad" values were the ones handled by individual threads was coincidence only.The issue we're seeing is similar to the one linked below. Dropping the unique index "fixed" the problem. Of course we can't leave a unique index without any constraint, but we're moving again:http://social.msdn.microsoft.com/Forums/en/sqldatabaseengine/thread/03f734cd-1c01-4767-b434-4be824c254b2Thanks!
dg
A: 

The moral of this story is fully explained in my blog post "You can't hold onto nothing" but the short version of this is that you want to use the HOLDLOCK hint. I use the pattern:

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
IDisposable