In SQL rollback transaction, from where it rollbacks the state? - sql

In SQL rollback transaction, from where it rollbacks the state? I mean where the data is stored so that rollback can take it back.

For Oracle, it is saving changes in redo log until commit. Every RDMS has own strategy.

you can use safe and easy code for run 100% (run all line of query) or no run any off them
|query 1| = like insert into or select or ...
|count of lines| = number off query
DECLARE #rowcount int set #rowcount = 0 ;
BEGIN TRANSACTION [Tran1]
BEGIN TRY
<Query 1> ; set #rowcount = (#rowcount + ##ROWCOUNT);
<Query 2> ; set #rowcount = (#rowcount + ##ROWCOUNT);
...
IF #rowcount = <count of lines>
COMMIT TRANSACTION[Tran1]
ELSE
ROLLBACK TRANSACTION[Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION[Tran1]
END CATCH
for example this code run 2 insert into line query but or run all of him or no run anything and ROLLBACK
DECLARE #rowcount int set #rowcount = 0 ;
BEGIN TRANSACTION [Tran1]
BEGIN TRY
insert into [database].[dbo].[tbl1] (fld1) values('1') ;
set #rowcount = (#rowcount + ##ROWCOUNT);
insert into [database].[dbo].[tbl2] (fld1) values('2') ;
set #rowcount = (#rowcount + ##ROWCOUNT);
IF #rowcount = 2
COMMIT TRANSACTION[Tran1]
ELSE
ROLLBACK TRANSACTION[Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION[Tran1]
END CATCH

Related

Result from job procedure with multiple internal procedures

I have one procedure with multiple internal procedures. The main procedure is with try-catch block and transaction in while expression. Each of the internal procedures has no transaction and no try-catch block.
My question is if procedure3 crashes with an error, will the whole transaction be rolled back and procedure1 and procedure2 will not have executed? In this case what will XACT_STATE() do?
It looks like that:
DECLARE #param int;
CREATE TABLE #table (id int);
INSERT INTO #table (id)
SELECT SubmissionDataID
FROM [sob].[SubmissionData]
WHERE [sob].[SubmissionData].SubmissionStatusID = 9
AND [sob].[SubmissionData].[IsLast] = 1;
WHILE EXISTS (SELECT TOP 1 id FROM #table)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
SELECT TOP 1 #param=id FROM #table
EXEC procedure1 (#param);
EXEC procedure2 (#param);
EXEC procedure3 (#param);
EXEC procedure4 (#param);
EXEC procedure5 (#param);
EXEC procedure6 (#param);
EXEC procedure7 (#param);
DELETE FROM #table
WHERE id = #param
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DELETE FROM #table
WHERE id = #param;
SELECT
ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE();
-- Transaction uncommittable
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION
PRINT 'INFO: TRANSACTION has been ROLLBACKED!';
THROW;
END
-- Transaction committable
IF (XACT_STATE()) = 1
COMMIT TRANSACTION
END CATCH
END --end while
DROP TABLE #table;
END
/* ************ END OF PROCEDURE ************ */
I'm not sure how XACT_STATE() works.

How to avoid concurrent read access issue in SQL Server?

I am getting a date value from a tblinfo table. If the date is null, then I will insert into table, else I will update the row in the table.
I am facing the issue that 2 sessions accessing the table simultaneously with same information, both are getting Null, so both are trying to insert records into same table with same primary key value. We are getting the primary key constraints issue. How to stop this?
begin tran
set #date=(select date from tblinfo where id=#id)
if(#date is null)
--insert
else
--update
commit tran
Using Locking
declare #Date DateTime
, #RowId bigint = 10
Begin Tran
select #Date = SomeDate
from tblInfo
with (RowLock) --this is the important point; during the transaction, this row will be locked, so other statements won't be able to read it
where Id = #RowId
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
if ##TranCount > 0 Commit Tran
Technique Avoiding Locking
If preventing reads from occurring is an issue for some reason, the below is a lockless approach; but requires an extra field in your database.
declare #Date DateTime
, #RowId bigint = 10
, #DataAvailable bit
Begin Tran
update tblInfo
set session_lock = ##SPID
where Id = #RowId
and session_lock is null
select #Date = SomeDate
, #DataAvailable = 1
from tblInfo
where Id = #RowId
and session_lock = ##SPID
if (#DataAvailable = 1)
begin
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
update tblInfo
set session_lock = null
where Id = #RowId
and session_lock = ##SPID
end
--optionally do something if the row was already "locked"
--else
--begin
--...
--end
if ##TranCount > 0 Commit Tran
Right after "begin tran", update your table like below script
begin tran
-- this update will lock the table so no other process can read data
update tblInfo with (tablock)
set date = date
---- do what ever you need to do here
set #date=(select * from tblinfo)
if(#date is null)
--insert
else
--update
commit tran
this will cause SQL Server lock the table and the second transaction will wait reading the data until the first process finish.
Relying on lock hints to get the desired outcome will perhaps prevent you from attempting to insert the value twice, but it won't prevent errors from happening. You'll just get an 'Unable to acquire lock' or deadlock error instead.
You should put a mutex in your code, in the minimally sized critical section. Using sp_getApplock, you can get a lock on a section of code with a wait period that you specify (subsequent threads will wait for the lock to clear and then continue). Here's some sample code:
declare #LockResource nvarchar(100) = 'VALUE_INSERT'
begin transaction
-- Fetch the lock:
EXEC #res = sp_getapplock
#Resource = #LockResource,
#LockMode = 'Exclusive',
#LockOwner = 'Transaction',
#LockTimeout = 10000,
#DbPrincipal = 'public'
if #res not in (0, 1)
begin
set #msg = 'Unable to acquire Lock for ' + #LockResource
raiserror (#msg , 16, 1)
rollback transaction
return
end
else
begin
begin try
---------------------------------------------
-- Fetch value if it exists, insert if not.
-- Both need to happen here.
---------------------------------------------
end try
begin catch
select #msg = 'An error occurred: ' + ERROR_MESSAGE()
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
rollback transaction
raiserror(#msg, 11, 1)
goto cleanup
end catch
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
end
commit transaction

Stored Procedure doesn't return affected rows after exception handling

I have added exception handling in my stored procedure as below.
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
INNER JOIN [dbo].[vw_Office] vw
ON (vw.OfficeID = bud.OfficeID)
WHERE vw.DistrictID = #DistrictID
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
SELECT ##ROWCOUNT AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
SET NOCOUNT OFF ;
END
I need to return the number of affected rows using ##ROWCOUNT. But this stored procedure always returns rowcount as 0. Any reason for this. Do I need to write the ##rowcount statement right after update?
You need to select ##ROWCOUNT after your UPDATE statement. As per the documentation:
Statements such as USE, SET , DEALLOCATE CURSOR, CLOSE CURSOR,
BEGIN TRANSACTION or COMMIT TRANSACTION reset the ROWCOUNT value to 0.
Since your ##ROWCOUNT is after the COMMIT TRAN, ##ROWCOUNT returns 0.
You need to store result of global variables in local variable because it will change after next instruction like:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
DECLARE #rowcount INT, #error INT;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT #error = ##ERROR, #rowcount = ##ROWCOUNT;
IF #error = 0
BEGIN
COMMIT TRAN;
SELECT #rowcount AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
END
Or even better resign for using ##ERROR in TRY CATCH block:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT ##ROWCOUNT AS AffectedRow;
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ERROR
ROLLBACK TRAN;
END CATCH
END
And where is #BudgetStateID defined?

Timeout occur sometimes when executing a Store Procedure

I have this stored procedure, which works if I execute manually. But sometimes I see timeout exception on running this Stored Procedure. Is this is due to MERGE, http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
ALTER PROCEDURE [dbo].[InsertOrUpdateMobileUser]
(
#ID BIGINT
,#Name NVARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION InsertOrUpdateMobileUser;
MERGE INTO MobileUsers MU
USING (SELECT #ID AS ID) T ON (MU.ID = T.ID)
WHEN MATCHED THEN
UPDATE SET [Name] = CASE WHEN #Name IS NULL OR #Name = '' THEN [Name] ELSE #Name END
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (#Name)
SELECT *
FROM MobileUsers
WHERE ID = #ID;
LBEXIT:
IF #TranCount = 0
COMMIT;
END TRY
BEGIN CATCH
DECLARE #Error INT, #Message VARCHAR(4000), #XState INT;
SELECT #Error = ERROR_NUMBER() ,#Message = ERROR_MESSAGE() ,#XState = XACT_STATE();
IF #XState = -1
ROLLBACK;
IF #XState = 1 AND #TranCount = 0
rollback
IF #XState = 1 AND #TranCount > 0
ROLLBACK TRANSACTION InsertOrUpdateMobileUser;
RAISERROR ('InsertOrUpdateMobileUser: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
You differentiate between two execution methods. What are they, exactly? You mean that it works if run only the code of the procedure, and doesn't work when you EXECUTE the proc? Of it works through EXECUTE, and fails in a job?
Timeouts only concern client applications. You have SqlCommand .CommandTimeout property in .NET, and in Management Studio there's Tools>Options>Query Execution>Command Timeout. If you have a job, then it should run infinitely, there's even no option to set the timeout in Sql Server Agent.

What Order should I Call ##ROWCOUNT/##ERROR

I am inserting a number or rows into a table using INSERT with SELECT.
After the transaction, I want to store both the ##ROWCOUNT and ##ERROR values into locallay declared variables.
INSERT SubscriberList (PublicationId, SubscriberId)
SELECT #PublicationId, S.SubscriberId
FROM Subscribers S
SET #NoRows = ##ROWCOUNT
SET #ErrorCode = ##ERROR
I wasn't sure if this was valid in as much if I call one, will I negate the other?
Set them both at once:
SELECT #NoRows = ##ROWCOUNT, #ErrorCode = ##ERROR
In addition to #JNK's answer...
I never use ##ERROR now because of TRY/CATCH
BEGIN TRY
BEGIN TRAN
INSERT SubscriberList (PublicationId, SubscriberId)
SELECT #PublicationId, S.SubscriberId
FROM Subscribers S
SET #NoRows = ##ROWCOUNT
... do more inserts, updates etc
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
SET #ErrorCode = ERROR_NUMBER()
RAISERROR ...
END CATCH