In Sql Server, is there a way to check if a selected group of rows are locked or not?
We are attempting to update/delete a large number of records in a multi-billion row table. Since this is a popular table, there is a lot of activity in different sections of this table. Any large update/delete activity is being blocked for extended periods of time (as it is waiting to get locks on all the rows or page lock or table lock) resulting in timeouts or taking multiple days to complete the task.
So, we are changing the approach to delete small batch of rows at at time. But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
- If not, then proceed with delete/update.
- If they are locked, then move on to the next group of records.
- At end, come back to the begining and attempt to update/delete the left out ones.
Is this doable?
Thanks,
ToC
sql-server locking blocking
|
show 2 more comments
We are attempting to update/delete a large number of records in a multi-billion row table. Since this is a popular table, there is a lot of activity in different sections of this table. Any large update/delete activity is being blocked for extended periods of time (as it is waiting to get locks on all the rows or page lock or table lock) resulting in timeouts or taking multiple days to complete the task.
So, we are changing the approach to delete small batch of rows at at time. But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
- If not, then proceed with delete/update.
- If they are locked, then move on to the next group of records.
- At end, come back to the begining and attempt to update/delete the left out ones.
Is this doable?
Thanks,
ToC
sql-server locking blocking
 
 
 2
 
 
 
 
 
 Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
 
 – Sean Gallardy
 May 21 '15 at 14:51
 
 
 
 
 
 
 
 
 
 
 @SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
 
 – ToC
 May 21 '15 at 14:54
 
 
 
 
 
 3
 
 
 
 
 
 You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
 
 – Geoff Patterson
 May 21 '15 at 15:47
 
 
 
 
 
 
 
 
 
 
 @gpatterson Interesting approach. I'll try this too.
 
 – ToC
 May 21 '15 at 16:30
 
 
 
 
 
 2
 
 
 
 
 
 To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
 
 – Sean Gallardy
 May 21 '15 at 17:21
 
 
 
|
show 2 more comments
We are attempting to update/delete a large number of records in a multi-billion row table. Since this is a popular table, there is a lot of activity in different sections of this table. Any large update/delete activity is being blocked for extended periods of time (as it is waiting to get locks on all the rows or page lock or table lock) resulting in timeouts or taking multiple days to complete the task.
So, we are changing the approach to delete small batch of rows at at time. But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
- If not, then proceed with delete/update.
- If they are locked, then move on to the next group of records.
- At end, come back to the begining and attempt to update/delete the left out ones.
Is this doable?
Thanks,
ToC
sql-server locking blocking
We are attempting to update/delete a large number of records in a multi-billion row table. Since this is a popular table, there is a lot of activity in different sections of this table. Any large update/delete activity is being blocked for extended periods of time (as it is waiting to get locks on all the rows or page lock or table lock) resulting in timeouts or taking multiple days to complete the task.
So, we are changing the approach to delete small batch of rows at at time. But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
- If not, then proceed with delete/update.
- If they are locked, then move on to the next group of records.
- At end, come back to the begining and attempt to update/delete the left out ones.
Is this doable?
Thanks,
ToC
sql-server locking blocking
sql-server locking blocking
asked May 21 '15 at 14:29
ToCToC
332314
332314
 
 
 2
 
 
 
 
 
 Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
 
 – Sean Gallardy
 May 21 '15 at 14:51
 
 
 
 
 
 
 
 
 
 
 @SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
 
 – ToC
 May 21 '15 at 14:54
 
 
 
 
 
 3
 
 
 
 
 
 You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
 
 – Geoff Patterson
 May 21 '15 at 15:47
 
 
 
 
 
 
 
 
 
 
 @gpatterson Interesting approach. I'll try this too.
 
 – ToC
 May 21 '15 at 16:30
 
 
 
 
 
 2
 
 
 
 
 
 To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
 
 – Sean Gallardy
 May 21 '15 at 17:21
 
 
 
|
show 2 more comments
 
 
 2
 
 
 
 
 
 Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
 
 – Sean Gallardy
 May 21 '15 at 14:51
 
 
 
 
 
 
 
 
 
 
 @SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
 
 – ToC
 May 21 '15 at 14:54
 
 
 
 
 
 3
 
 
 
 
 
 You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
 
 – Geoff Patterson
 May 21 '15 at 15:47
 
 
 
 
 
 
 
 
 
 
 @gpatterson Interesting approach. I'll try this too.
 
 – ToC
 May 21 '15 at 16:30
 
 
 
 
 
 2
 
 
 
 
 
 To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
 
 – Sean Gallardy
 May 21 '15 at 17:21
 
 
 
2
2
Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
– Sean Gallardy
May 21 '15 at 14:51
Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
– Sean Gallardy
May 21 '15 at 14:51
@SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
– ToC
May 21 '15 at 14:54
@SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
– ToC
May 21 '15 at 14:54
3
3
You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
– Geoff Patterson
May 21 '15 at 15:47
You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
– Geoff Patterson
May 21 '15 at 15:47
@gpatterson Interesting approach. I'll try this too.
– ToC
May 21 '15 at 16:30
@gpatterson Interesting approach. I'll try this too.
– ToC
May 21 '15 at 16:30
2
2
To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
– Sean Gallardy
May 21 '15 at 17:21
To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
– Sean Gallardy
May 21 '15 at 17:21
|
show 2 more comments
                                6 Answers
                            6
                        
