I need to run a query that selects ten records. Then, based on their values and some outside information, update said records.
Unfortunately I am running into deadlocks when I do this in a multi-threaded fashion. Both threads A and B run their selects at the same time, acquiring read locks on the ten records. So when one of them tries to do an update, the other transaction is aborted.
So what I need to be able to say is "select and write-lock these ten records".
(Yea, I know serial transactions should be avoided, but this is a special case for me.)
Try applying UPDLOCK
BEGIN TRAN
SELECT * FROM table1
WITH (UPDLOCK, ROWLOCK)
WHERE col1 = 'value1'
UPDATE table1
set col1 = 'value2'
where col1 = 'value1'
COMMIT TRAN
Related
Here's my problem. Let's say I have a long running SELECT query (#1):
select * from table1 -- assume this runs for a long time
While it's running, I run another SELECT (#2) from the same table. It runs in parallel and finishes in a second:
select top 1 * from table1
So #2 is not blocked by #1.
Now, let's say I want to run #3, which is truncate and reload of table1:
begin tran
truncate table table1
insert into table1
select * from table2
commit
#3 is blocked by #1 and has to wait, which is understandable. However, it also puts TABLOCK on the table1 and #2 cannot run either. Essentially #2 becomes blocked by #1.
Question: is there a way to run #3 in a way that is not blocking other queries?
I'd like to see #3 waiting for #1, but #2 still able to run.
I tried to check for locks before running #3, but can't see any locks in sys.dm_tran_locks view. Is there another place where I could see them?
I haven't done this in a while, but you could try it like this:
begin tran
CREATE TABLE table1_new <(identical to table1, incl. indexes, keys and triggers)>
insert into table1_new
select * from table2
drop table table1
RENAME N'table1_new', N'table1', N'OBJECT'
commit
You may need to try a higher isolation level to insure that your users don't get any "Object Not Found" errors.
Ok, I found the solution. Not sure why it wasn't working for me before, but sys.dm_tran_locks view is the way to go.
Basically, I'm waiting until there are no locks on the table and then start truncate/reload.
begin tran
--wait until there are no locks on the table
while exists(
select 1 from sys.dm_tran_locks
where resource_database_id = DB_ID()
and resource_associated_entity_id = OBJECT_ID(N'dbo.table1')
)
begin
waitfor delay '00:00:05'
end
truncate table table1
insert into table1
select * from table2
commit
A colleague of mine suggested another option, which I ended up using: set LOCK_TIMEOUT before running TRUNCATE.
If it can't lock the table it simply fails. (Of course there should be some mechanism to re-run it later on)
SET LOCK_TIMEOUT 0
TRUNCATE TABLE table1
Hope it helps someone.
I came across a procedure where TRANSACTION has been kept open, here is the snippet
BEGIN TRAN
--Lot of select queries to process the business logic, lets assume 30 seconds to generate the #Par3 and #Par4 as they are having XML data
IF 1= 1
BEGIN
UPDATE Table SET Col1= 'Value' WHERE Col2=#Par1 AND Col3 = #Par4
UPDATE Table2 SET Col5= 'Value' WHERE Col2=#Par1 AND Col3 = #Par4
END
COMMIT
I would like to know if the above code will lock the tables which are in SELECT clause. I am planning to add the TRANSACTION only before the UPDATE.
Is the below code is better than the above one
BEGIN
--Lot of select queries to process the business logic, lets assume 30 seconds to generate the #Par3 and #Par4 as they are having XML data
IF 1= 1
BEGIN
BEGIN TRAN
UPDATE Table SET Col1= 'Value' WHERE Col2=#Par1 AND Col3 = #Par4
UPDATE Table2 SET Col5= 'Value' WHERE Col2=#Par1 AND Col3 = #Par4
COMMIT
END
Please let me know if it makes any difference.
The default isolation level should be READ COMMITTED. Without having READ_COMMITTED_SNAPSHOT set to ON, your select may be blocked if in another transaction some updates/deletes/inserts are done. This depends on which locks are used which depends on the data you touch.
Both statemenmts should be equal as without specifing any transaction, SQL server creates one on its own and should use the default isolation level.
BEGIN TRAN
UPDATE Table SET Col1= 'Value' WHERE Col2=#Par1 AND Col3 = #Par4
COMMIT
has no sense at all as by default SQL Server operates in autocommit mode, this means that there is no need to wrap single UPDATE statement in begin tran..commit, it will be committed automatically.
The original code opens a transaction for doing multiple updates, this mean that business logic requires all these updates to be committed or all to be rolled back if something goes wrong.
I would like to know if the above code will lock the tables which are in SELECT clause
This depends on your SELECTs and on the table structure. If your table has an index on col2 or col3 and your SELECTs do not touch the same rows or use readpast there will be no conflict.
I know that NOLOCK is default for SELECT operations. So, if I even don't write with (NOLOCK) keyword for a select query, the row won't be locked.
I couldn't find what happens if with (ROWLOCK) is not specified for UPDATE and DELETE query. Is there a difference between below queries?
UPDATE MYTABLE set COLUMNA = 'valueA';
and
UPDATE MYTABLE WITH (ROWLOCK) set COLUMNA = 'valueA';
If there is no hint, then the db engine chooses the LOCK mdoe as a function of the operation(select/modify), the level of isolation and granularity, and the possibility of escalating the granularity level. Specifying ROWLOCKX does not give 100% of the result of the fact that it will be X on a rows. In general, a very large topic for such a broad issue
Read first about Lock Modes that https://technet.microsoft.com/en-us/library/ms175519(v=sql.105).aspx
If
In statement 1 (without rowlock) the DBMS decides to lock the entire table or the page that updating record is in it. so it means while updating the row all or number of other rows in the table are locked and could not be updated or deleted.
Statement 2 (with (ROWLOCK)) suggests the DBMS to only lock the record that is being updated. But be ware that this is just a HINT and there is no guarantee that the it will be accepted by the DBMS.
So, if I even don't write with (NOLOCK) keyword for a select query, the row won't be locked.
select queries always take a lock and it is called shared lock and duration of the lock depends on your isolation level
Is there a difference between below queries?
UPDATE MYTABLE set COLUMNA = 'valueA';
and
UPDATE MYTABLE WITH (ROWLOCK) set COLUMNA = 'valueA';
Suppose your first statement affects more than 5000 locks,locks will be escalated to table ,but with rowlock ...SQLServer won't lock total table
I have TableA with Col1 as the primary key. I am running the following transaction without committing it (for test purposes).
BEGIN TRANSACTION
UPDATE TableA
SET Col3 = 0
WHERE Col2 = 'AAA'
In the meanwhile, I run the following query and see that it waits on the first transaction to complete.
SELECT *
FROM TableA
WHERE Col2 = 'BBB'
But the following query returns the results immediately:
SELECT *
FROM TableA
WHERE Col1 = '1'
So I thought that the second query might need to read rows that have exclusive locks put by the first transaction in order to select rows with Col2 = 'BBB'. That's why then I tried to index Col2 so that a table seek will not be necessary but that did not work either. Second query still waits on the first transaction.
What should be done to prevent SELECT from blocking (except the use of NOLOCK).
P.S: Transaction isolation level is "Read Committed".
This SQL (called from c#) occasionally results in a deadlock.
The server is not under much load so the approach used is to lock as much as possible.
-- Lock to prevent race-conditions when multiple instances of an application calls this SQL:
BEGIN TRANSACTION
-- Check that no one has inserted the rows in T1 before me, and that T2 is in a valid state (Test1 != null)
IF NOT EXISTS (SELECT TOP 1 1 FROM T1 WITH(HOLDLOCK, TABLOCKX) WHERE FKId IN {0}) AND
NOT EXISTS(SELECT TOP 1 1 FROM T2 WITH(HOLDLOCK, TABLOCKX) WHERE DbID IN {0} AND Test1 IS NOT NULL)
BEGIN
-- Great! Im the first - go insert the row in T1 and update T2 accordingly. Finally write a log to T3
INSERT INTO T1(FKId, Status)
SELECT DbId, {1} FROM T2 WHERE DbId IN {0};
UPDATE T2 SET LastChangedBy = {2}, LastChangedAt = GETDATE() WHERE DbId IN {0};
INSERT INTO T3 (F1, FKId, F3)
SELECT {2}, DbId, GETDATE() FROM T2 WHERE DbId IN {0} ;
END;
-- Select status on the rows so the program can evaluate what just happened
SELECT FKId, Status FROM T1 WHERE FkId IN {0};
COMMIT TRANSACTION
I believe the problem is that multiple tables needs to be locked.
I'm a bit unsure when the tables are actually xlocked - when a table is used the first time - or are all tables locked at one time at BEGIN TRANS?
Using Table Locks can increase the likelihood of getting deadlocks... Not all deadlocks are caused by out of sequence operations... Some can be caused, (as you have found) by other activity that only tries to lock a single record in the same table you are locking completely, so locking the entire table increases the probability for that conflict to occur. When using serializable isolation level, range locks are placed on index rows, which can prevent inserts/deletes by other sql operations, in a way that can cause a deadlock by two concurrent operations from the same procedure, even though they are coded to perform their ops in the same order...
In any event, to find out what exactly is causing the deadlock, set SQL Server Trace flags 1204 and 1222. These will cause detailed info to be written to the SQL Server Logs about each deadlock, including what statements were involved.
Here is a good article about how to do this.
(Don't forget to turn these flags off when you're done...)
Locks are done when you call lock or select with lock and released on commit or rollback.
You could get a dead lock if another procedure locks in T3 first and in T1 or T2 afterwards. Then two transactions are waiting for each other to get a resource, while locking what the other needs.
You could also avoid the table lock and use isolation level serializable.
The problem with locking is that you really need to look at all places you do locking at the same time, there's no way to isolate and split up the problem into many smaller ones and look at those individually.
For instance, what if some other code locks the same tables, but without it being obvious, and in the wrong order? That'll cause a deadlock.
You need to analyze the server state at the moment the deadlock is discovered to try to figure out what else is running at the moment. Only then can try to fix it.