Hold exclusive lock for select query - sql-server

SQL Server: how to hold exclusive lock for select query? For mysql,
select * from Employee e
where e.id=123
for update
Other concurrent transaction can not read or write the selected row.
How to achieve the same for SQL server?
SELECT *
FROM Employee e
WITH (HOLDLOCK, ROWLOCK)
WHERE e.id = 123;
For (HOLDLOCK, ROWLOCK), is it holding the READ LOCK on the selected row? With READ LOCK, other transaction can still read the locked row, right?

You should specify XLOCK-hint on your statement to achieve desired behavior.
Other concurrent transaction can not read or write the selected row.
SELECT *
FROM Employee e
WITH (XLOCK)
WHERE e.id = 123;
Now I want to describe why HOLDLOCK and ROWLOCK won't do a trick.
HOLDLOCK will inform SqlServer to hold S-locks (shared locks) until transaction ends. S-lock prevents concurrent writes while someting is being read. But it allows to read data concurrently. That's why it called 'shared'. By default, shared locks live as long as it proposed by current isolation level of transaction. For read committed isolation level (which is default) shared lock will be released after data was fully read. That means row-level lock releases just after row was read, page-level lock releases after all data on page was read and table-level lock releases after statement was ended. But HOLDLOCK forces shared locks to be released at the end of transaction. So, when you set HOLDLOCK, shared locks of statement are forced to live longer like it was executed within transaction with repeatable read isolation level.
XLOCK works just like HOLDLOCK with one difference: X-locks (exclusive locks) are used.
UPDLOCK forces SqlServer to use U-locks (update locks) inistead of S-locks without any other differences with HOLDLOCK hint. U-lock is a specific lock type for read statements followed by any writes within transaction, what potentially can cause deadlocks. So, you use UPDLOCK when you want restrict reads of the same data during concurrent transactions for update purpose.
ROWLOCK tells SqlServer to hold locks at row-level even when page- or table-level locks are supposed to be used. Basically, you don't need to specify ROWLOCK if you're not sure what you do, because you can make statement execution more complicated.

With the addition of an exclusive lock (xlock) or update lock (updlock) any other select statement will be blocked, for the duration of the transaction. Whether only the row is locked (or not) depends on how the row is accessed: if a table scan is performed then the whole table is exclusively locked.
begin transaction
select *
from Employee with(xlock /*or updlock*/, holdlock, rowlock /*not assured*/)
where id = 123
--check the locks
exec sp_lock
--rollback transaction

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);

In SQL Server, how can I lock a single row in a way similar to Oracle's "SELECT FOR UPDATE WAIT"?