active
oldest
votes
So, we are changing the approach to delete small batch of rows at at time.
This is a really good idea to delete in small careful batches or chunks. I would add a small waitfor delay '00:00:05' and depending on the recovery model of the database - if FULL, then do a log backup and if SIMPLE then do a manual CHECKPOINT to avoid bloating of transaction log - between batches.
But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
What you are telling is not entirely possible out of box (keeping in mind your 3 bullet points). If the above suggestion - small batches + waitfor delay does not work (provided you do proper testing), then you can use the query HINT.
Do not use NOLOCK - see kb/308886, SQL Server Read-Consistency Problems by Itzik Ben-Gan, Putting NOLOCK everywhere - By Aaron Bertrand and SQL Server NOLOCK Hint & other poor ideas.
READPAST hint will help in your scenario. The gist of READPAST hint is - if there is a row level lock then SQL server wont read it.
Specifies that the Database Engine not read rows that are locked by other transactions. When
READPASTis specified, row-level locks are skipped. That is, the Database Engine skips past the rows instead of blocking the current transaction until the locks are released.
During my limited testing, I found really good throughput when using DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK) and setting the query session isolation level to READ COMMITTED using SET TRANSACTION ISOLATION LEVEL READ COMMITTED which is default isolation level anyway.
add a comment |
If I understand the request correctly, the goal is to delete batches of rows, while at the same time, DML operations are occurring on rows throughout the table. The goal is to delete a batch; however, if any underlying rows contained within the range defined by said batch are locked, then we must skip that batch and move to the next batch. We must then return to any batches that were not previously deleted and retry our original delete logic. We must repeat this cycle until all required batches of rows are deleted.
As has been mentioned, it is reasonable to use a READPAST hint and the READ COMMITTED (default) isolation level, in order to skip past ranges that may contain blocked rows. I will go a step further and recommend using the SERIALIZABLE isolation level and nibbling deletes.
SQL Server uses Key-Range locks to protect a range of rows implicitly included in a record set being read by a Transact-SQL statement while using the serializable transaction isolation level...find more here:
https://technet.microsoft.com/en-US/library/ms191272(v=SQL.105).aspx
With nibbling deletes, our goal is to isolate a range of rows and ensure that no changes will occur to those rows while we are deleting them, that is to say, we do not want phantom reads or insertions. The serializable isolation level is meant to solve this problem.
Before I demonstrate my solution, I would like to add that neither am I recommending switching your database's default isolation level to SERIALIZABLE nor am I recommending that my solution is the best. I merely wish to present it and see where we can go from here.
A few house-keeping notes:
- The SQL Server version that I am using is Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- My test database is using the FULL recovery model
To begin my experiment, I will set up a test database, a sample table, and I will fill the table with 2,000,000 rows.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000
At this point, we will need one or more indexes upon which the locking mechanisms of the SERIALIZABLE isolation level can act.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO
Now, let us check to see that our 2,000,000 rows were created
SELECT
    COUNT(*)
FROM
    tbl;

So, we have our database, table, indexes, and rows. So, let us set up the experiment for nibbling deletes. First, we must decide how best to create a typical nibbling delete mechanism.
DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION
        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );
        SET @RowCount = ROWCOUNT_BIG();
    COMMIT TRANSACTION;
    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;
END;
As you can see, I placed the explicit transaction inside the while loop. If you would like to limit log flushes, then feel free to place it outside the loop. Furthermore, since we are in the FULL recovery model, you may wish to create transaction log backups more often while running your nibbling delete operations, in order to ensure that your transaction log can be prevented from growing outrageously.
So, I have a couple goals with this setup. First, I want my key-range locks; so, I try to keep the batches as small as possible. I also do not want to impact negatively the concurrency on my "gigantic" table; so, I want to take my locks and leave them as fast as I can. So, I recommend that you make your batch sizes small.
Now, I want to provide a very short example of this deletion routine in action. We must open a new window within SSMS and delete one row from our table. I will do this within an implicit transaction using the default READ COMMITTED isolation level.
DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;
Was this row actually deleted?
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;
Yes, it was deleted.

Now, in order to see our locks, let us open a new window within SSMS and add a code snippet or two. I am using Adam Mechanic's sp_whoisactive, which can be found here: sp_whoisactive
SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Now, we are ready to begin. In a new SSMS window, let us begin an explicit transaction that will attempt to re-insert the one row that we deleted. At the same time, we will fire off our nibbling delete operation.
The insert code:
BEGIN TRANSACTION
    SET IDENTITY_INSERT dbo.tbl ON;
    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );
    SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Let us kick off both operations beginning with the insert and followed by our deletes. We can see the key-range locks and exclusive locks.

The insert generated these locks:

The nibbling delete/select is holding these locks:

Our insert is blocking our delete as expected:

Now, let us commit the insert transaction and see what is up.

And as expected, all transactions complete. Now, we must check to see whether the insert was a phantom or whether the delete operation removed it as well.
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;
In fact, the insert was deleted; so, no phantom insert was allowed.

So, in conclusion, I think the true intention of this exercise is not to try and track every single row, page, or table-level lock and try to determine whether an element of a batch is locked and would therefore require our delete operation to wait. That may have been the intent of the questioners; however, that task is herculean and basically impractical if not impossible. The real goal is to ensure that no unwanted phenomena arise once we have isolated the range of our batch with locks of our own and then precede to delete the batch. The SERIALIZABLE isolation level achieves this objective. The key is to keep your nibbles small, your transaction log under control, and eliminate unwanted phenomena.
If you want speed, then don't build gigantically deep tables that cannot be partitioned and therefore be unable to use partition switching for the fastest results. The key to speed is partitioning and parallelism; the key to suffering is nibbles and live-locking.
Please let me know what you think.
I created some further examples of the SERIALIZABLE isolation level in action. They should be available at the links below.
Delete Operation
Insert Operation
Equality Operations - Key-Range Locks on Next Key Values
Equality Operations - Singleton Fetch of Existent Data
Equality Operations - Singleton Fetch of Nonexistent Data
Inequality Operations - Key-Range Locks on Range and Next Key Values
add a comment |
Summarizing other approaches originally offered in comments to the question.
 - Use - NOWAITif the desired behaviour is to fail the whole chunk as soon as an incompatible lock is encountered.
 
 - From the - NOWAITdocumentation:
 
 
 - Instructs the Database Engine to return a message as soon as a lock is encountered on the table. - NOWAITis equivalent to specifying- SET LOCK_TIMEOUT 0for a specific table. The- NOWAIThint does not work when the- TABLOCKhint is also included. To terminate a query without waiting when using the- TABLOCKhint, preface the query with- SETLOCK_TIMEOUT 0;instead.
 
 
 - Use - SET LOCK_TIMEOUTto achieve a similar outcome, but with a configurable timeout:
 
 - From the - SET LOCK_TIMEOUTdocumentation
 
 
 - Specifies the number of milliseconds a statement waits for a lock to be released. 
 
 - When a wait for a lock exceeds the timeout value, an error is returned. A value of 0 means to not wait at all and return a message as soon as a lock is encountered. 
 
 
add a comment |
To assume we have 2 parallels queries:
connect/session 1: will locked the row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
connect/session 2: will ignore locked row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR connect/session 2: will throw exception
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
Try filtering on something like this - it can get complicated if you want to get really, really specific. Look in BOL for the description of sys.dm_tran_locks
SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
 
 
 
 
 
 
 
 just curious - why the downvote?
 
 – rottengeek
 Sep 11 '15 at 20:17
 
 
 
add a comment |
You can use NoLOCK while you are deleting and if the rows are locked, they wont be deleted. Its not ideal but may do the trick for you.
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
 
 
 7
 
 
 
 
 
 If I try that on my local machine I get- Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005
 
 – Tom V
 Aug 25 '15 at 13:33
 
 
 
 
 
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "182"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f102160%2fin-sql-server-is-there-a-way-to-check-if-a-selected-group-of-rows-are-locked-or%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
                                6 Answers
                            6
                        
active
oldest
votes
                                6 Answers
                            6
                        
active
oldest
votes
active
oldest
votes
active
oldest
votes
So, we are changing the approach to delete small batch of rows at at time.
This is a really good idea to delete in small careful batches or chunks. I would add a small waitfor delay '00:00:05' and depending on the recovery model of the database - if FULL, then do a log backup and if SIMPLE then do a manual CHECKPOINT to avoid bloating of transaction log - between batches.
But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
What you are telling is not entirely possible out of box (keeping in mind your 3 bullet points). If the above suggestion - small batches + waitfor delay does not work (provided you do proper testing), then you can use the query HINT.
Do not use NOLOCK - see kb/308886, SQL Server Read-Consistency Problems by Itzik Ben-Gan, Putting NOLOCK everywhere - By Aaron Bertrand and SQL Server NOLOCK Hint & other poor ideas.
READPAST hint will help in your scenario. The gist of READPAST hint is - if there is a row level lock then SQL server wont read it.
Specifies that the Database Engine not read rows that are locked by other transactions. When
READPASTis specified, row-level locks are skipped. That is, the Database Engine skips past the rows instead of blocking the current transaction until the locks are released.
During my limited testing, I found really good throughput when using DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK) and setting the query session isolation level to READ COMMITTED using SET TRANSACTION ISOLATION LEVEL READ COMMITTED which is default isolation level anyway.
add a comment |
So, we are changing the approach to delete small batch of rows at at time.
This is a really good idea to delete in small careful batches or chunks. I would add a small waitfor delay '00:00:05' and depending on the recovery model of the database - if FULL, then do a log backup and if SIMPLE then do a manual CHECKPOINT to avoid bloating of transaction log - between batches.
But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
What you are telling is not entirely possible out of box (keeping in mind your 3 bullet points). If the above suggestion - small batches + waitfor delay does not work (provided you do proper testing), then you can use the query HINT.
Do not use NOLOCK - see kb/308886, SQL Server Read-Consistency Problems by Itzik Ben-Gan, Putting NOLOCK everywhere - By Aaron Bertrand and SQL Server NOLOCK Hint & other poor ideas.
READPAST hint will help in your scenario. The gist of READPAST hint is - if there is a row level lock then SQL server wont read it.
Specifies that the Database Engine not read rows that are locked by other transactions. When
READPASTis specified, row-level locks are skipped. That is, the Database Engine skips past the rows instead of blocking the current transaction until the locks are released.
During my limited testing, I found really good throughput when using DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK) and setting the query session isolation level to READ COMMITTED using SET TRANSACTION ISOLATION LEVEL READ COMMITTED which is default isolation level anyway.
add a comment |
So, we are changing the approach to delete small batch of rows at at time.
This is a really good idea to delete in small careful batches or chunks. I would add a small waitfor delay '00:00:05' and depending on the recovery model of the database - if FULL, then do a log backup and if SIMPLE then do a manual CHECKPOINT to avoid bloating of transaction log - between batches.
But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
What you are telling is not entirely possible out of box (keeping in mind your 3 bullet points). If the above suggestion - small batches + waitfor delay does not work (provided you do proper testing), then you can use the query HINT.
Do not use NOLOCK - see kb/308886, SQL Server Read-Consistency Problems by Itzik Ben-Gan, Putting NOLOCK everywhere - By Aaron Bertrand and SQL Server NOLOCK Hint & other poor ideas.
READPAST hint will help in your scenario. The gist of READPAST hint is - if there is a row level lock then SQL server wont read it.
Specifies that the Database Engine not read rows that are locked by other transactions. When
READPASTis specified, row-level locks are skipped. That is, the Database Engine skips past the rows instead of blocking the current transaction until the locks are released.
During my limited testing, I found really good throughput when using DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK) and setting the query session isolation level to READ COMMITTED using SET TRANSACTION ISOLATION LEVEL READ COMMITTED which is default isolation level anyway.
So, we are changing the approach to delete small batch of rows at at time.
This is a really good idea to delete in small careful batches or chunks. I would add a small waitfor delay '00:00:05' and depending on the recovery model of the database - if FULL, then do a log backup and if SIMPLE then do a manual CHECKPOINT to avoid bloating of transaction log - between batches.
But we want to check if the selected (let's say 100 or 1000 or 2000 rows) are currently locked by a different process or not.
What you are telling is not entirely possible out of box (keeping in mind your 3 bullet points). If the above suggestion - small batches + waitfor delay does not work (provided you do proper testing), then you can use the query HINT.
Do not use NOLOCK - see kb/308886, SQL Server Read-Consistency Problems by Itzik Ben-Gan, Putting NOLOCK everywhere - By Aaron Bertrand and SQL Server NOLOCK Hint & other poor ideas.
READPAST hint will help in your scenario. The gist of READPAST hint is - if there is a row level lock then SQL server wont read it.
Specifies that the Database Engine not read rows that are locked by other transactions. When
READPASTis specified, row-level locks are skipped. That is, the Database Engine skips past the rows instead of blocking the current transaction until the locks are released.
During my limited testing, I found really good throughput when using DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK) and setting the query session isolation level to READ COMMITTED using SET TRANSACTION ISOLATION LEVEL READ COMMITTED which is default isolation level anyway.
edited Aug 28 '15 at 17:57
answered Aug 25 '15 at 17:24


