Why does the index lock my updates in SQL Server 2005?

When one process has one record locked in Update-Mode then an other process can't update any other records on that table with some queries, other queries that access the same records (but with a different WHERE statement) will execute.
This problem occurs with SQL Server 2005, but it didn't with SQL Server 2000 (or any other database).

The problem:
-Process 1 locks a record in the table, and keeps it in Update-Mode, because the user is editing it. (Using OLEDB Pessimistic Cursor-locking)
-Process 2 wants to update an other record, buts gets a "Lock timeout" when using one query but not with another.

For example this query will work :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE row_id = 110;

But this qeury will give me a "Lock timeout" :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2';

But it is the same record!!
The record with name= 'REC_2'has the row_id = 110, both values are unique in the table.

The data:
The table [gwseqnumber] has the following CREATE statement:
CREATE TABLE GWSEQNUMBER
(
NEXTSEQNR INTEGER,
NAME CHAR (20),
ADMINISTRATIONCODE INTEGER,
FINDHIGHESTNUMBER CHAR (1),
CLOSEDYN CHAR (1),
ROW_ID INT IDENTITY(1,1) NOT NULL
);

CREATE INDEX KEY_1 ON GWSEQNUMBER (NEXTSEQNR);
CREATE UNIQUE KEY_2 ON GWSEQNUMBER (ADMINISTRATIONCODE, NAME, IDENTIFIER);
CREATE UNIQUE INDEX KEY_3 ON GWSEQNUMBER (ROW_ID);

Both KEY_2 and KEY_3 are unique, KEY_1 is not.

If I remove the index on the NEXTSEQNR column (the index named KEY_1) then both these queries will work, so it is obviously related to the index.
Altough the index is obsolete and can be removed from this table, it should not result in bogus locking errors.

Even when i removed the index not all the queries will work. (With work i mean not run into a locking error, while the record is not even locked.)

Solutions tried:
- Set the compaitiblity level of the databsae back to 80 (for SQL Server 2000 compatibility).
- I have already tried to disable Page-Locking on all the indexes of this table.


So why does the index lock my updates in SQL Server 2005?
And how do i fix it so my database does not run into these locks?

[2923 byte] By [TheovanderStap] at [2007-12-19]
# 1

Suggestion is that UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2' requires table scan to locate the record, so the scan stops every time it reaches the locked record with row_id = 100. Anyway, you can investigate it by sp_lock procedure.

Solution: try to create index on the name column.

P.S. What's is the reason the table have no clustered index at all?

gavrilenko_s at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 2

That explains how the statements gets a lock error, but not why. This problem did not occur with SQL Server 2000, or most other databases, so why is it a problem for SQL Server 2005?

If i where to add an index for every possible WHERE statement then i would need to create thousands for my entire database. This is not a very logical solution since it is possible that new WHERE statements are introduced.

Why is there no clustered index?
I tried to keep the indexes as simple as possible to make it easier to find the problem. Making one of the indexes clustered however would not solve this problem, have already tried this.

Since the record i want is not locked i should still be able to update it, right?
And if i use a different WHERE statement i can update it, that's just wrong!

So the question remains: How do i fix it so my database does not run into these locks? (Since this problem did not occur with SQL Server 2000.)

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 3

So your main problem's here:
-Process 1 locks a record in the table, and keeps it in Update-Mode, because the user is editing it.

The approach is to keep transactions as short as possible. And never to use standart lock engine to implement business logic - it's not flexible, its behavior may change from version to version ( as you have found it out yourself ). All the data must have been saved within one short transaction after the user pushed the 'Save' button. To implement pessimistic locks you may somehow mark you record as 'locked' (it's a tricky to maintain only live 'locks', but the problem solvable)

If i where to add an index for every possible WHERE statement then i would need to create thousands for my entire database. This is not a very logical solution since it is possible that new WHERE statements are introduced.

Since the record i want is not locked i should still be able to update it, right?
And if i use a different WHERE statement i can update it, that's just wrong!

