What is the correct isolation level for Order header - Order lines transactions? - sql

Lets say we have a usual situation with Order_Header and Order_LineItems tables.
Also, lets say we have transactions for creating, updating and selecting Orders. Like:
Create:
BEGIN TRANSACTION
INSERT INTO Order_Headers...
SET #Id = SCOPE_IDENTITY()
INSERT INTO Order_LineItems...(using #Id)
DECLARE #SomeVar INT
--just example to show update dependant on select
SELECT #SomeVar = COUNT(*) Order_Headers
WHERE OrderDate > '1-1-2017'
UPDATE Order_Headers
SET SomeValue = #SomeVar
WHERE Id = #Id
COMMIT
END TRANSACTION
On the other hand, we have transaction for getting Orders base on some criteria, for simplicity, lets say last 10:
SELECT TOP 10 * FROM Order_Headers
ORDER BY Id DESC
Could someone please say what would be correct isolation level for each transaction and shortly explain why?
[UPDATE]
I want to make sure that no other session can insert rows matching
WHERE OrderDate > '1-1-2017'
I also want to make sure that second transaction (pure selecting orders) never pick up rows that are not fully 'done' in first transaction. Meaning those that are created in transactions INSERT part but not yet updated in UPDATE part. (i guess that is covered by READ COMMITED being default, right?)
[UPDATE 2]
i want
WHERE OrderDate > '1-1-2017'
to be the value at the begining of the transaction.

First make sure your database has enabled snapshot isolation
ALTER DATABASE YOURDB
SET ALLOW_SNAPSHOT_ISOLATION ON
First transaction requires SNAPSHOT isolation
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
Second Transaction requires READ COMMITTED isolation
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

Related

How to set up a lock inside a store procedure?

I am trying to set up a read/write lock in SQL Server. My stored procedure is
CREATE PROCEDURE test()
AS
BEGIN
SELECT VALUE FROM MYTABLE WHERE ID=1
UPDATE MYTABLE SET VALUE=VALUE+1 WHERE ID=1
END
I would like to be sure tha no-one else is going to read or update the "Value" field while this stored procedure is being executed.
I read lots of posts and I read that in SQL Server should be enough to set up a transaction.
CREATE PROCEDURE test()
AS
BEGIN
BEGIN TRANSACTION
SELECT VALUE FROM MYTABLE WHERE ID=1
UPDATE MYTABLE SET VALUE=VALUE+1 WHERE ID=1
COMMIT TRANSACTION
END
But to me this is not enough, since I tried launching two parallel connections, both of them using this stored procedure. With SQL Server Management Studio's debugger, i stopped the first execution inside the transaction, and i observed that the second transaction has been executed!
So i tried to add ISOLATION LEVEL
CREATE PROCEDURE test()
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRANSACTION
SELECT VALUE FROM MYTABLE WHERE ID=1
UPDATE MYTABLE SET VALUE=VALUE+1 WHERE ID=1
COMMIT TRANSACTION
END
but the result is the same.
I also tried to set isolation level in the client code
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
EXEC test
but again nothing changed.
My question is: in such situation, which is the correct way to set up a lock that blocks the others?
thank you
in such situation, which is the correct way to set up a lock that blocks the others?
The correct lock here is to read the table with an UPDLOCK, in a transaction.
SELECT VALUE FROM MYTABLE with (UPDLOCK)
WHERE ID=1
You can also use an OUTPUT clause to update and return the value in a single query, which will also prevent two sessions from reading and updating the same value
update MyTable set value = value+1
output inserted.value
However you should not generate keys like this. Only one session can generate a key at a time, and the locking to generate the key is held until the end of the session's current transaction. Instead use a SEQUENCE or an IDENTITY column.

Snapshot isolation behaviour. "Triggered" at first query?

I am doing some tests to try to understand how snapshot isolation works...and I do not. I have SET ALLOW_SNAPSHOT_ISOLATION ON in my db (not interested in READ_COMMITTED_SNAPSHOT atm). Then I do the following tests. I will mark different sessions (practically different tabs in my ssms) by [s1] and [s2] markup,[s2] being the isolated session, and [s1] simulating another, non-isolated session.
First, make a table, and let's give it a row. #[s1]:
create table _g1 (v int)
insert _g1 select 1
select * from _g1
(Output: 1)
Now let's begin an isolated transaction.
#[s2]:
set transaction isolation level snapshot
begin tran
Insert another row, #[s1]:
insert _g1 select 2
Now let's see what the isolated transaction "sees", #[s2]:
select * from _g1
(Output: 1,2)
Strange. Shouldn't the isolation "start counting" from the moment of the "Begin tran"? Here, it should not have returned the 2....Let's do this another time. #[s1]:
insert _g1 select 3
#[s2]:
select * from _g1
(Output: 1,2)
So, this time it worked as I expected and did not account the latest insert.
How is this behaviour explained? Does the isolation start working after the first access of each table?
Snapshot isolation works with row versioning. For each modification on a row, the database engine maintains the previous and the current version of the row, along with the serial number (XSN) of the transaction that made the modification.
When snapshot isolation is used for a transaction in [s2]:
The Database Engine reads a row within the transaction and retrieves
the row version from tempdb whose sequence number is closest to, and
lower than, the transaction sequence number.
(see "How Snapshot Isolation and Row Versioning Work", in https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/snapshot-isolation-in-sql-server). The transaction sequence number XSN2 for the transaction in [s2] is not assigned until a DML statement is issued.
sys.dm_tran_active_snapshot_database_transactions is a DMV which returns a virtual table
for all active transactions that generate or potentially access row versions. You can query this view to get information about active transactions that access row versions.
To verify all the above, you could try:
#[s1]
create table _g1 (v int)
#[s2]
set transaction isolation level snapshot
begin tran
select * from sys.dm_tran_active_snapshot_database_transactions -- < No XSN has been assigned, yet. Zero rows are returned.
select * from _g1 --< XSN2 is now assigned.
(Output: zero rows)
select * from sys.dm_tran_active_snapshot_database_transactions -- < XSN2 has been assigned and the corresponding record is returned.
#[s1]
insert _g1 select 1
select * from _g1
(Output: 1)
#[s2]
select * from _g1
(Output: zero rows)
Please, see the remarks in https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-tran-active-snapshot-database-transactions-transact-sql?view=sql-server-ver15 about when an XSN is issued:
sys.dm_tran_active_snapshot_database_transactions reports transactions that are assigned a transaction sequence number (XSN). The XSN is assigned when the transaction first accesses the version store. In a database that is enabled for snapshot isolation or read committed isolation using row versioning, the examples show when an XSN is assigned to a transaction:
If a transaction is running under serializable isolation level, an XSN is assigned when the transaction first executes a statement, such as an UPDATE operation, that causes a row version to be created.
If a transaction is running under snapshot isolation, an XSN is assigned when any data manipulation language (DML) statement, including a SELECT operation, is executed.
Therefore, to answer your question, snapshot isolation "starts counting" after the first 'SELECT' or other DML statement issued within the transaction and not immediately after the 'begin trasaction' statement.
You can Set Transaction Isolation Level Snapshot
either on Database level or Session level.
In our example,we have set on Session level.
So Isolation Level Snapshot will work only in that session on which it was declare.
Secondly, you must issue a T-Sql statement.
In #s2 ,
Set Transaction Isolation Level Snapshot
Begin Tran
Here Transaction is open but there is no T-Sql.
So Snapshot Version of which which table will be maintain ?
Set Transaction Isolation Level Snapshot
Begin Tran
select * from _g1
Here isolation level will work on table _g1. or what ever tables are mention in T-Sql within Transaction .
In other word it will maintain Own version of records for all tables in TempDB mention in T-Sql this TRANSACTION.
It will read data from TempDB untill that Transaction is not Commit or Rollback.
After this,it will read data from Table .
In #s2, Begin Tran is without RollBack or Commit.
Though all record are committed in #s1,
it do not fetch 3. it fetch 1,2 which were committed prior to issue T Sql on same table.
If Rollback or Commit is done in #S2 then output will be (1,2,3).
Since all Insert statement in #s1 is committed.
After Transaction is Commit or Rollback, it will read data from .
In other example,
Truncate table _g1.
We first start #s2,
Set Transaction Isolation Level Snapshot
Begin Tran
select * from _g1
Output : no record.
Here database engine has maintain own version for table _g1.
Since there is no record in _g1,so TempDB is empty.
In #s1,
insert _g1 select 1
select * from _g1
(Output: 1)
In #s2,
If you simply only run
select * from _g1
or you run all script
Output is still nothing.Because we have not committed or rollback so it continue reading from TempDB.
After Commit or Rollback, it will again refresh Record of TempDB.
So output in #s2 will be 1

sql server a simple query takes forever to run due to transaction isolation level

I've come across a problem while learning transaction isolation levels in SQL server.
The problem is that after I run this code (and it finishes without errors):
set implicit_transactions off;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN T1;
SELECT (...)
WAITFOR DELAY '00:00:5'
SELECT (...)
WAITFOR DELAY '00:00:3'
COMMIT TRAN T1;
I want to run this query:
set implicit_transactions off;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRANSACTION T2;
INSERT (...)
INSERT (...)
COMMIT TRANSACTION T2;
But it just says "Executing query", and does nothing.
I think it's because the lock on the tables somehow continues after the first transaction has been finished. Can someone help?
Of course the selects and the inserts refer to the same tables.
Either the first tran is still open (close the window to make sure it is not), or some other tran is open (exec sp_who2). You can't suppress X-locks taken by DML because SQL Server needs those locks during rollback.
#usr offers good possibilities.
A related specific possibility is that you selected only part of the first transaction to execute while tinkering - i.e. executed BEGIN TRAN T1 and never executed COMMIT TRAN T1. It happens - part of Murphy's Law I think. Try executing just COMMIT TRAN T1, then re-trying the second snippet.
The following worked just fine for me on repeated, complete executions in a single session:
set implicit_transactions off;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN T1;
SELECT * from tbl_A
WAITFOR DELAY '00:00:5'
SELECT * from tbl_B
WAITFOR DELAY '00:00:3'
COMMIT TRAN T1;
set implicit_transactions off;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRANSACTION T2;
INSERT tbl_A (ModifiedDate) values (GETDATE())
INSERT tbl_B (ModifiedDate) values (GETDATE())
INSERT tbl_A (ModifiedDate) select top 1 ModifiedDate from tbl_A
INSERT tbl_B (ModifiedDate) select top 1 ModifiedDate from tbl_B
COMMIT TRANSACTION T2;
1 - SET IMPLICT_TRANSACTIONS is usually OFF unless you SET ANSI_DEFAULTS to ON. Then it will be ON. Thus, you can remove this extra statement if it is not needed.
2 - I agree with Aaron. Read uncommitted (no lock) should be used with a SELECT statement. However, this can lead to invalid results. It is prone to missing data, reading data twice, or scan errors.
Read Committed Snap Shot Isolation (RCSI) is a better option at the expense of tempdb (version store space). This will allow your reports (readers) not to be blocked by transactions (writers).
3 - Setting, SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, will use the most amount of locks. Therefore, increase the chances of blocking .
Why use this low concurrency isolation level with two INSERT statements?
I can understand using this level to UPDATE multiple tables. For instance, a bank transaction. Debit one row and Credit another row. Two tables. No one has access to the records until the transaction is complete.
In short, I would use READ COMMITTED isolation level for the insert statements. More than likely, the data being inserted is different.
However, the whole picture is not here.
There is some type of blocking that is occurring. You need to find the root cause.
Here is a code snippet to look at locks and objects that are locked.
--
-- Locked object details
--
-- Old school technique
EXEC sp_lock
GO
-- Lock details
SELECT
resource_type, resource_associated_entity_id,
request_status, request_mode,request_session_id,
resource_description
FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID('AdventureWorks2012')
GO
-- Page/Key details
SELECT object_name(object_id) as object_nm, *
FROM sys.partitions
WHERE hobt_id = 72057594047037440
GO
-- Object details
SELECT object_name(1266103551)
GO
If you still need help, please identify the two blocking transactions and the locks. Please post this information.

How do locks work in a insert...select statement?

Two transactions in two sessions are operating on same item as:
In session 1:
begin tran T1
insert into Invoice with (item,OrderNumber)
select 'ItemA', max(OrderNumber)+1
from Orders
where item='ItemA'
waitfor delay '00:00:05'
commit T1
In session 2:
begin tran T2
insert into Invoice with (item,OrderNumber)
select 'ItemA', max(OrderNumber)+1
from Orders
where item='ItemA'
commit T2
If just like this, two identical rows will be insert into the table Orders. But I want to transaction in either session is done first and then another transaction can read new max(OrderNumber) and then insert next value. I add holdlock to T1 as:
begin tran T1
insert into Invoice with (item,OrderNumber)
select 'ItemA', max(OrderNumber)+1
from Orders with (holdlock)
where item='ItemA'
waitfor delay '00:00:05'
commit T1
Does SQl SERVER assign shared lock to select first since it parse select statement first then assign exclusive lock to insert statement? How does exactly locks works to each other in two session? Thanks for any hints
You can use serializable isolation level for your transaction.
Ex:
set transaction isolation level serializable
begin tran
insert into Invoice with (item,OrderNumber)
select 'ItemA', max(OrderNumber)+1
from Orders
where item='ItemA'
waitfor delay '00:00:05'
commit tran
Serializable option will provide following transaction features:
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.
Above will work for your problem but I would suggest using an identity column instead of max ordernumber + 1 logic. So change the OrderNumber to be identity in your table and when you read data use row_number number to compute order number by Item in runtime, here is an example query:
select Item, Row_Number() over(partition by Item order by OrderNumber) as OrderNumber
from Invoice
So the above query will give the result you need.
What is your ultimate goal. I don't think you cannot stop an insert with a lock on select, it will only lock the selected rows from any update.
Database locking schemes are integral part of any database management application. For integrity of data stored in databases there are different locking schemes provided by different database vendors.
You should check the following links First Link
Second Link. If these doesn't help then please let me know so that I can help you further.

Which SQL Read TRANSACTION ISOLATION LEVEL do I want for long running insert?

I have a long running insert transaction that inserts data into several related tables.
When this insert is running, I cannot perform a select * from MainTable. The select just spins its wheels until the insert is done.
I will be performing several of these inserts at the same/overlapping time. To check that the information is not inserted twice, I query the MainTable first to see if an entry is there and that its processed bit is not set.
During the insert transaction, it flips the MainTable processed bit for that row.
So I need to be able to read the table and also be able to tell if the specific row is currently being updated.
Any ideas on how to set this up in Microsoft SQL 2005? I am looking through the SET TRANSACTION ISOLATION LEVEL documentation.
Thank you,
Keith
EDIT: I do not think that the same insert batch will happen at the same time. These are binary files that are being processed and their data inserted into the database. I check that the file has not been processed before I parse and insert the data. When I do the check, if the file has not been seen before I do a quick insert into the MainTable with the processed bit set false.
Is there a way to lock the row being updated instead of the entire table?
You may want to rethink your process before you use READ UNCOMMITTED. There are many good reasons for isolated transactions. If you use READ UNCOMMITTED you may still get duplicates because there is a chance both of the inserts will check for updates at the same time and both not finding them creating duplicates. Try breaking it up into smaller batches or issue periodic COMMITS
EDIT
You can wrap the MainTable update in a transaction that will free up that table quicker but you still may get conflicts with the other tables.
ie
BEGIN TRANSACTION
SELECT #ProcessedBit = ProcessedBit FROM MainTable WHERE ID = XXX
IF #ProcessedBit = False
UPDATE MainTable SET ProcessedBit = True WHERE ID = XXX
COMMIT TRANSACTION
IF #ProcessedBit = False
BEGIN
BEGIN TRANSACTION
-- start long running process
...
COMMIT TRANSACTION
END
EDIT to enable error recovery
BEGIN TRANSACTION
SELECT #ProcessedStatus = ProcessedStatus FROM MainTable WHERE ID = XXX
IF #ProcessedStatus = 'Not Processed'
UPDATE MainTable SET ProcessedBit = 'Processing' WHERE ID = XXX
COMMIT TRANSACTION
IF #ProcessedStatus = 'Not Processed'
BEGIN
BEGIN TRANSACTION
-- start long running process
...
IF No Errors
BEGIN
UPDATE MainTable SET ProcessedStatus = 'Processed' WHERE ID = XXX
COMMIT TRANSACTION
ELSE
ROLLBACK TRANSACTION
END
The only isolation level that allows one transaction to read changes executed by another transaction in progress (before it commits) is:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED