91

I'm using a Microsoft SQL Server 2005 database with isolation level READ_COMMITTED and READ_COMMITTED_SNAPSHOT=ON.

Now I want to use:

SELECT * FROM <tablename> FOR UPDATE

...so that other database connections block when trying to access the same row "FOR UPDATE".

I tried:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

...but this blocks all other connections even for selecting an id other than "1".

Which is the correct hint to do a SELECT FOR UPDATE as known for Oracle, DB2, MySql?

EDIT 2009-10-03:

These are the statements to create the table and the index:

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

A lot of parallel processes do this SELECT:

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

EDIT 2009-10-05:

For a better overview I've written down all tried solutions in the following table:

mechanism              | SELECT on different row blocks | SELECT on same row blocks
-----------------------+--------------------------------+--------------------------
ROWLOCK                | no                             | no
updlock, rowlock       | yes                            | yes
xlock,rowlock          | yes                            | yes
repeatableread         | no                             | no
DBCC TRACEON (1211,-1) | yes                            | yes
rowlock,xlock,holdlock | yes                            | yes
updlock,holdlock       | yes                            | yes
UPDLOCK,READPAST       | no                             | no

I'm looking for        | no                             | yes
Bill Paetzke
  • 13,332
  • 6
  • 44
  • 46
tangens
  • 39,095
  • 19
  • 120
  • 139
  • I'm not only looking for optimizer hints. Another possible solution could be to change the isolation level, global database attributes, ... Everything (but to use a different database) is possible. – tangens Sep 30 '09 at 08:55
  • 4
    What are you trying to do that needs such locking. It's usually better to solve with proper queries rather than server 'features' – TFD Oct 02 '09 at 23:39
  • 2
    Can you please provide the query that you are using and the DDL of the table(s), including any Keys and Indexes. – RBarryYoung Oct 03 '09 at 01:51
  • are you sure your other query is not transaction isolation read uncommitted? – Nathan Feger Oct 03 '09 at 23:22
  • Yes, I'm sure. It's two times the same java class with the same mechanism to create a JDBC connection. – tangens Oct 05 '09 at 15:56
  • have you tried making the MVCC permanent on your database? mssql 2005 default isolation level is non-mvcc, you have to set your database explicitly to mvcc(i.e. ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON) http://www.codinghorror.com/blog/archives/001166.html – Michael Buen Oct 07 '09 at 06:08
  • 1
    As a workaround you can try to do a simple update on this row first (without really changing any data). After that you can proceed with the row like in was selected for update. – Vladimir Oct 23 '11 at 17:32
  • @Lenin, i think it will not block select, would it? – bjan May 09 '12 at 12:16
  • @bjan, it should block it if you wrap it in a transaction. But I'm not sure if this solution works however – Vladimir May 11 '12 at 19:34
  • @Lenin, yes SQL Server 2000, atleast, does not allow SELECT on a row in session if the same row is being modified by another session in a transaction. However, Oracle, atleast 11g, does not. It allows SELECT – bjan May 18 '12 at 13:08
  • You don't mention enabling ALLOW_SNAPSHOT_ISOLATION, too. Did you try that in combination with READ_COMMITTED_SNAPSHOT? – László van den Hoek Aug 08 '12 at 17:31

18 Answers18

36

Recently I had a deadlock problem because Sql Server locks more then necessary (page). You can't really do anything against it. Now we are catching deadlock exceptions... and I wish I had Oracle instead.

Edit: We are using snapshot isolation meanwhile, which solves many, but not all of the problems. Unfortunately, to be able to use snapshot isolation it must be allowed by the database server, which may cause unnecessary problems at customers site. Now we are not only catching deadlock exceptions (which still can occur, of course) but also snapshot concurrency problems to repeat transactions from background processes (which cannot be repeated by the user). But this still performs much better than before.