There are not only logic while the MSSQL executes a query, there are also a physics presented. MSSQL should locate the data you want to update (select or delete, no matter). If MSSQL does't find apropriate index, it use table or clustered index scan access the data, no other options. While scanning the data, if MSSQL faces the exclusively locked record, it can't even read it (that's eclusive locks were invented for, weren't they?), and since the record couldn't be read, you can't identify does the record meet your conditions or not, can you? So the scan process stops untill the record remains locked (or the timeout occured).

So avoid the scan as a method of data locating. It's a slow method. It has other not so obvious disadvantages.

For example this query will work :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE row_id = 110;

But this qeury will give me a "Lock timeout" :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2';

Why does you use different key fields to locate the same record? And, if name field is a key a key field why does it have no index on it?

Since this problem did not occur with SQL Server 2000

There are many changes made to the MSSQL 2005 engine and query optimizer. Anyway, you should comare execution palns for the queries run on different versions of MSSQL.

gavrilenko_s at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 4

First of all thanks for your reply. It is obvious you know your stuff.

The approach is to keep transactions as short as possible.

I understand that we must try to keep transactions and locks as short as possible. But we can't sacrifice the option that users are working manually on a record and that no other users are allowed to edit (either manually or by using one of the many functions).
So records will be locked for longer periods of time, it just can't be avoided, unless you remove all the users ( not a bad idea :-) ).

And never to use standart lock engine to implement business logic - it's not flexible.

How else can you efficiently prevent other users from updating the record, and prevent other UPDATES genereated by functions or external processes from updating it. This is what locks are for, and what pessimistic locking is for.

You mention Exclusive-Locks, but they are not used in this case, the pessimistic locking mechanism uses an Update-Lock, so the data remains readable.

Why does you use different key fields to locate the same record? And, if name field is a key a key field why does it have no index on it?

Due to the many functions that our system implements many possible WHERE statements can be used to locate records (even if they are meant to locate the same record).

There are many changes made to the MSSQL 2005 engine and query optimizer. Anyway, you should comare execution palns for the queries run on different versions of MSSQL.

Why was this not included in the backward compatibility feature of SQL Server 2005, (by setting the Compatibility Level to 80)?

Maybe the only way to solve this problem is to change ALL the UPDATE queries to use a Sub selection like so:
UPDATE gwseqnumber SET nextseqnr = 3 WHERE row_id IN (SELECT row_id FROM gwseqnumber WHERE NAME = 'REC_2');

Although this will result in possibly slow and inefficient execution, it will allow the program to update the requested record (that was not locked anyway!).

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 5

Pessimistic locking is not a really good idea in a server based application. Consider changing to use optimistic locks unless you have a really high amount of users editing the same row.

This statement:

UPDATE gwseqnumber SET nextseqnr = 3 WHERE row_id IN (SELECT row_id FROM gwseqnumber WHERE NAME = 'REC_2');

Shouldn't run any slower than:

UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2';

But it should still be blocked by an UPDATE or EXCLUSIVE locks...Anyhow, I would suggest two steps to see what is going on:

First, use profiler to capture the exact statments that are taking place when the lock is being taken.

Next, try running this to look at the locks. Perhaps it will give us an idea of the locking issues you are having. You might want to trim the output to just a couple of session_ids...

(Ok, I am a bit new with the dynamic management views, so I might not have this 100% correct :)

use adventureworks
go
select login_name,
case des.transaction_isolation_level
when 0 then 'Unspecified' when 1 then 'ReadUncomitted'
when 2 then 'ReadCommitted' when 3 then 'Repeatable'
when 4 then 'Serializable' when 5 then 'Snapshot'
end
request_session_id, resource_type, resource_subtype, request_mode, request_type, request_status,
request_owner_type,
case when resource_type = 'object' then object_name(resource_associated_entity_id)
when resource_type = 'database' then db_name(resource_associated_entity_id)
when resource_type in ('key','page') then (select object_name(object_id) from sys.partitions where hobt_id = resource_associated_entity_id)
else cast(resource_associated_entity_id as varchar(20))
end
from sys.dm_tran_locks dtl
left outer join sys.dm_exec_sessions des
on dtl.request_session_id = des.session_id
where request_session_id <> @@spid

