views:

220

answers:

1

I need to select first (let's say) 10000 rows in database and return them. There may be more clients that do this operation at one time. I came up with this query:

update v set v.batch_Id = :batchId 
    from tblRedir v 
    inner join (
        select top 10000 id 
            from tblRedir
            where batch_Id is null 
            order by Date asc
    ) v2 on v.id=v2.id

It is a operation that consists from update and nested select. Both the queries work on the same table (tblRedir). The idea is that the rows are first marked by a unique batchId and then returned via

select * from tblRedir where batch_id = :batchId

(the batchid is a unique identifier (e.g. timestamp or guid) for each this update)

My question:

I thought that the operation update with nested select is atomic - that means that every client receives his own set of data that is unique (no other client received a subset of his data).

However it looks that I'm wrong - in some cases there are clients that receive no data, because probably they first both execute the select and then both execute the update (so the first client has no marked rows).

Is this operation atomic or not?


I work with Sql server 2005. The query is run via NHibernate like this

session.CreateSQLQuery('update....')
+2  A: 

SELECT places shared locks on the rows read which then can be lifted in READ COMMITED isolation mode.

UPDATE places the update locks later promoted to exclusive locks. They are not lifted until the end of the transaction.

You should make the locks to retain as soon as they are placed.

You can do it by making the transaction isolation level REPEATABLE READ which will retain the shared locks until the end of the transaction and will prevent UPDATE part from locking these rows.

Alternatively, you can rewrite your query as this:

WITH    q AS
        (
        SELECT  TOP 10000 *
        FROM    mytable WITH (ROWLOCK, READPAST)
        WHERE   batch_id IS NULL
        ORDER BY
                date
        )
UPDATE  q
SET     batch_id = @myid

, which will just skip the locked rows.

Quassnoi
Thx for your answer. I've tried the alternative ('with q as...') and it seems that READPAST can not be used with HOLDLOCK.I tried isolation level 'read committed', 'repeatable read' and in both cases sql server complains 'You can only specify the READPAST lock in the READ COMMITTED or REPEATABLE READ isolation levels'. Is the query correct even without the HOLDLOCK?
stej
`@stej`: I don't think `SQL Server` will split `SELECT` and `UPDATE` parts in this case, so yes, the update locks will be placed automatically. You may remove the `HOLDLOCK`.
Quassnoi
Thank you. I tried it, it seems it works. In some cases there were many deadlocks, but then suddenly there is none - it's hard to reproduce it. In case I'll be able to describe it somehow, I'll submit a new question.
stej