KinKin
54.1k481192
54.1k481192
add a comment |
add a comment |
If I understand the request correctly, the goal is to delete batches of rows, while at the same time, DML operations are occurring on rows throughout the table. The goal is to delete a batch; however, if any underlying rows contained within the range defined by said batch are locked, then we must skip that batch and move to the next batch. We must then return to any batches that were not previously deleted and retry our original delete logic. We must repeat this cycle until all required batches of rows are deleted.
As has been mentioned, it is reasonable to use a READPAST hint and the READ COMMITTED (default) isolation level, in order to skip past ranges that may contain blocked rows. I will go a step further and recommend using the SERIALIZABLE isolation level and nibbling deletes.
SQL Server uses Key-Range locks to protect a range of rows implicitly included in a record set being read by a Transact-SQL statement while using the serializable transaction isolation level...find more here:
https://technet.microsoft.com/en-US/library/ms191272(v=SQL.105).aspx
With nibbling deletes, our goal is to isolate a range of rows and ensure that no changes will occur to those rows while we are deleting them, that is to say, we do not want phantom reads or insertions. The serializable isolation level is meant to solve this problem.
Before I demonstrate my solution, I would like to add that neither am I recommending switching your database's default isolation level to SERIALIZABLE nor am I recommending that my solution is the best. I merely wish to present it and see where we can go from here.
A few house-keeping notes:
- The SQL Server version that I am using is Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- My test database is using the FULL recovery model
To begin my experiment, I will set up a test database, a sample table, and I will fill the table with 2,000,000 rows.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000
At this point, we will need one or more indexes upon which the locking mechanisms of the SERIALIZABLE isolation level can act.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO
Now, let us check to see that our 2,000,000 rows were created
SELECT
    COUNT(*)
FROM
    tbl;

So, we have our database, table, indexes, and rows. So, let us set up the experiment for nibbling deletes. First, we must decide how best to create a typical nibbling delete mechanism.
DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION
        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );
        SET @RowCount = ROWCOUNT_BIG();
    COMMIT TRANSACTION;
    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;
END;
As you can see, I placed the explicit transaction inside the while loop. If you would like to limit log flushes, then feel free to place it outside the loop. Furthermore, since we are in the FULL recovery model, you may wish to create transaction log backups more often while running your nibbling delete operations, in order to ensure that your transaction log can be prevented from growing outrageously.
So, I have a couple goals with this setup. First, I want my key-range locks; so, I try to keep the batches as small as possible. I also do not want to impact negatively the concurrency on my "gigantic" table; so, I want to take my locks and leave them as fast as I can. So, I recommend that you make your batch sizes small.
Now, I want to provide a very short example of this deletion routine in action. We must open a new window within SSMS and delete one row from our table. I will do this within an implicit transaction using the default READ COMMITTED isolation level.
DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;
Was this row actually deleted?
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;
Yes, it was deleted.

Now, in order to see our locks, let us open a new window within SSMS and add a code snippet or two. I am using Adam Mechanic's sp_whoisactive, which can be found here: sp_whoisactive
SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Now, we are ready to begin. In a new SSMS window, let us begin an explicit transaction that will attempt to re-insert the one row that we deleted. At the same time, we will fire off our nibbling delete operation.
The insert code:
BEGIN TRANSACTION
    SET IDENTITY_INSERT dbo.tbl ON;
    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );
    SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Let us kick off both operations beginning with the insert and followed by our deletes. We can see the key-range locks and exclusive locks.

The insert generated these locks:

The nibbling delete/select is holding these locks:

Our insert is blocking our delete as expected:

Now, let us commit the insert transaction and see what is up.

And as expected, all transactions complete. Now, we must check to see whether the insert was a phantom or whether the delete operation removed it as well.
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;
In fact, the insert was deleted; so, no phantom insert was allowed.

So, in conclusion, I think the true intention of this exercise is not to try and track every single row, page, or table-level lock and try to determine whether an element of a batch is locked and would therefore require our delete operation to wait. That may have been the intent of the questioners; however, that task is herculean and basically impractical if not impossible. The real goal is to ensure that no unwanted phenomena arise once we have isolated the range of our batch with locks of our own and then precede to delete the batch. The SERIALIZABLE isolation level achieves this objective. The key is to keep your nibbles small, your transaction log under control, and eliminate unwanted phenomena.
If you want speed, then don't build gigantically deep tables that cannot be partitioned and therefore be unable to use partition switching for the fastest results. The key to speed is partitioning and parallelism; the key to suffering is nibbles and live-locking.
Please let me know what you think.
I created some further examples of the SERIALIZABLE isolation level in action. They should be available at the links below.
Delete Operation
Insert Operation
Equality Operations - Key-Range Locks on Next Key Values
Equality Operations - Singleton Fetch of Existent Data
Equality Operations - Singleton Fetch of Nonexistent Data
Inequality Operations - Key-Range Locks on Range and Next Key Values
add a comment |
If I understand the request correctly, the goal is to delete batches of rows, while at the same time, DML operations are occurring on rows throughout the table. The goal is to delete a batch; however, if any underlying rows contained within the range defined by said batch are locked, then we must skip that batch and move to the next batch. We must then return to any batches that were not previously deleted and retry our original delete logic. We must repeat this cycle until all required batches of rows are deleted.
As has been mentioned, it is reasonable to use a READPAST hint and the READ COMMITTED (default) isolation level, in order to skip past ranges that may contain blocked rows. I will go a step further and recommend using the SERIALIZABLE isolation level and nibbling deletes.
SQL Server uses Key-Range locks to protect a range of rows implicitly included in a record set being read by a Transact-SQL statement while using the serializable transaction isolation level...find more here:
https://technet.microsoft.com/en-US/library/ms191272(v=SQL.105).aspx
With nibbling deletes, our goal is to isolate a range of rows and ensure that no changes will occur to those rows while we are deleting them, that is to say, we do not want phantom reads or insertions. The serializable isolation level is meant to solve this problem.
Before I demonstrate my solution, I would like to add that neither am I recommending switching your database's default isolation level to SERIALIZABLE nor am I recommending that my solution is the best. I merely wish to present it and see where we can go from here.
A few house-keeping notes:
- The SQL Server version that I am using is Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- My test database is using the FULL recovery model
To begin my experiment, I will set up a test database, a sample table, and I will fill the table with 2,000,000 rows.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000
At this point, we will need one or more indexes upon which the locking mechanisms of the SERIALIZABLE isolation level can act.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO
Now, let us check to see that our 2,000,000 rows were created
SELECT
    COUNT(*)