Post the results and let's see what is causing the blocks. To me, if you didn't see blocking in 2000 it might have been a bug. Any time you modify a table and it requires a table scan, it should be blocked by any full pessimistic lock (if any other user can touch a row in the table or index of a table, they could fetch a copy of the row and later try to modify it.

# 6

First of all thanks, you helped me a bit further but I still haven’t found the solution.

I have done as you asked me and also did some more testing of my own.
First off all I have temporary simplified the problem, for testing. Process 1 now locks the record in a transaction with the following query and nothing else.

SELECT nextseqnr FROM gwseqnumber WITH (UPDLOCK) WHERE name = “REC_1”

This should only result in one update lock and only on this row. Running your script proves this works as planned.The following image shows that we have multiple intended locks, and only one update lock.

Image 1

Now when we execute the following query in a second process we get a lock timeout.

UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2'

Using the script again gives us the following interesting result.

Image 2

It appears that for some reason the query wants to put an update lock on the same record. Now the big question is, why?

If the query scans the complete table to find the record to update, it appears to me that it should only lock the record that it is supposed to update. Shouldn’t a table scan just work like a select?

If the query locks the record to update the index, than why doesn’t it work the same when I change the where statement in the query? (see previous examples)

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 7

So what is "probably" happening is that it is doing a table scan, locking each row as it scans through each row. In ReadCommitted isolation level, locks are acquired through grabbing a row, checking it, releasing it, grab the next, and so on. (commonly referred to as "crabbing" through the rows, for the mental image of a crab walking sideways.) Updates should always be done using a unique index for maximum concurrency. If you have fetched the row for editing, why not fetch the primary key as well and use this as the key for the update?

The reason why you get the blocking here but not with the SELECT, is that since the UPDATE is crabbing using update locks, an Update lock (which signals that the process might update the row.) (If you look at the lock compatibility chart that I mentioned yesterday (searching for shared lock mode will get you there) it shows what locks are compatible) Update locks are compatible with Shared locks, but not update locks. Hence reader can read the row, but not update it.

You should note that what you are doing is not really an effective pessimistic locking mechanism, even if you get the locking worked out. All this does is force any other writer to wait. But if three people read the row, and then all three go to update the row, they will all wait and eventually succeed or lock timeout. And if they lock the row for editing and their fetch was before the other writer made the change, you can overwrite changes. (at which time you need to use optimistic locking)

# 8

So what is "probably" happening is that it is doing a table scan, locking each row as it scans through each row. In ReadCommitted isolation level, locks are acquired through grabbing a row, checking it, releasing it, grab the next, and so on. (commonly referred to as "crabbing" through the rows, for the mental image of a crab walking sideways.)

That would make sense, BUT only if it where implemented consistently.
I found out that using the same WHERE statement (that blocked) but updating a different value is allowed, example:

This qeury will give me a "Lock timeout" :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2';

But this query will work :
UPDATE gwseqnumber SET findhighestnumber = 'Y' WHERE name = 'REC_2';

Updates should always be done using a unique index for maximum concurrency.

What about updates that need to update a value of a whole group of records (but not the one that is locked), it is impossible to use the unique index then. Unless you do a SELECT first or use a sub-select in your Update.

If you have fetched the row for editing, why not fetch the primary key as well and use this as the key for the update?

The second process did not fetch the row for editing, it just wants to update the record (the one that is not locked). It will continue to retry until it can, but it should not need to wait for a lock on a different record.

The reason why you get the blocking here but not with the SELECT, is that since the UPDATE is crabbing using update locks, an Update lock (which signals that the process might update the row.) (If you look at the lock compatibility chart that I mentioned yesterday (searching for shared lock mode will get you there) it shows what locks are compatible) Update locks are compatible with Shared locks, but not update locks. Hence reader can read the row, but not update it.

I understand the whole concept of Shared-Locks, Update-Locks and Exclusive-Locks, i have been programming with databases for many years now and run into many weird database 'features'. But when a lock is applied to one record, it should not block other records (unless using Page or Table locking), especially not inconsistently, allowing some queries to update but blocking others.

You should note that what you are doing is not really an effective pessimistic locking mechanism, even if you get the locking worked out. All this does is force any other writer to wait. But if three people read the row, and then all three go to update the row, they will all wait and eventually succeed or lock timeout. And if they lock the row for editing and their fetch was before the other writer made the change, you can overwrite changes. (at which time you need to use optimistic locking)

We only implement the pessimistic lock for when user manually want to edit a record, if a lock already resides on the record they immediately get a warning and will not be able to go to the update screen. This meens that when a second user does finally get to the update screen he can see the new values before he updates them. Using optimistic locking might reduce the time a record is locked, but will not solve the problem described.

If Microsoft wanted to implement a blocking feature that would prevent software from updating records in a table where one record is locked, they should just have removed the Row Lock and Page Lock and allow just the Table Lock. The way its implemented now in SQL Server 2005 is inconsistent AND is a potential new source of Deadlocks!

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 9
>>This qeury will give me a "Lock timeout" :
UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2';

But this query will work :
UPDATE gwseqnumber SET findhighestnumber = 'Y' WHERE name = 'REC_2';
<<

Very interesting. Check the plan, then then do the second one in a REPEATABLE READ isolation transaction and looks at the locks (so we can see all of the locks that it required to do the job without releasing them.)

Best guess is that it is using a different index to find the row somehow.

But when a lock is applied to one record, it should not block other records (unless using Page or Table locking), especially not inconsistently, allowing some queries to update but blocking others.

I doubt it really is inconsistent internally (I know it looks that way). I will bet that the reason the second update doesn't get blocked is due to an index on the nextseqnr column. The plan of the two queries will tell you, but I will bet that the second update uses a different index to get the row (for some reason, which hopefully a person with deep internal query processor knowledge can give us. If they don't answer here, I will try to find out for you.)

