Understanding locking behavior in SQL Server - sql-server

I tried to reproduce the situation of question [1].
On table, taken and filled with data from wiki's "Isolation (database systems)" [2],
in SQL Server 2008 R2 SSMS, I executed:
1) first in first tab (window) of SSMS
-- transaction isolation level in first window does not influence results (?)
-- initially I thought that second transaction in 2) runs at the level set in first window
begin transaction
INSERT INTO users VALUES ( 3, 'Bob', 27 )
waitfor delay '00:00:22'
rollback
2) immediately after, in second window
-- this is what I commented/uncommented
-- set transaction isolation level SERIALIZABLE
-- set transaction isolation level READ REPEATABLE
-- set transaction isolation level READ COMMITTED
-- set transaction isolation level READ UNCOMMITTED
SELECT * FROM users --WITH(NOLOCK)
Update:
Sorry, results were corrected.
My results, depending on isolation level set in 2), are that SELECT returns:
immediately (reading uncommitted inserted row)
for all cases of SELECT with NOLOCK
for READ UNCOMMITTED (SELECT either with or without NOLOCK)
is waiting the completion of transaction 1) (ONLY IF SELECT is without NOLOCK) and
in READ COMMITTED and higher (REPEATABLE READ, SERIALIZABLE) transaction isolation level
These results contradict to situation described in question (and explained in answers?) [1]
(for example, that SELECT with NOCHECK is waiting completion of 1)), etc.
How can my results and [1] be explained?
Update2:
This question is really subquestion of my questions [3] (or the result of them not being answered).
Cited:
[1]
Explain locking behavior in SQL Server
Explain locking behavior in SQL Server
[2]
"Isolation (database systems)"
Plz add trailing ) to link. I cannot manage to preserve it here in the link!
http://en.wikipedia.org/wiki/Isolation_(database_systems)
[3]
Is NOLOCK the default for SELECT statements in SQL Server 2005?
Is NOLOCK the default for SELECT statements in SQL Server 2005?

There is a useful MSDN link her talk about locking hints in SQL 2008. Maybe in your example its a case of SQL Server 2008 disfavoring your tables locks?
(The following snippet from the link below talks about locks potentially being ingored by SQL Server 2008)
As shown in the following example, if the transaction isolation level is set to SERIALIZABLE, and the table-level locking hint NOLOCK is used with the SELECT statement, key-range locks typically used to maintain serializable transactions are not taken.
CopyUSE AdventureWorks2008R2;
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
GO
BEGIN TRANSACTION;
GO
SELECT Title
FROM HumanResources.Employee WITH (NOLOCK);
GO
-- Get information about the locks held by
-- the transaction.
SELECT
resource_type,
resource_subtype,
request_mode
FROM sys.dm_tran_locks
WHERE request_session_id = ##spid;
-- End the transaction.
ROLLBACK;
GO
The only lock taken that references HumanResources.Employee is a schema stability (Sch-S) lock. In this case, serializability is no longer guaranteed.
In SQL Server 2008, the LOCK_ESCALATION option of A LTER TABLE can disfavor table locks, and enable HoBT locks on partitioned tables. This option is not a locking hint, but can but used to reduce lock escalation. For more information, see ALTER TABLE (Transact-SQL).

The hint in the second query overrides transaction isolation level.
SELECT ... WITH (NOLOCK) is basically identical to SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT ....
With any other isolation level the locks are honored, so the second transaction waits until the locks are released by the first one.

Related

Does SQL lock tables when queries return results greater than 5000 records?