Community
  • 1
  • 1
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • I'm afraid this is true. I found no way to get a working "SELECT FOR UPDATE". My solution now is to give up the "SELECT FOR UPDATE" and do a simple, non blocking "SELECT" and check for concurrent "UPDATES" with an update counter ("UPDATE WHERE id=? and updateCount=?"). – tangens Oct 07 '09 at 06:02
  • 1
    SQL 2008 provides two new optimistic isolation levels, similar to what Oracle offers – Chris Bednarski Apr 20 '10 at 09:52
  • @ChrisBednarski i used WITH(ROWLOCK) in SQL Server 2008 R2 and it still locked more than one row !!! can you provide some explanation – bjan May 18 '12 at 04:56
  • 2
    @bjan: look up snapshot isolation. Some info here http://msdn.microsoft.com/en-us/library/tcbchxcb(v=vs.80).aspx – Chris Bednarski May 20 '12 at 01:31
  • 1
    @ChrisBednarski thanks for the link, these two commands made a difference of day and night: ALTER DATABASE _MyDatabase_ SET ALLOW_SNAPSHOT_ISOLATION ON; ALTER DATABASE _MyDatabase_ SET READ_COMMITTED_SNAPSHOT ON; see also MichaelBuen's answer to this question. – László van den Hoek Aug 08 '12 at 17:25
  • Sorry, but although I dislike SQLServer, this actually is wrong. You _CAN_ have rowlevel locks by defining an index perfectly matching the where clause. pitfall : you also have to do this if your where clause contains the primary key column. I found out that sqlserver does not care for a unique match by applying the primary key (hibernate, PK+discriminator = pagelock), so one must define a matching index. – gorefest Jun 11 '15 at 10:28
30

I have a similar problem, I want to lock only 1 row. As far as I know, with UPDLOCK option, SQLSERVER locks all the rows that it needs to read in order to get the row. So, if you don't define a index to direct access to the row, all the preceded rows will be locked. In your example:

Asume that you have a table named TBL with an id field. You want to lock the row with id=10. You need to define a index for the field id (or any other fields that are involved in you select):

CREATE INDEX TBLINDEX ON TBL ( id )

And then, your query to lock ONLY the rows that you read is:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

If you don't use the INDEX(TBLINDEX) option, SQLSERVER needs to read all rows from the beginning of the table to find your row with id=10, so those rows will be locked.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
ManuelConde
  • 309
  • 3
  • 2
  • +1 for providing the root cause, why SQL Server "ignores" the rowlock directive. I've used this approach under my app and indeed I've achieved what tangens asks for – Shmil The Cat Jan 26 '14 at 11:26
  • -1 I had a clustered index and including that inside the hints made no difference. The updlock+rowlock+holdlock worked great once I realized that by mistake I was touching other records past the locking SELECT statement in my sp (was missing a condition on ids that was present in the original select). – MoonStom Nov 17 '15 at 00:49
9

You cannot have snapshot isolation and blocking reads at the same time. The purpose of snapshot isolation is to prevent blocking reads.

Christian Hayter
  • 30,581
  • 6
  • 72
  • 99
6

The full answer could delve into the internals of the DBMS. It depends on how the query engine (which executes the query plan generated by the SQL optimizer) operates.

However, one possible explanation (applicable to at least some versions of some DBMS - not necessarily to MS SQL Server) is that there is no index on the ID column, so any process trying to work a query with 'WHERE id = ?' in it ends up doing a sequential scan of the table, and that sequential scan hits the lock which your process applied. You can also run into problems if the DBMS applies page-level locking by default; locking one row locks the entire page and all the rows on that page.

There are some ways you could debunk this as the source of trouble. Look at the query plan; study the indexes; try your SELECT with ID of 1000000 instead of 1 and see whether other processes are still blocked.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • So it depends on MS SQL Server; maybe it won't skip over locks on the index. I suggest trying the 'ID = ' test on a big enough data set that you have multiple pages in use. You might see a difference; you might not. You might try running the 'other processes' at a 'dirty read' isolation level; this should get you past the locks for reading - but it is not a very good solution in general. – Jonathan Leffler Sep 27 '09 at 15:48
  • All the processes do the same read: they select "their" record and update the status at the end of the operation. – tangens Sep 27 '09 at 20:13
  • 1
    So, the value for ID is the same for each process? Or they each use a different ID? I assume the latter. And the issue is whether the DBMS will allow sufficiently different values of ID to bypass any locking. My speculation is that if the ID values are different enough, you'll get past locks. But not if the index on ID is not used, for example. Have you looked at the query plans yet? – Jonathan Leffler Sep 27 '09 at 21:05
  • Sorry, I wasn't able to figure out how to do this yet. And yes, each process uses a different ID. – tangens Sep 28 '09 at 05:08
  • I looked at the query plan yes but I saw nothing special. – tangens Oct 05 '09 at 16:00
6

perhaps making mvcc permanent could solve it (as opposed to specific batch only: SET TRANSACTION ISOLATION LEVEL SNAPSHOT):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[EDIT: October 14]

After reading this: Better concurrency in Oracle than SQL Server? and this: http://msdn.microsoft.com/en-us/library/ms175095.aspx