Can you post the complete table structure and the plans to the queries? As well as the locks from the queries?

I understand the whole concept of Shared-Locks, Update-Locks and Exclusive-Locks, i have been programming with databases for many years now and run into many weird database 'features'.

I hope I didn't sound condesending. I had to go to some stuff I had written about locks to remember exactly what they were (I have a memory like an elephant with a pre-frontal lobotomy.) I am keen to help you out in any way I can. Locking and blocking is very interesting because they do a lot of tricks to avoid deadlocks and blocks that don't seem straightforward sometimes.

We only implement the pessimistic lock for when user manually want to edit a record, if a lock already resides on the record they immediately get a warning and will not be able to go to the update screen.

This is fine as long as you refresh the data set before edit. All I was trying to say was that if you let the user edit the data as they have it in cache and you just lock it, it may have changed (I am not at all familiar with how pessimistic locking works using OLEDB, since it is really discouraged (actually because of these kind of locking issues :)

[

# 10

I will bet that the reason the second update doesn't get blocked is due to an index on the nextseqnr column. The plan of the two queries will tell you, but I will bet that the second update uses a different index to get the row (for some reason, which hopefully a person with deep internal query processor knowledge can give us. If they don't answer here, I will try to find out for you.)

I have checked the plans, and found out the following.

Query 1:
UPDATE gwseqnumber SET nextseqnr = 3 WHERE name = 'REC_2'.

Query 2:
UPDATE gwseqnumber SET findhighestnumber = 'Y' WHERE name = 'REC_2';

The first query uses a table scan. While the second query uses the index “Key_2”, Wich is the index on the columns “tablename”, “administrationcode” and identifier.

I have also tried to reproduce the same on SQL Server 2000.

With SQL 2000 both queries will use index “key_2”, instead of only the second in SQL 2005. I also forced a table scan under SQL 2000, using a column without an index in the where statement.

This resulted in the same lock timeout, as suspected. So at least I’m now certain it isn’t really an SQL Server 2005 problem, but something that has always been there, but never been seen earlier in our application due to SQL 2000 using the indexes possibly more.

I have no idea why SQL Server 2005 chooses not to use the same index in the first query.

Can you post the complete table structure and the plans to the queries? As well as the locks from the queries?

The original complete structure of the table:

CREATE TABLE GWSEQNUMBER
(
NEXTSEQNR INTEGER,
TABLENAME CHAR (20),
ADMINISTRATIONCODE INTEGER,
FINDHIGHESTNUMBER CHAR (1),
DUMMY1 VARCHAR (80),
DUMMY2 VARCHAR (80),
DUMMY3 VARCHAR (80),
DUMMY4 VARCHAR (80),
DUMMY5 VARCHAR (80),
CLOSEDYN CHAR (1),
IDENTIFIER CHAR (20),
GW_INSERT_BY CHAR (20),
GW_INSERT_DATE DATETIME,
GW_INSERT_TIME SMALLDATETIME,
GW_UPDATE_BY CHAR (20),
GW_UPDATE_DATE DATETIME,
GW_UPDATE_TIME SMALLDATETIME,
GW_ROW_ID INT IDENTITY(1,1) NOT NULL
);
CREATE INDEX GWSEQNUMB_KEY_1 ON GWSEQNUMBER (NEXTSEQNR);
CREATE UNIQUE INDEX GWSEQNUMB_KEY_2 ON GWSEQNUMBER (ADMINISTRATIONCODE, TABLENAME, IDENTIFIER);
CREATE UNIQUE INDEX GWSEQNUMB_KEY_3 ON GWSEQNUMBER (TABLENAME, ADMINISTRATIONCODE, IDENTIFIER);
CREATE UNIQUE INDEX RW_GWSEQNUMBER ON GWSEQNUMBER (GW_ROW_ID);

Here are the plans for the queries on 2005. (remove the txt extention)
execution_plan_queryA_2005.sqlplan.txt
execution_plan_queryB_2005.sqlplan.txt

And here is an image of the lock, using query 2 in a repeatable read transaction.
Image

The image of the locks from Query 1 has already been posted before.

Now my questions are:
Why does SQL Server 2005 do a table scan while it could also use the index in query 1?
And is there a setting or an hint to force SQL Server to use the index instead of a table scan. I know it is possible to do a Index hint, in a select statement but this isn’t possible in an update it seems.

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 11

I am really hoping that someone else will chime in as well. As for why 2000 used the index and 2005 doesn't is a good question, but it could just be due to cost (I wasn't able to see the plans, they don't seem to be there, though the image is) differences.

You can force the index by using a FROM clause:

create table test
(
testId int
)
go
create unique index testIndex on test (testId)
go
update test
set testid = 1

update test
set testId = 1
from test with (index = testIndex)

# 12

I updated the links, our web server does not share files with that extention so I added .txt.

Using a FROM with an INDEX hint in the UPDATE query did work, the query now executes and does not wait for the other lock.

If the problem was just one query then this would solve it, but this problem could happen anytime with many different queries.
So, is there an other way to make sure that SQL Server 2005 always uses an index (if available)? Like a database or connection setting?

TheovanderStap at 2007-9-9 > top of Msdn Tech,SQL Server,Transact-SQL...
# 13

Nope. As you probably know, SQL Server uses a cost based optimizer. Most likely what has occurred is that in 2005 it was cheaper to do the query without the index.

You could add more data until it is more costly to do the table scan....

You could try using the READ_COMMITTED_SNAPSHOT database setting. It would alleviate this locking. Try searching for it: http://search.msn.com/results.aspx?q=READ_COMMITTED_SNAPSHOT and there are some good articles about it.

I didn't suggest it before because I was confused by your pessimistic locking mechanism where you want them to be able to read the data. If you fetch the data again before the edit then the READ_COMMITTED_SNAPSHOT might work for you.

SQL Server

Site Classified