FROM
    tbl;

So, we have our database, table, indexes, and rows. So, let us set up the experiment for nibbling deletes. First, we must decide how best to create a typical nibbling delete mechanism.
DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION
        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );
        SET @RowCount = ROWCOUNT_BIG();
    COMMIT TRANSACTION;
    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;
END;
As you can see, I placed the explicit transaction inside the while loop. If you would like to limit log flushes, then feel free to place it outside the loop. Furthermore, since we are in the FULL recovery model, you may wish to create transaction log backups more often while running your nibbling delete operations, in order to ensure that your transaction log can be prevented from growing outrageously.
So, I have a couple goals with this setup. First, I want my key-range locks; so, I try to keep the batches as small as possible. I also do not want to impact negatively the concurrency on my "gigantic" table; so, I want to take my locks and leave them as fast as I can. So, I recommend that you make your batch sizes small.
Now, I want to provide a very short example of this deletion routine in action. We must open a new window within SSMS and delete one row from our table. I will do this within an implicit transaction using the default READ COMMITTED isolation level.
DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;
Was this row actually deleted?
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;
Yes, it was deleted.

Now, in order to see our locks, let us open a new window within SSMS and add a code snippet or two. I am using Adam Mechanic's sp_whoisactive, which can be found here: sp_whoisactive
SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Now, we are ready to begin. In a new SSMS window, let us begin an explicit transaction that will attempt to re-insert the one row that we deleted. At the same time, we will fire off our nibbling delete operation.
The insert code:
BEGIN TRANSACTION
    SET IDENTITY_INSERT dbo.tbl ON;
    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );
    SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Let us kick off both operations beginning with the insert and followed by our deletes. We can see the key-range locks and exclusive locks.

The insert generated these locks:

The nibbling delete/select is holding these locks:

Our insert is blocking our delete as expected:

Now, let us commit the insert transaction and see what is up.

And as expected, all transactions complete. Now, we must check to see whether the insert was a phantom or whether the delete operation removed it as well.
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;
In fact, the insert was deleted; so, no phantom insert was allowed.

So, in conclusion, I think the true intention of this exercise is not to try and track every single row, page, or table-level lock and try to determine whether an element of a batch is locked and would therefore require our delete operation to wait. That may have been the intent of the questioners; however, that task is herculean and basically impractical if not impossible. The real goal is to ensure that no unwanted phenomena arise once we have isolated the range of our batch with locks of our own and then precede to delete the batch. The SERIALIZABLE isolation level achieves this objective. The key is to keep your nibbles small, your transaction log under control, and eliminate unwanted phenomena.
If you want speed, then don't build gigantically deep tables that cannot be partitioned and therefore be unable to use partition switching for the fastest results. The key to speed is partitioning and parallelism; the key to suffering is nibbles and live-locking.
Please let me know what you think.
I created some further examples of the SERIALIZABLE isolation level in action. They should be available at the links below.
Delete Operation
Insert Operation
Equality Operations - Key-Range Locks on Next Key Values
Equality Operations - Singleton Fetch of Existent Data
Equality Operations - Singleton Fetch of Nonexistent Data
Inequality Operations - Key-Range Locks on Range and Next Key Values
add a comment |
If I understand the request correctly, the goal is to delete batches of rows, while at the same time, DML operations are occurring on rows throughout the table. The goal is to delete a batch; however, if any underlying rows contained within the range defined by said batch are locked, then we must skip that batch and move to the next batch. We must then return to any batches that were not previously deleted and retry our original delete logic. We must repeat this cycle until all required batches of rows are deleted.
As has been mentioned, it is reasonable to use a READPAST hint and the READ COMMITTED (default) isolation level, in order to skip past ranges that may contain blocked rows. I will go a step further and recommend using the SERIALIZABLE isolation level and nibbling deletes.
SQL Server uses Key-Range locks to protect a range of rows implicitly included in a record set being read by a Transact-SQL statement while using the serializable transaction isolation level...find more here:
https://technet.microsoft.com/en-US/library/ms191272(v=SQL.105).aspx
With nibbling deletes, our goal is to isolate a range of rows and ensure that no changes will occur to those rows while we are deleting them, that is to say, we do not want phantom reads or insertions. The serializable isolation level is meant to solve this problem.
Before I demonstrate my solution, I would like to add that neither am I recommending switching your database's default isolation level to SERIALIZABLE nor am I recommending that my solution is the best. I merely wish to present it and see where we can go from here.
A few house-keeping notes:
- The SQL Server version that I am using is Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- My test database is using the FULL recovery model
To begin my experiment, I will set up a test database, a sample table, and I will fill the table with 2,000,000 rows.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000
At this point, we will need one or more indexes upon which the locking mechanisms of the SERIALIZABLE isolation level can act.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO
Now, let us check to see that our 2,000,000 rows were created
SELECT
    COUNT(*)