When the READ_COMMITTED_SNAPSHOT database option is set ON, the mechanisms used to support the option are activated immediately. When setting the READ_COMMITTED_SNAPSHOT option, only the connection executing the ALTER DATABASE command is allowed in the database. There must be no other open connection in the database until ALTER DATABASE is complete. The database does not have to be in single-user mode.

i've come to conclusion that you need to set two flags in order to activate mssql's MVCC permanently on a given database:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;
Community
  • 1
  • 1
Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • After reading the comment of @Chris Bednarski's under the accepted answer, I came to the same conclusion as you, Michael. This appears to relieve a great deal of our deadlocking issues. If you use the database properties dialog from SQL Server Management Studio, you are prompted to disconnect all clients and no restart is necessary. – László van den Hoek Aug 08 '12 at 17:22
5

Try (updlock, rowlock)

BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
  • Yes, I tried rowlock (without combination with other hints), but then a concurrent SELECT FOR UPDATE of the same row doesn't block. – tangens Sep 27 '09 at 15:03
  • OK, I tried (updlock, rowlock), but a second SELECT FOR UPDDATE blocks even when accessing another row. – tangens Sep 27 '09 at 15:11
  • (xlock,rowlock) blocks other rows too. – tangens Sep 27 '09 at 15:34
  • Why do you think that it should Block? – RBarryYoung Oct 03 '09 at 01:52
  • Because that's the behaviour I'm looking for. Two threads read a record for update, change some values and do the update. I want the second thread to block until the first finishes his transaction. – tangens Oct 03 '09 at 09:35
  • 1
    That's pessimistic concurrency. If you want that then why are you specifying optimistic concurrency? – RBarryYoung Oct 04 '09 at 05:09
  • I think you are asking why I use "READ_COMMITTED_SNAPSHOT=ON"? Because otherwise a second SELECT on the same row doesn't block even if I say 'WITH (updlock)'. – tangens Oct 05 '09 at 15:58
3

OK, a single select wil by default use "Read Committed" transaction isolation which locks and therefore stops writes to that set. You can change the transaction isolation level with

Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
Begin Tran
  Select ...
Commit Tran

These are explained in detail in SQL Server BOL

Your next problem is that by default SQL Server 2K5 will escalate the locks if you have more than ~2500 locks or use more than 40% of 'normal' memory in the lock transaction. The escalation goes to page, then table lock

You can switch this escalation off by setting "trace flag" 1211t, see BOL for more information

TFD
  • 23,890
  • 2
  • 34
  • 51
3

I'm assuming you don't want any other session to be able to read the row while this specific query is running...

Wrapping your SELECT in a transaction while using WITH (XLOCK,READPAST) locking hint will get the results you want. Just make sure those other concurrent reads are NOT using WITH (NOLOCK). READPAST allows other sessions to perform the same SELECT but on other rows.

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT
ewoo
  • 51
  • 3
3

Create a fake update to enforce the rowlock.

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

If that's not locking your row, god knows what will.

After this "UPDATE" you can do your SELECT (ROWLOCK) and subsequent updates.

KMån
  • 9,896
  • 2
  • 31
  • 41
Feu
  • 5,372
  • 1
  • 31
  • 57
  • I wonder if this performs worse than the alternative, which is to just do a selected with an xlock hint. Rowlock isn't necessary reliable, because SQL Server doesn't actually lock rows, it locks 6-byte hashes of rows, so there's a chance of collision with a large number of records in the table. Also, because rowlock is a "hint", it doesn't guarantee lock escalation won't occur, although you can probably minimize that risk by disabling lock escalation on the table. – Triynko Sep 09 '19 at 22:04
2

Question - is this case proven to be the result of lock escalation (i.e. if you trace with profiler for lock escalation events, is that definitely what is happening to cause the blocking)? If so, there is a full explanation and a (rather extreme) workaround by enabling a trace flag at the instance level to prevent lock escalation. See http://support.microsoft.com/kb/323630 trace flag 1211

But, that will likely have unintended side effects.

If you are deliberately locking a row and keeping it locked for an extended period, then using the internal locking mechanism for transactions isn't the best method (in SQL Server at least). All the optimization in SQL Server is geared toward short transactions - get in, make an update, get out. That's the reason for lock escalation in the first place.

So if the intent is to "check out" a row for a prolonged period, instead of transactional locking it's best to use a column with values and a plain ol' update statement to flag the rows as locked or not.