I'm am working in SharePoint and am trying to understand why there is a limit of 5000 records for views on lists that can have a far greater number.
My question is not about SharePoint, but SQL. Does SQL have the limit described below, or was this something imposed on SQL by the SharePoint team?
For performance reasons, whenever SQL Server executes a single query
that returns 5000 + items, locks escalation happens within the SQL
table. As a result, the entire table will be locked. Since the entire
Share Point data is stored as a single table, a single list view query
that exceeds 5000+ items will lock the entire Share Point data table
within that content. The database and all the users will face huge
performance degradation. The entire set of users using Share Point at
the time of lock escalation will have to wait for a longer time to
retrieve the data. Hence, you can see that the list threshold is a
limitation that is imposed upon Share Point by its backend SQL Server.
This issue is generated from SQL and the reason is row lock
escalation. In order to avoid this performance degradation, Share
Point has imposed the limitation of 5000 items to be queried at any
point of time. Any queries for 5000+ items will be dealt the threshold
error message. Ref link
Thanks
EDIT____________________________________
An article on this issue:
https://www.c-sharpcorner.com/article/sharepoint-list-threshold-issue-the-traditional-problem/
Does SQL Server lock tables when queries return results greater than 5000
records?
Not generally, no.
It is documented that 5,000 is a magic number for the database engine to first attempt lock escalation (followed by further attempts at 1,250 increments) but unless running at repeatable read or serializable isolation level this will not generally be hit just by returning 5,0000 items in a SELECT. The default read committed level will release locks as soon as the data is read so never hit the threshold.
You can see the effect of isolation level on this with the following example.
CREATE TABLE T(C INT PRIMARY KEY);
INSERT INTO T
SELECT TOP 10000 ROW_NUMBER() OVER (ORDER BY ##SPID)
FROM sys.all_objects o1, sys.all_objects o2
And (uses undocumented trace flags so should only be used in dev environment)
DBCC TRACEON(3604,611);
/*5,000 key locks are held*/
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN
SELECT COUNT(*) FROM (SELECT TOP 5000 C FROM T) T
SELECT resource_type, request_mode, count(*) FROM sys.dm_tran_locks where request_session_id = ##spid GROUP BY resource_type, request_mode;
COMMIT
/*No key locks are held. They have been escalated to an object level lock. The messages tab shows the lock escalation (in my case after 6248 locks not 5,000)*/
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN
SELECT COUNT(*) FROM (SELECT TOP 10000 C FROM T) T
SELECT resource_type, request_mode, count(*) FROM sys.dm_tran_locks where request_session_id = ##spid GROUP BY resource_type, request_mode;
COMMIT
/*No key locks are held. They are released straight away at this isolation level. The messages tab shows no lock escalation messages*/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRAN
SELECT COUNT(*) FROM (SELECT TOP 10000 C FROM T) T
SELECT * FROM sys.dm_tran_locks where request_session_id = ##spid
COMMIT
DBCC TRACEOFF(3604,611);

What is the TRANSACTION ISOLATION LEVEL that is equivalent with (NOLOCK) for all select statements?

I am building a stored procedure for reporting and I am using (NOLOCK) for all select statements.
There is no locking requirement for the scenario I am working on.
I am thinking to change the TRANSACTION ISOLATION LEVEL at the top of the stored procedure, and avoid adding (NOLOCK) to all of the select statements. Is there a TRANSACTION ISOLATION LEVEL that is equivalent with (NOLOCK) when I set it at the top of the store procedures?
TRANSACTION ISOLATION LEVEL : READ UNCOMMITTED
Specifies that statements can read rows that have been modified by
other transactions but not yet committed. Transactions running at the
READ UNCOMMITTED level do not issue shared locks to prevent other
transactions from modifying data read by the current transaction. READ
UNCOMMITTED transactions are also not blocked by exclusive locks that
would prevent the current transaction from reading rows that have been
modified but not committed by other transactions. When this option is
set, it is possible to read uncommitted modifications, which are
called dirty reads. Values in the data can be changed and rows can
appear or disappear in the data set before the end of the transaction.
This option has the same effect as setting NOLOCK on all tables in all
SELECT statements in a transaction. This is the least restrictive of
the isolation levels.
Note : This is not a recommended Isolation level as this can allows dirty reads
If you want to set the ISOLOATION LEVEL to the SP alone then try changing the SP
CREATE PROCEDURE PRC_SP AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
--your statements
END

SET TRANSACTION ISOLATION LEVEL and Highlighting Statements

I write multiple SELECT statements in one window and normally highlight and run them as needed.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM Table1
SELECT * FROM Table2
SELECT * FROM Table3
This is an example of what my window might look like, now from here I will only highlight SELECT * FROM Table2 and run it.
Will SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; still apply even if it is not highlighted with the statement?
If it does still apply, how does it do it?
Ignoring the usual spiel about why you shouldn't be using READ UNCOMMITTED...
Setting the TRANSACTION ISOLATION LEVEL applies at the connection level, until the connection is dropped or another level is set.
From BOL:
SET TRANSACTION ISOLATION LEVEL (Transact-SQL)
Controls the locking and row versioning behavior of Transact-SQL statements issued by a connection to SQL Server.
So running this once in your SSMS query window will apply for all batched queries you send to the server within that connection (window).
Alternatively, you could add WITH (NOLOCK) to your statements to apply to individual ones, e.g.
SELECT * FROM Table1;
SELECT * FROM Table2 WITH (NOLOCK);
SELECT * FROM Table3;
No, if you didn't execute it, it will not apply to your connection. As Chris mentioned in the previous answer, once you execute it in your connection (query windows in SSMS), all following queries in this connection will be executed using this transaction isolation level.
If you have doubts, you can check which transaction isolation level used for your connection right now:
DBCC USEROPTIONS

SQL Server ROWLOCK over a SELECT if not exists INSERT transaction

I have upgraded from SQL Server 2005 to 2008. I remember that in 2005, ROWLOCK simply did not work and I had to use PAGELOCK or XLOCK to achieve any type of actual locking. I know a reader of this will ask "what did you do wrong?" Nothing. I conclusively proved that I could edit a "ROWLOCKED" row, but couldn't if I escalated the lock level. I haven't had a chance to see if this works in SQL 2008. My first question is has anyone come across this issue in 2008?
My second question is as follows. I want to test if a value exists and if so, perform an update on relevant columns, rather than an insert of the whole row. This means that if the row is found it needs to be locked as a maintenance procedure could delete this row mid-process, causing an error.
To illustrate the principle, will the following code work?
BEGIN TRAN
SELECT ProfileID
FROM dbo.UseSessions
WITH (ROWLOCK)
WHERE (ProfileID = #ProfileID)
OPTION (OPTIMIZE FOR (#ProfileID UNKNOWN))
if ##ROWCOUNT = 0 begin
INSERT INTO dbo.UserSessions (ProfileID, SessionID)
VALUES (#ProfileID, #SessionID)
end else begin
UPDATE dbo.UserSessions
SET SessionID = #SessionID, Created = GETDATE()
WHERE (ProfileID = #ProfileID)
end
COMMIT TRAN
An explanation...
ROWLOCK/PAGELOCK is granularity
XLOCK is mode
Granularity and isolation level and mode are orthogonal.
Granularity = what is locked = row, page, table (PAGLOCK, ROWLOCK, TABLOCK)
Isolation Level = lock duration, concurrency (HOLDLOCK, READCOMMITTED, REPEATABLEREAD, SERIALIZABLE)
Mode = sharing/exclusivity (UPDLOCK, XLOCK)
"combined" eg NOLOCK, TABLOCKX
XLOCK would have locked the row exclusively as you want. ROWLOCK/PAGELOCK wouldn't have.

SQL Server 2005 - Is there a way to lock a SELECT in a Transaction?

Is there a way to lock a SELECT in a Transaction? If a SELECT occurs, no more SELECTs are executed while the first one is not finished.
Thanks!
Not sure I understand your question correctly, but if you want to impose more rigorous locking than SQL Server's default, then you can either bump up the isolation level or use a locking hint. This can be useful if you first need to SELECT something and then later, based on the value SELECTed, do an UPDATE. To avoid a phantom UPDATE from another transaction (wherein the value you previously SELECTed was changed in b/w the SELECT and UPDATE), you can impose an update lock on your SELECT statement.
Eg:
select * from mytable with (holdlock, xlock)
Notice that the SELECT statement above uses the more rigorous update lock & holds that lock for the duration of the transaction. You would also want to wrap your statements in an explicit transaction, as in:
begin transaction
select * from mytable with (holdlock, xlock) -- exclusive lock held for the entire transaction
-- more code here...
update mytable set col='whatever' where ...
commit transaction
Be wary, of course, for long-running transactions.
Might want to look at Isolation Level instead of a hint
You'll need an isolation level of SERIALIZABLE:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
The other SELECT queries will block until the transaction completes in this case. The default level is usually READ COMMITTED.
it seems you're looking for a pessimistic locking strategy but without acctually locking your data. look into Application locks in sql server.
Changing your isolation levels is generally your best choice.
But, if for whatever reason that's not an option for you - you could also do an exclusive table lock...
select * from MyTable with (tablockx)
That would prevent any other selects on the table until your transaction is finished.

Resources