views:

142

answers:

4
IF NOT EXISTS(SELECT * FROM MyTable WITH(nolock) WHERE Key = 'MyKey')
  INSERT MyTable(Key) Values('MyKey')

If The value does not exist in the table, does the query aquire a lock?

+2  A: 

EXISTS normally will still acquire a lock. But you added a hint that told it not to, and so it won't.

Joel Coehoorn
+5  A: 

From the docs:

READUNCOMMITTED and NOLOCK hints apply only to data locks. All queries, including those with READUNCOMMITTED and NOLOCK hints, acquire Sch-S (schema stability) locks during compilation and execution. Because of this, queries are blocked when a concurrent transaction holds a Sch-M (schema modification) lock on the table. For example, a data definition language (DDL) operation acquires a Sch-M lock before it modifies the schema information of the table. Any concurrent queries, including those running with READUNCOMMITTED or NOLOCK hints, are blocked when attempting to acquire a Sch-S lock. Conversely, a query holding a Sch-S lock blocks a concurrent transaction that attempts to acquire a Sch-M lock. For more information about lock behavior, see Lock Compatibility (Database Engine).

So it won't acquire a data lock, but it will still acquire a schema stability lock.

Greg Beech
Unfortunately, as with most docs that was nearly incomprehensible. But it sounds like it's saying you'll still get a relatively weak lock that only prevents you from ALTERing or DROPing the table, and even that is held only as long as it takes to compile the query.
Joel Coehoorn
As a side note, it's pretty dodgy using locking hints when checking for the presence of a row before doing an insert. If your transaction isolation level is something other than READ UNCOMMITTED then you're doing the insert on the basis of a query that isn't necessarily correct in the context of your current transaction, so you will potentially get errors if two queries try to insert the same record at the same time.
Greg Beech
@Joel - Yeah, you'll only get locks that prevent you modifying the table, but that both during compilation and execution. Realistically unless you have a self-modifying database that's unlikely to be a concern (and realistically, if you do have a self-modifying database, then you probably have more concerns than this...).
Greg Beech
@Greg, My query is looking for historical data, but new data is being written all the time. I don't want to prevent new data from being written while looking for old data.
scottm
A: 

Using a NOLOCK hint will indeed prevent the row lock. Just a heads up though, this kind of 'lookup and insert' is riddled with problems. The operation is not atomic and two sessions trying to do it will cause a race condition when both find the key missing and both try to insert, resulting in one of them causing a duplicate key violation. Is it also suboptimal because the index seek occurs twice (once to lookup the key, once to locate the insert position). The optimal and correct solution is to actually try to insert and recover from the duplicate key error if already exists.

Remus Rusanu
A: 

That code is vulnerable to error. Instead you could try:

Put a unique index on the table so that it's not possible to insert multiple rows that conflict, and then just insert. A conflict generates an error, which you'd need to handle.

Or, if conflicts are an expected condition and not the exception, then you'll want to make the insert/check atomic:

insert MyTable( [Key] ) 
  select 'MyKey' 
  where not exists ( 
    select * 
    from MyTable 
    where [Key] = 'MyKey' 
)

Also, note that (nolock) and Read Uncommitted do not produce accurate results, by design. It's OK for reporting and such, but dangerous to act on your data based on a decision that uses (nolock).

onupdatecascade