onupdatecascade
  • 3,336
  • 22
  • 35
  • Agreed, although I'd rather go for an optimistic locking approach of creating row versions, as it's done in ORM's / persistence layers such as Hibernate, see: https://codippa.com/optimistic-locking-in-hibernate-auto-versioning-in-hibernate/ This assumes a controlled environment where updates go through a specific application, or that multiple applications adopt the same approach. This article covers most scenarios, including SQL Server's specific 'rowversion' data type: http://www.simple-talk.com/sql/t-sql-programming/developing-modifications-that-survive-concurrency/ – Kamal Feb 26 '20 at 22:11
2

Application locks are one way to roll your own locking with custom granularity while avoiding "helpful" lock escalation. See sp_getapplock.

Constantin
  • 27,478
  • 10
  • 60
  • 79
  • App locks, unfortunately, don't always help when it comes to deadlock issues. No matter what you lock on with sp_getapplock, when you actually go and start modifying database records, you have to contend with the fact that the engine starts locking rows, and inadvertently locks unrelated rows as a result of page or table lock escalations that could collide with concurrent transactions. Best to disable lock escalation and specify exclusive row lock hints, and lock rows in a defined locking order to avoid deadlock as much as possible. – Triynko Sep 09 '19 at 22:06
  • I'd rather go for an optimistic locking approach of creating row versions, as it's done in ORM's / persistence layers such as Hibernate, see: https://codippa.com/optimistic-locking-in-hibernate-auto-versioning-in-hibernate/ This assumes a controlled environment where updates go through a specific application, or that multiple applications adopt the same approach. This article covers most scenarios, including SQL Server's specific 'rowversion' data type and your own sp_getapplock suggestion: http://www.simple-talk.com/sql/t-sql-programming/developing-modifications-that-survive-concurrency/ – Kamal Feb 26 '20 at 22:12
1

Try using:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

This should make the lock exclusive and hold it for the duration of the transaction.

RMorrisey
  • 7,637
  • 9
  • 53
  • 71
1

According to this article, the solution is to use the WITH(REPEATABLEREAD) hint.

erikkallen
  • 33,800
  • 13
  • 85
  • 120
1

Revisit all your queries, maybe you have some query that select without ROWLOCK/FOR UPDATE hint from the same table you have SELECT FOR UPDATE.


MSSQL often escalates those row locks to page-level locks (even table-level locks, if you don't have index on field you are querying), see this explanation. Since you ask for FOR UPDATE, i could assume that you need transacion-level(e.g. financial, inventory, etc) robustness. So the advice on that site is not applicable to your problem. It's just an insight why MSSQL escalates locks.


If you are already using MSSQL 2005(and up), they are MVCC-based, i think you should have no problem with row-level lock using ROWLOCK/UPDLOCK hint. But if you are already using MSSQL 2005 and up, try to check some of your queries which query the same table you want to FOR UPDATE if they escalate locks by checking the fields on their WHERE clause if they have index.


P.S.
I'm using PostgreSQL, it also uses MVCC have FOR UPDATE, i don't encounter same problem. Lock escalations is what MVCC solves, so i would be surprised if MSSQL 2005 still escalate locks on table with WHERE clauses that doesn't have index on its fields. If that(lock escalation) is still the case for MSSQL 2005, try to check the fields on WHERE clauses if they have index.

Disclaimer: my last use of MSSQL is version 2000 only.

Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • Thanks for the link to the article. I've written a junit test doing exaclty what I need and I can reproduce the missing locks or the escalated locks with just a few statements. So I can guarantee that there is no mix of queries with and without ROWLOCK. – tangens Sep 30 '09 at 09:59
1

You have to deal with the exception at commit time and repeat the transaction.

1

I solved the rowlock problem in a completely different way. I realized that sql server was not able to manage such a lock in a satisfying way. I choosed to solve this from a programatically point of view by the use of a mutex... waitForLock... releaseLock...

jessn
  • 11
  • 1
0

Have you tried READPAST?

I've used UPDLOCK and READPAST together when treating a table like a queue.

Gratzy
  • 9,164
  • 4
  • 30
  • 45
  • I tried it, but then a second select doesn't find the record. That's not the behaviour I'm looking for. I want the second select to block until the first finishes his transaction. – tangens Oct 03 '09 at 09:38
-1

How about trying to do a simple update on this row first (without really changing any data)? After that you can proceed with the row like in was selected for update.

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

Edit: you should wrap it in a transaction of course

Edit 2: another solution is to use SERIALIZABLE isolation level

Vladimir
  • 1,630
  • 2
  • 18
  • 28