I have a program that connects to an Oracle database and performs operations on it. I now want to adapt that program to also support an SQL Server database.
In the Oracle version, I use "SELECT FOR UPDATE WAIT" to lock specific rows I need. I use it in situations where the update is based on the result of the SELECT and other sessions can absolutely not modify it simultaneously, so they must manually lock it first. The system is highly subject to sessions trying to access the same data at the same time.
For example:
Two users try to fetch the row in the database with the highest priority, mark it as busy, performs operations on it, and mark it as available again for later use.
In Oracle, the logic would go basically like this:
BEGIN TRANSACTION;
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1'
ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;
Note that the queries are built dynamically in my code. Also note that when the previously most favorable row is marked as unavailable, the second user will automatically go for the next one and so on. Furthermore, different users working on different categories will not have to wait for each other's locks to be released. Worst comes to worst, after 5 seconds, an error would be returned and the operation would be cancelled.
So finally, the question is: how do I achieve the same results in SQL Server? I have been looking at locking hints which, in theory, seem like they should work. However, the only locks that prevents other locks are "UPDLOCK" AND "XLOCK" which both only work at a table level.
Those locking hints that do work at a row level are all shared locks, which also do not satisfy my needs (both users could lock the same row at the same time, both mark it as unavailable and perform redundant operations on the corresponding item).
Some people seem to add a "time modified" column so sessions can verify that they are the ones who modified it, but this sounds like there would be a lot of redundant and unnecessary accesses.
You're probably looking forwith (updlock, holdlock). This will make a select grab an exclusive lock, which is required for updates, instead of a shared lock. The holdlock hint tells SQL Server to keep the lock until the transaction ends.
FROM TABLE_ITEM with (updlock, holdlock)
As documentation sayed:
XLOCK
Specifies that exclusive locks are to be taken and held until the
transaction completes. If specified with ROWLOCK, PAGLOCK, or TABLOCK,
the exclusive locks apply to the appropriate level of granularity.
So solution is using WITH(XLOCK, ROWLOCK):
BEGIN TRANSACTION;
SELECT ITEM_ID
FROM TABLE_ITEM
WITH(XLOCK, ROWLOCK)
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;
In SQL Server there are locking hints but they do not span their statements like the Oracle example you provided. The way to do it in SQL Server is to set an isolation level on the transaction that contains the statements that you want to execute. See this MSDN page but the general structure would look something like:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
select * from ...
update ...
COMMIT TRANSACTION;
SERIALIZABLE is the highest isolation level. See the link for other options. From MSDN:
SERIALIZABLE Specifies the following:
Statements cannot read data that has been modified but not yet
committed by other transactions.
No other transactions can modify data that has been read by the
current transaction until the current transaction completes.
Other transactions cannot insert new rows with key values that would
fall in the range of keys read by any statements in the current
transaction until the current transaction completes.
Have you tried WITH (ROWLOCK)?
BEGIN TRAN
UPDATE your_table WITH (ROWLOCK)
SET your_field = a_value
WHERE <a predicate>
COMMIT TRAN

SQL Server transaction isolation problem - global variable