FROM
    tbl;

So, we have our database, table, indexes, and rows. So, let us set up the experiment for nibbling deletes. First, we must decide how best to create a typical nibbling delete mechanism.
DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION
        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );
        SET @RowCount = ROWCOUNT_BIG();
    COMMIT TRANSACTION;
    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;
END;
As you can see, I placed the explicit transaction inside the while loop. If you would like to limit log flushes, then feel free to place it outside the loop. Furthermore, since we are in the FULL recovery model, you may wish to create transaction log backups more often while running your nibbling delete operations, in order to ensure that your transaction log can be prevented from growing outrageously.
So, I have a couple goals with this setup. First, I want my key-range locks; so, I try to keep the batches as small as possible. I also do not want to impact negatively the concurrency on my "gigantic" table; so, I want to take my locks and leave them as fast as I can. So, I recommend that you make your batch sizes small.
Now, I want to provide a very short example of this deletion routine in action. We must open a new window within SSMS and delete one row from our table. I will do this within an implicit transaction using the default READ COMMITTED isolation level.
DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;
Was this row actually deleted?
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;
Yes, it was deleted.

Now, in order to see our locks, let us open a new window within SSMS and add a code snippet or two. I am using Adam Mechanic's sp_whoisactive, which can be found here: sp_whoisactive
SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Now, we are ready to begin. In a new SSMS window, let us begin an explicit transaction that will attempt to re-insert the one row that we deleted. At the same time, we will fire off our nibbling delete operation.
The insert code:
BEGIN TRANSACTION
    SET IDENTITY_INSERT dbo.tbl ON;
    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );
    SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Let us kick off both operations beginning with the insert and followed by our deletes. We can see the key-range locks and exclusive locks.

The insert generated these locks:

The nibbling delete/select is holding these locks:

Our insert is blocking our delete as expected:

Now, let us commit the insert transaction and see what is up.

And as expected, all transactions complete. Now, we must check to see whether the insert was a phantom or whether the delete operation removed it as well.
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;
In fact, the insert was deleted; so, no phantom insert was allowed.

So, in conclusion, I think the true intention of this exercise is not to try and track every single row, page, or table-level lock and try to determine whether an element of a batch is locked and would therefore require our delete operation to wait. That may have been the intent of the questioners; however, that task is herculean and basically impractical if not impossible. The real goal is to ensure that no unwanted phenomena arise once we have isolated the range of our batch with locks of our own and then precede to delete the batch. The SERIALIZABLE isolation level achieves this objective. The key is to keep your nibbles small, your transaction log under control, and eliminate unwanted phenomena.
If you want speed, then don't build gigantically deep tables that cannot be partitioned and therefore be unable to use partition switching for the fastest results. The key to speed is partitioning and parallelism; the key to suffering is nibbles and live-locking.
Please let me know what you think.
I created some further examples of the SERIALIZABLE isolation level in action. They should be available at the links below.
Delete Operation
Insert Operation
Equality Operations - Key-Range Locks on Next Key Values
Equality Operations - Singleton Fetch of Existent Data
Equality Operations - Singleton Fetch of Nonexistent Data
Inequality Operations - Key-Range Locks on Range and Next Key Values
If I understand the request correctly, the goal is to delete batches of rows, while at the same time, DML operations are occurring on rows throughout the table. The goal is to delete a batch; however, if any underlying rows contained within the range defined by said batch are locked, then we must skip that batch and move to the next batch. We must then return to any batches that were not previously deleted and retry our original delete logic. We must repeat this cycle until all required batches of rows are deleted.
As has been mentioned, it is reasonable to use a READPAST hint and the READ COMMITTED (default) isolation level, in order to skip past ranges that may contain blocked rows. I will go a step further and recommend using the SERIALIZABLE isolation level and nibbling deletes.
SQL Server uses Key-Range locks to protect a range of rows implicitly included in a record set being read by a Transact-SQL statement while using the serializable transaction isolation level...find more here:
https://technet.microsoft.com/en-US/library/ms191272(v=SQL.105).aspx
With nibbling deletes, our goal is to isolate a range of rows and ensure that no changes will occur to those rows while we are deleting them, that is to say, we do not want phantom reads or insertions. The serializable isolation level is meant to solve this problem.
Before I demonstrate my solution, I would like to add that neither am I recommending switching your database's default isolation level to SERIALIZABLE nor am I recommending that my solution is the best. I merely wish to present it and see where we can go from here.
A few house-keeping notes:
- The SQL Server version that I am using is Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- My test database is using the FULL recovery model
To begin my experiment, I will set up a test database, a sample table, and I will fill the table with 2,000,000 rows.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000
At this point, we will need one or more indexes upon which the locking mechanisms of the SERIALIZABLE isolation level can act.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO
Now, let us check to see that our 2,000,000 rows were created
SELECT
    COUNT(*)
FROM
    tbl;

So, we have our database, table, indexes, and rows. So, let us set up the experiment for nibbling deletes. First, we must decide how best to create a typical nibbling delete mechanism.
DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION
        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );
        SET @RowCount = ROWCOUNT_BIG();
    COMMIT TRANSACTION;
    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;
END;
As you can see, I placed the explicit transaction inside the while loop. If you would like to limit log flushes, then feel free to place it outside the loop. Furthermore, since we are in the FULL recovery model, you may wish to create transaction log backups more often while running your nibbling delete operations, in order to ensure that your transaction log can be prevented from growing outrageously.
So, I have a couple goals with this setup. First, I want my key-range locks; so, I try to keep the batches as small as possible. I also do not want to impact negatively the concurrency on my "gigantic" table; so, I want to take my locks and leave them as fast as I can. So, I recommend that you make your batch sizes small.
Now, I want to provide a very short example of this deletion routine in action. We must open a new window within SSMS and delete one row from our table. I will do this within an implicit transaction using the default READ COMMITTED isolation level.
DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;
Was this row actually deleted?
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;
Yes, it was deleted.

