SQL Transaction not working - sql

I am using below SQL to delete records and insert into Customers table inside a transaction. If there is an error in the insert statements, I am seeing the error message and when I try to execute select * from customers, it is not displaying result set. And when I close SSMS window, it is showing There are uncommitted transactions. Do you wish to commit these transactions before closing the window?
After I click OK, results are getting displayed from the table. So, is there any locking mechanism taking place while using transaction.
USE CMSDB;
BEGIN TRY
BEGIN TRAN t1;
DELETE FROM Customers
print ##trancount -->prints 3 since there are three records
INSERT INTO CUSTOMERS
INSERT INTO CUSTOMERd --> error here
INSERT INTO CUSTOMERS
COMMIT TRAN t1;
END TRY
BEGIN CATCH
print 'hi' --> not printing
select ##trancount --> not resulting anything
IF ##TRANCOUNT > 0
ROLLBACK TRAN t1;
-- Error Message
DECLARE #Err nvarchar(1000)
SET #Err = ERROR_MESSAGE()
RAISERROR (#Err,16,1)
END CATCH
GO
Message
(3 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 208, Level 16, State 1, Line 8
Invalid object name 'dbo.Customerd'.

Excerpt from TRY…CATCH description:
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of
deferred name resolution.
In this case what happens is
The error is not caught and control passes out of the TRY…CATCH
construct to the next higher level.

Related

XACT_ABORT doesn't always rollback the transaction on error. When does it do it exactly?

Question
The documentation of SET XACT_ABORT says little more than this about the effect of enabling this option.
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.
I do not believe this is the full truth. After reading this, I was worried that if a stored procedure which enables this option is executed in a transaction created by an external process, it may end up rolling back the outer transaction. Luckily, my fears turned out to be unfounded. However, this means now that I do no truly understand how XACT_ABORT works. What are the conditions SQL Server checks for whether a transaction should be rolled back or not?
Prior investigation
I have carried out the following experiment: (a summary of this code is below, as having a numbered list before a code block breaks StackOverflow's formatting, duh)
CREATE TABLE Dummy
(
ID INT NOT NULL IDENTITY CONSTRAINT PK_Dummy PRIMARY KEY,
Text NVARCHAR(128) NOT NULL
)
CREATE UNIQUE NONCLUSTERED INDEX IX_Dummy_Text ON dbo.Dummy(Text)
GO
CREATE OR ALTER PROCEDURE InsertDummy
#Text NVARCHAR(128)
AS
BEGIN
SET NOCOUNT OFF
SET XACT_ABORT ON
INSERT dbo.Dummy (Text) VALUES (#Text)
END
GO
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC dbo.InsertDummy #Text = N'Dummy'
EXEC dbo.InsertDummy #Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
PRINT 'ERROR! ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
-- Echo the error
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE();
SELECT #ErrorSeverity = ERROR_SEVERITY();
SELECT #ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
PRINT 'At the end ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
IF ##TRANCOUNT>0
ROLLBACK
Create a Dummy table with a UNIQUE index
A stored procedure which inserts into Dummy. The procedures enables XACT_ABORT.
Code which executes this procedure twice, in a transaction. The second call fails, as it attempts to insert a duplicate value into Dummy.
The same code prints out the ##TRANCOUNT value to show if we are still in a transaction or not. It also enables XACT_ABORT.
The output of this test is:
(1 row affected)
(0 rows affected)
ERROR! ##TRANCOUNT is 1
Msg 50000, Level 14, State 1, Line 74
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
At the end ##TRANCOUNT is 1
An error was raised yet the the transaction not rolled back. The way this setting works is clearly not as simplistic as the documentation would have me believe. Why was the transaction not rolled back?
This answer mentions that XACT_ABORT only rolls back the transaction if the severity of the error is at least 16. The error in this example is only level 14. However, even if I replace the INSERT in the procedure with RAISERROR (N'Custom error', 16, 0), the transaction is still not rolled back.
UPDATE: What I found that although the transaction is not rolled back in my test, it is doomed! ##TRANCOUNT is 1 when I execute this sample regardless of the XACT_ABORT setting: but if the setting is ON, XACT_STATE() is -1, indicating an uncomittable transaction. When XACT_ABORT is OFF, XACT_STATE() is 1.
Question
"An error was raised yet the transaction not rolled back. The way these setting works is clearly not as simplistic as the documentation would have me believe. Why was the transaction not rolled back"
The answer to that is that RAISERROR will not cause XACT_ABORT to
trigger! This means we can be in a very messed up state transaction
wise
Abort, Abort, We Are XACT_ABORT:ing, Or Are We?!
According to MSDN ,
The THROW statement honors SET XACT_ABORT. RAISERROR does not. New
applications should use THROW instead of RAISERROR.
We can use the THROW statement instead of the RAISERROR.
So we can use the following statement in order to trigger the XACT_ABORT
TRUNCATE TABLE Dummy
GO
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC dbo.InsertDummy #Text = N'Dummy'
EXEC dbo.InsertDummy #Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
THROW
END CATCH
PRINT 'At the end ##TRANCOUNT is ' + CONVERT(NVARCHAR, ##TRANCOUNT)
IF ##TRANCOUNT>0
ROLLBACK
The output will be;
(1 row affected)
(0 rows affected)
Msg 2601, Level 14, State 1, Procedure dbo.InsertDummy, Line 7 [Batch Start Line 5]
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
For the updated issue you can see set xact_abort on and try-catch together

Insert fails within transaction, but sql server returns 1 row(s) affected?

This is the execution flow of my stored procedure:
ALTER procedure dbo.usp_DoSomething
as
declare #Var1 int
declare #Var2 int
declare #Var3 int
select
#Var1 = Var1,
#Var2 = Var2,
#Var3 = Var3
from Table
where
...
BEGIN TRY
BEGIN TRANSACTION
/* UPDATE Table. This executes successfully */
/* INSERT Table. This fails due to PK violation */
COMMIT TRAN /* This does not happen */
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN /* This occurs because TRANS failed */
END CATCH
The UPDATE runs successfully. The INSERT fails, so the transaction is rolled back.
After execution, the table looks correct and nothing has changed. But when I run the SP, I get the following messages:
(1 row(s) affected)
(0 row(s) affected)
So I'm asking myself, where is the first 1 row(s) affected coming from?
Then I'm thinking that this is the reason, but wanted to confirm: OUTPUT Clause (Transact-SQL)
An UPDATE, INSERT, or DELETE statement that has an OUTPUT clause will return
rows to the client even if the statement encounters errors and is rolled back.
The result should not be used if any error occurs when you run the statement.
By default, a rowcount will be returned for every DML statement, unless SET NOCOUNT ON is enabled. Regardless whether a transaction is successful or not, or rolled back or committed, your UPDATE statement was successful, thus the notification (1 row(s) affected).
The OUTPUT clause you mentioned has nothing to do with it, since you haven't specified it.
The first select with setting variables could produce 1 row affected

Error with uncommitted transaction

I have the following query that I ran in SQL Server Management Studio:
BEGIN TRANSACTION [Tran1]
BEGIN TRY
UPDATE SomeTable
SET value = 0
WHERE username = 'test'
OR [PR_RequestDate] < DATEADD(day, -2, GETDATE())
INSERT INTO SomeTable (username, value)
VALUES('test', 'test')
COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Tran1]
END CATCH
GO
After executing it every other query that I try to run on the same table runs forever, and when I try to exist SQL Server Management Studio, I get a warning that there are uncommitted transactions.
What exactly is wrong with my query and how should I fix it?
Typos will be caught immediately,further as per Ivan suggestionmyou are not showing us entire info.You are getting uncommitted transactions error due to error in update or insert and the entire query trying to rollback..You can do the follow to get total message and trancount,,
set xact_abort on--to rollback entire batch,rollback in catch is redundant
begin try
begin tran
commit
end try
begin catch
select ##trancount--gives me number of open transactions
select error_message()
rollback tran
end catch

Exception Handling not handling Error in SQL-SERVER

We are handling Exceptions in our stored procedures using Try/Catch block. In a stored procedure I have intentionally made an error to check how the exceptions being handled. I wrote a stored procedure like as below,
CREATE PROCEDURE TEST
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN
;WITH CTE
AS
(SELECT TOP 10 *
FROM student)
SELECT * FROM CTE
SELECT * INTO #VAL
FROM CTE
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR, ERROR_LINE() AS [Error Line], ERROR_MESSAGE() AS [Error Message]
ROLLBACK TRAN;
END CATCH
SET NOCOUNT OFF;
END
In the Above Stored procedure I have used a CTE multiple Times. I expected that SQL-SERVER will handle the exception but it didn't. While Executing the Above stored Procedure I got below error
Msg 208, Level 16, State 1, Procedure TEST, Line 16
Invalid object name 'CTE'.
Msg 266, Level 16, State 2, Procedure TEST, Line 16
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 2.
Why the Exception is not handled ? Could someone give idea on this?
Thanks for the help.
stored procedures follow deferred object name resolution while executing.So in this case cte is a non existent object to it .
Further try catch cannot handle this type of errors
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as **object name resolution errors** that occur after compilation because of deferred name resolution
Please see MSDN for more info(see errors unaffected by try catch )

Procedure resuming even after the error

Below is my procedure in SQL Server 2005
PROCEDURE [dbo].[sp_ProjectBackup_Insert]
#prj_id bigint
AS
BEGIN
DECLARE #MSG varchar(200)
DECLARE #TranName varchar(200)
DECLARE #return_value int
-- 1. Starting the transaction
begin transaction #TranName
-- 2. Insert the records
SET IDENTITY_INSERT [PMS_BACKUP].[Common].[PROJECT] ON INSERT INTO [PMS_BACKUP].[Common].[PROJECT] ([PRJ_ID],[PRJ_NO1],[PRJ_NO2],[PRJ_NO3],[PRJ_DESC],[IS_TASKFORCE],[DATE_CREATED],[IS_APPROVED],[DATE_APPROVED],[IS_HANDEDOVER],[DATE_HANDEDOVER],[DATE_START],[DATE_FINISH],[YEAR_OF_ORDER],[CLIENT_DETAILS],[SCOPE_OF_WORK],[IS_PROPOSAL],[PRJ_MANAGER],[PRJ_NAME],[MANAGER_VALDEL],[MANAGER_CLIENT],[DEPT_ID],[locationid],[cut_off_date]) SELECT * FROM [pms].[Common].[PROJECT] T WHERE T.PRJ_ID = (#prj_id) SET IDENTITY_INSERT [PMS_BACKUP].[Common].[PROJECT] OFF IF ##ERROR <> 0 GOTO HANDLE_ERROR
SET IDENTITY_INSERT [PMS_BACKUP].[Common].[DEPARTMENT_CAP] ON INSERT INTO [PMS_BACKUP].[Common].[DEPARTMENT_CAP] ([CAP_ID],[DEPT_ID],[PRJ_ID],[IS_CAPPED],[DATE_CAPPED],[CAPPED_BY],[CAP_APPROVED_BY],[STATUS],[UNCAPPED_BY],[DATE_UNCAPPED],[DESCRIPTION],[UNCAP_APPROVED_BY],[LOCATIONID]) SELECT * FROM [pms].[Common].[DEPARTMENT_CAP] T WHERE T.PRJ_ID = (#prj_id) SET IDENTITY_INSERT [PMS_BACKUP].[Common].[DEPARTMENT_CAP] OFF IF ##ERROR <> 0 GOTO HANDLE_ERROR
INSERT INTO [PMS_BACKUP].[Common].[DOC_REG] SELECT * FROM [pms].[Common].[DOC_REG] T WHERE T.PRJ_ID = (#prj_id) IF ##ERROR <> 0 GOTO HANDLE_ERROR
-- 3. Commit transaction
COMMIT TRANSACTION #TranName;
return ##trancount;
HANDLE_ERROR:
rollback transaction #TranName
RETURN 1
END
and the issue is even if the first insert query fails, its not stopping the processing and resume the rest of the insert queries. The return value I am getting is 1, but in the results window I can see the log like this
(0 row(s) affected) Msg 2627, Level
14, State 1, Procedure
sp_ProjectBackup_Insert, Line 35
Violation of PRIMARY KEY constraint
'PK_PROJECT'. Cannot insert duplicate
key in object 'Common.PROJECT'. The
statement has been terminated.
(0 row(s) affected)
(0 row(s) affected)
I thought the return 1 will make the exit from error handling code but not happening. Any problem with my error handling?
There are so many things wrong with this I don't know where to start.
As far as your error call, you are trapping whether there is an error on the last step run before the error, not if any error has occurred so far. Since the last step is not the insert but the set_identity_insert statement, there is no error to trap.
Now, on to what needs fixing besides that.
If this is a backup table and is only used as a backup table, get rid of the identity property all together. No need to keep turning the insert on and off, just fix the table, it is not being directly written to by users data is alawys coming from another table, so why does it need an identity at all?
Next, the error you got indicates to me that what you need to be doing is inserting only records that don't already exist in the backup table not all records. You may also need to update existing records. Or you need to truncate the table first before doing the insert if you only need the most current data period and the data table being copied is not that large (you don't want to re-enter a million records when only 100 were new and 10 were changed).
In SQL Server 2005 you have TRY CATCH blocks available, you should start using those instead of goto.
Never, ever, ever use SELECT * in an insert. Or any time the code will go to production. Select * is a very poor programming technique. In the insert for instance it will cause problems when the initial table is changed as you define the columns to insert into but not those in the select.
Finally, you should not name stored procedures with sp at the start. System procs start with sp and SQL Server will look there first for the proc before looking at user procs. It's a little wasted time every time you call a proc. Overall it's bad for the system and if they happen to have a system proc with the same name yours will never be called.
You need to put proper error handling around your statements. With SQL 2005 and up, that means try/catch:
PROCEDURE [dbo].[sp_ProjectBackup_Insert]
#prj_id bigint
AS
BEGIN
DECLARE #MSG varchar(200)
DECLARE #TranName varchar(200)
DECLARE #return_value int
-- 1. Starting the transaction
BEGIN TRANSACTION #TranName
-- 2. Insert the records
BEGIN TRY
SET IDENTITY_INSERT [PMS_BACKUP].[Common].[PROJECT] ON INSERT INTO [PMS_BACKUP].[Common].[PROJECT] ([PRJ_ID],[PRJ_NO1],[PRJ_NO2],[PRJ_NO3],[PRJ_DESC],[IS_TASKFORCE],[DATE_CREATED],[IS_APPROVED],[DATE_APPROVED],[IS_HANDEDOVER],[DATE_HANDEDOVER],[DATE_START],[DATE_FINISH],[YEAR_OF_ORDER],[CLIENT_DETAILS],[SCOPE_OF_WORK],[IS_PROPOSAL],[PRJ_MANAGER],[PRJ_NAME],[MANAGER_VALDEL],[MANAGER_CLIENT],[DEPT_ID],[locationid],[cut_off_date]) SELECT * FROM [pms].[Common].[PROJECT] T WHERE T.PRJ_ID = (#prj_id) SET IDENTITY_INSERT [PMS_BACKUP].[Common].[PROJECT] OFF IF ##ERROR <> 0 GOTO HANDLE_ERROR
SET IDENTITY_INSERT [PMS_BACKUP].[Common].[DEPARTMENT_CAP] ON INSERT INTO [PMS_BACKUP].[Common].[DEPARTMENT_CAP] ([CAP_ID],[DEPT_ID],[PRJ_ID],[IS_CAPPED],[DATE_CAPPED],[CAPPED_BY],[CAP_APPROVED_BY],[STATUS],[UNCAPPED_BY],[DATE_UNCAPPED],[DESCRIPTION],[UNCAP_APPROVED_BY],[LOCATIONID]) SELECT * FROM [pms].[Common].[DEPARTMENT_CAP] T WHERE T.PRJ_ID = (#prj_id) SET IDENTITY_INSERT [PMS_BACKUP].[Common].[DEPARTMENT_CAP] OFF IF ##ERROR <> 0 GOTO HANDLE_ERROR
INSERT INTO [PMS_BACKUP].[Common].[DOC_REG] SELECT * FROM [pms].[Common].[DOC_REG] T WHERE T.PRJ_ID = (#prj_id) IF ##ERROR <> 0 GOTO HANDLE_ERROR
-- 3. Commit transaction
COMMIT TRANSACTION #TranName;
RETURN 0
END TRY
BEGIN CATCH
--HANDLE_ERROR
ROLLBACK TRANSACTION #TranName
RETURN 1
END CATCH
END
(Be sure to test and debug this -- should be good, but you never know.)
The RETURN value is only relevant to whatever called the procedure -- if it's not checking for success or failure, then you may have a problem.