SQL Server 2008 R2 (Data Center edition - I think)
I have a very specific requirement for the database.
I need to insert a row marked with timestamp [ChangeTimeStamp]. Timestamp value is passed as a parameter. Timestamp has to be unique.
Two processes can insert values at the same time, and I happen to run into duplicate key insertion once in a while. To avoid this, I am trying:
declare #maxChangeStamp bigint
set transaction isolation level read committed
begin transaction
select #maxChangeStamp = MAX(MaxChangeTimeStamp) from TSMChangeTimeStamp
if (#maxChangeStamp > #changeTimeStamp)
set #maxChangeStamp = #maxChangeStamp + 1
else
set #maxChangeStamp = #changeTimeStamp
update TSMChangeTimeStamp
set MaxChangeTimeStamp = #maxChangeStamp
commit
set #changeTimeStamp = #maxChangeStamp
insert statment
REPEATABLE READ - causes deadlock
READ COMMITTED - causes duplicate key inserts
#changeTimeStamp is my parameter.
TSMChangeTimeStamp holds only one value.
If anyone has a good idea how to solve this I will appreciate any help.
You don't read-increment-update, this will fail no matter what you try. Alway update and use the OUTPUT clause to the new value:
update TSMChangeTimeStamp
set MaxChangeTimeStamp += 1
output inserted.MaxChangeTimeStamp;
You can capture the output value if you need it in T-SQL. But although this will do what you're asking, you most definitely do not want to do this, specially on a system that is high end enough to run DC edition. Generating the next timestamp will place an X lock on the timestamp resource, and thus will prevent every other transaction from generating a new timestamp until the current transaction commits. You achieve complete serialization of work with only one transaction being active at a moment. The performance will tank to the bottom of the abyss.
You must revisit your requirement and come up with a more appropriate one. As it is now your requirement can also be expressed as 'My system is too fast, how can I make is really really really slow?'.
Inside the transaction, the SELECT statement will acquire a shared lock if the mode is not READ COMMITTED or snapshot isolation. If two processes both start the SELECT at the same time, they will both acquire a shared lock.
Later, the UPDATE statement attempts to acquire an exclusive lock (or update lock). Unfortunately, neither one can acquire an exclusive lock, because the other process has a shared lock.
Try using the WITH (UPDLOCK) table hint on the SELECT statement. From MSDN:
UPDLOCK
Specifies that update locks are to be taken and held until the
transaction completes. UPDLOCK takes update locks for read operations
only at the row-level or page-level. If UPDLOCK is combined with
TABLOCK, or a table-level lock is taken for some other reason, an
exclusive (X) lock will be taken instead.
When UPDLOCK is specified, the READCOMMITTED and READCOMMITTEDLOCK
isolation level hints are ignored. For example, if the isolation level
of the session is set to SERIALIZABLE and a query specifies (UPDLOCK,
READCOMMITTED), the READCOMMITTED hint is ignored and the transaction
is run using the SERIALIZABLE isolation level.
For example:
begin transaction
select #maxChangeStamp = MAX(MaxChangeTimeStamp) from TSMChangeTimeStamp with (updlock)
Note that update locks may be promoted to a table lock if there is no index for your table (Microsoft KB article 179362).
Explicitly requesting an XLOCK may also work.
Also note your UPDATE statement does not have a WHERE clause. This causes the UPDATE to lock and update every record in the table (if applicable in your case).

Does SQL Server wrap Select...Insert Queries into an implicit transaction?

When I perform a select/Insert query, does SQL Server automatically create an implicit transaction and thus treat it as one atomic operation?
Take the following query that inserts a value into a table if it isn't already there:
INSERT INTO Table1 (FieldA)
SELECT 'newvalue'
WHERE NOT EXISTS (Select * FROM Table1 where FieldA='newvalue')
Is there any possibility of 'newvalue' being inserted into the table by another user between the evaluation of the WHERE clause and the execution of the INSERT clause if I it isn't explicitly wrapped in a transaction?
You are confusing between transaction and locking. Transaction reverts your data back to the original state if there is any error. If not, it will move the data to the new state. You will never ever have your data in an intermittent state when the operations are transacted. On the other hand, locking is the one that allows or prevents multiple users from accessing the data simultaneously. To answer your question, select...insert is atomic and as long as no granular locks are explicitly requested, no other user will be able to insert while select..insert is in progress.
John, the answer to this depends on your current isolation level. If you're set to READ UNCOMMITTED you could be looking for trouble, but with a higher isolation level, you should not get additional records in the table between the select and insert. With a READ COMMITTED (the default), REPEATABLE READ, or SERIALIZABLE isolation level, you should be covered.
Using SSMS 2016, it can be verified that the Select/Insert statement requests a lock (and so most likely operates atomically):
Open a new query/connection for the following transaction and set a break-point on ROLLBACK TRANSACTION before starting the debugger:
BEGIN TRANSACTION
INSERT INTO Table1 (FieldA) VALUES ('newvalue');
ROLLBACK TRANSACTION --[break-point]
While at the above break-point, execute the following from a separate query window to show any locks (may take a few seconds to register any output):
SELECT * FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID()
AND resource_associated_entity_id = OBJECT_ID(N'dbo.Table1');
There should be a single lock associated to the BEGIN TRANSACTION/INSERT above (since by default runs in an ISOLATION LEVEL of READ COMMITTED)
OBJECT ** ********** * IX LOCK GRANT 1
From another instance of SSMS, open up a new query and run the following (while still stopped at the above break-point):
INSERT INTO Table1 (FieldA)
SELECT 'newvalue'
WHERE NOT EXISTS (Select * FROM Table1 where FieldA='newvalue')
This should hang with the string "(Executing)..." being displayed in the tab title of the query window (since ##LOCK_TIMEOUT is -1 by default).
Re-run the query from Step 2.
Another lock corresponding to the Select/Insert should now show:
OBJECT ** ********** 0 IX LOCK GRANT 1
OBJECT ** ********** 0 IX LOCK GRANT 1
ref: How to check which locks are held on a table

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