Now, in order to see our locks, let us open a new window within SSMS and add a code snippet or two. I am using Adam Mechanic's sp_whoisactive, which can be found here: sp_whoisactive
SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Now, we are ready to begin. In a new SSMS window, let us begin an explicit transaction that will attempt to re-insert the one row that we deleted. At the same time, we will fire off our nibbling delete operation.
The insert code:
BEGIN TRANSACTION
    SET IDENTITY_INSERT dbo.tbl ON;
    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );
    SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Let us kick off both operations beginning with the insert and followed by our deletes. We can see the key-range locks and exclusive locks.

The insert generated these locks:

The nibbling delete/select is holding these locks:

Our insert is blocking our delete as expected:

Now, let us commit the insert transaction and see what is up.

And as expected, all transactions complete. Now, we must check to see whether the insert was a phantom or whether the delete operation removed it as well.
SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;
In fact, the insert was deleted; so, no phantom insert was allowed.

So, in conclusion, I think the true intention of this exercise is not to try and track every single row, page, or table-level lock and try to determine whether an element of a batch is locked and would therefore require our delete operation to wait. That may have been the intent of the questioners; however, that task is herculean and basically impractical if not impossible. The real goal is to ensure that no unwanted phenomena arise once we have isolated the range of our batch with locks of our own and then precede to delete the batch. The SERIALIZABLE isolation level achieves this objective. The key is to keep your nibbles small, your transaction log under control, and eliminate unwanted phenomena.
If you want speed, then don't build gigantically deep tables that cannot be partitioned and therefore be unable to use partition switching for the fastest results. The key to speed is partitioning and parallelism; the key to suffering is nibbles and live-locking.
Please let me know what you think.
I created some further examples of the SERIALIZABLE isolation level in action. They should be available at the links below.
Delete Operation
Insert Operation
Equality Operations - Key-Range Locks on Next Key Values
Equality Operations - Singleton Fetch of Existent Data
Equality Operations - Singleton Fetch of Nonexistent Data
Inequality Operations - Key-Range Locks on Range and Next Key Values
edited Nov 13 '18 at 23:10
Community♦
1
1
answered Aug 27 '15 at 3:01


ooutwireooutwire
1,127716
1,127716
add a comment |
add a comment |
Summarizing other approaches originally offered in comments to the question.
 - Use - NOWAITif the desired behaviour is to fail the whole chunk as soon as an incompatible lock is encountered.
 
 - From the - NOWAITdocumentation:
 
 
 - Instructs the Database Engine to return a message as soon as a lock is encountered on the table. - NOWAITis equivalent to specifying- SET LOCK_TIMEOUT 0for a specific table. The- NOWAIThint does not work when the- TABLOCKhint is also included. To terminate a query without waiting when using the- TABLOCKhint, preface the query with- SETLOCK_TIMEOUT 0;instead.
 
 
 - Use - SET LOCK_TIMEOUTto achieve a similar outcome, but with a configurable timeout:
 
 - From the - SET LOCK_TIMEOUTdocumentation
 
 
 - Specifies the number of milliseconds a statement waits for a lock to be released. 
 
 - When a wait for a lock exceeds the timeout value, an error is returned. A value of 0 means to not wait at all and return a message as soon as a lock is encountered. 
 
 
add a comment |
Summarizing other approaches originally offered in comments to the question.
 - Use - NOWAITif the desired behaviour is to fail the whole chunk as soon as an incompatible lock is encountered.
 
 - From the - NOWAITdocumentation:
 
 
 - Instructs the Database Engine to return a message as soon as a lock is encountered on the table. - NOWAITis equivalent to specifying- SET LOCK_TIMEOUT 0for a specific table. The- NOWAIThint does not work when the- TABLOCKhint is also included. To terminate a query without waiting when using the- TABLOCKhint, preface the query with- SETLOCK_TIMEOUT 0;instead.
 
 
 - Use - SET LOCK_TIMEOUTto achieve a similar outcome, but with a configurable timeout:
 
 - From the - SET LOCK_TIMEOUTdocumentation
 
 
 - Specifies the number of milliseconds a statement waits for a lock to be released. 
 
 - When a wait for a lock exceeds the timeout value, an error is returned. A value of 0 means to not wait at all and return a message as soon as a lock is encountered. 
 
 
add a comment |
Summarizing other approaches originally offered in comments to the question.
 - Use - NOWAITif the desired behaviour is to fail the whole chunk as soon as an incompatible lock is encountered.
 
 - From the - NOWAITdocumentation:
 
 
 - Instructs the Database Engine to return a message as soon as a lock is encountered on the table. - NOWAITis equivalent to specifying- SET LOCK_TIMEOUT 0for a specific table. The- NOWAIThint does not work when the- TABLOCKhint is also included. To terminate a query without waiting when using the- TABLOCKhint, preface the query with- SETLOCK_TIMEOUT 0;instead.
 
 
 - Use - SET LOCK_TIMEOUTto achieve a similar outcome, but with a configurable timeout:
 
 - From the - SET LOCK_TIMEOUTdocumentation
 
 
 - Specifies the number of milliseconds a statement waits for a lock to be released. 
 
 - When a wait for a lock exceeds the timeout value, an error is returned. A value of 0 means to not wait at all and return a message as soon as a lock is encountered. 
 
 
Summarizing other approaches originally offered in comments to the question.
 - Use - NOWAITif the desired behaviour is to fail the whole chunk as soon as an incompatible lock is encountered.
 
 - From the - NOWAITdocumentation:
 
 
 - Instructs the Database Engine to return a message as soon as a lock is encountered on the table. - NOWAITis equivalent to specifying- SET LOCK_TIMEOUT 0for a specific table. The- NOWAIThint does not work when the- TABLOCKhint is also included. To terminate a query without waiting when using the- TABLOCKhint, preface the query with- SETLOCK_TIMEOUT 0;instead.
 
 
 - Use - SET LOCK_TIMEOUTto achieve a similar outcome, but with a configurable timeout:
 
 - From the - SET LOCK_TIMEOUTdocumentation
 
 
 - Specifies the number of milliseconds a statement waits for a lock to be released. 
 
 - When a wait for a lock exceeds the timeout value, an error is returned. A value of 0 means to not wait at all and return a message as soon as a lock is encountered. 
 
 
edited Apr 13 '17 at 12:42
Community♦
1
1
answered Aug 29 '15 at 11:38
Paul White♦Paul White
53.8k14286459
53.8k14286459
add a comment |
add a comment |
To assume we have 2 parallels queries:
connect/session 1: will locked the row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
connect/session 2: will ignore locked row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR connect/session 2: will throw exception
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
To assume we have 2 parallels queries:
connect/session 1: will locked the row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
connect/session 2: will ignore locked row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR connect/session 2: will throw exception
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
To assume we have 2 parallels queries:
connect/session 1: will locked the row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
connect/session 2: will ignore locked row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR connect/session 2: will throw exception
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
To assume we have 2 parallels queries:
connect/session 1: will locked the row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
connect/session 2: will ignore locked row = 777
SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777
OR connect/session 2: will throw exception
DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
answered 8 mins ago


LebnikLebnik
101
101
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
Lebnik is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
add a comment |
Try filtering on something like this - it can get complicated if you want to get really, really specific. Look in BOL for the description of sys.dm_tran_locks
SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
 
 
 
 
 
 
 
 just curious - why the downvote?
 
 – rottengeek
 Sep 11 '15 at 20:17
 
 
 
add a comment |
Try filtering on something like this - it can get complicated if you want to get really, really specific. Look in BOL for the description of sys.dm_tran_locks
SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
 
 
 
 
 
 
 
 just curious - why the downvote?
 
 – rottengeek
 Sep 11 '15 at 20:17
 
 
 
add a comment |
Try filtering on something like this - it can get complicated if you want to get really, really specific. Look in BOL for the description of sys.dm_tran_locks
SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
Try filtering on something like this - it can get complicated if you want to get really, really specific. Look in BOL for the description of sys.dm_tran_locks
SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id
edited Aug 25 '15 at 22:20
answered Aug 25 '15 at 21:03


rottengeekrottengeek
615516
615516
 
 
 
 
 
 
 
 just curious - why the downvote?
 
 – rottengeek
 Sep 11 '15 at 20:17
 
 
 
add a comment |
 
 
 
 
 
 
 
 just curious - why the downvote?
 
 – rottengeek
 Sep 11 '15 at 20:17
 
 
 
just curious - why the downvote?
– rottengeek
Sep 11 '15 at 20:17
just curious - why the downvote?
– rottengeek
Sep 11 '15 at 20:17
add a comment |
You can use NoLOCK while you are deleting and if the rows are locked, they wont be deleted. Its not ideal but may do the trick for you.
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
 
 
 7
 
 
 
 
 
 If I try that on my local machine I get- Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005
 
 – Tom V
 Aug 25 '15 at 13:33
 
 
 
 
 
add a comment |
You can use NoLOCK while you are deleting and if the rows are locked, they wont be deleted. Its not ideal but may do the trick for you.
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
 
 
 7
 
 
 
 
 
 If I try that on my local machine I get- Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005
 
 – Tom V
 Aug 25 '15 at 13:33
 
 
 
 
 
add a comment |
You can use NoLOCK while you are deleting and if the rows are locked, they wont be deleted. Its not ideal but may do the trick for you.
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
You can use NoLOCK while you are deleting and if the rows are locked, they wont be deleted. Its not ideal but may do the trick for you.
DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
answered Aug 25 '15 at 12:43
mouliinmouliin
429313
429313
 
 
 7
 
 
 
 
 
 If I try that on my local machine I get- Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005
 
 – Tom V
 Aug 25 '15 at 13:33
 
 
 
 
 
add a comment |
 
 
 7
 
 
 
 
 
 If I try that on my local machine I get- Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005
 
 – Tom V
 Aug 25 '15 at 13:33
 
 
 
 
 
7
7
If I try that on my local machine I get
Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005– Tom V
Aug 25 '15 at 13:33
If I try that on my local machine I get
Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., deprecated since 2005– Tom V
Aug 25 '15 at 13:33
add a comment |
Thanks for contributing an answer to Database Administrators Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f102160%2fin-sql-server-is-there-a-way-to-check-if-a-selected-group-of-rows-are-locked-or%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
Have you looked into READPAST as part of the delete statement or NOWAIT (to fail the whole group)? One of these may work for you. msdn.microsoft.com/en-us/library/ms187373.aspx
– Sean Gallardy
May 21 '15 at 14:51
@SeanGallardy I have not considered that idea, but now I will. But is there an easier way to check if a particular row is locked or not? Thanks.
– ToC
May 21 '15 at 14:54
3
You might also look into LOCK_TIMEOUT (msdn.microsoft.com/en-us/library/ms189470.aspx). For example, this is the way that Adam Machanic's sp_whoisactive ensures that the procedure doesn't wait for too long if it is blocked when trying to gather an execution plan. You can set a short timeout or even use a value of 0 ("0 means to not wait at all and return a message as soon as a lock is encountered.") You can combine this with a TRY/CATCH to catch error 1222 ("Lock request time out period exceeded") and proceed to the next batch.
– Geoff Patterson
May 21 '15 at 15:47
@gpatterson Interesting approach. I'll try this too.
– ToC
May 21 '15 at 16:30
2
To answer, no there is not an easier way to see if the rows are locked unless there is something specifically done in the application. Basically you could first do a select with HOLDLOCK and XLOCK with a lock_timeout set (which is what NOWAIT in my original comment is about, setting the timeout to 0). If you don't get it, then you know something is locked. There is nothing easily available to say "Is row X in Table Y using Index Z locked by something". We can see if the table has locks or any pages/rows/keys/etc have locks, but translating that into specific rows in a query wouldn't be easy.
– Sean Gallardy
May 21 '15 at 17:21