I have a MSSSQL stored procedure performing a distributed transaction that looks like this:
SET XACT_ABORT ON;
SET NOCOUNT ON;
BEGIN TRY
BEGIN DISTRIBUTED TRANSACTION
insert into LNKSRV.INST.dbo.zz (id, val) values (1, 'a');
insert into LNKSRV.INST.dbo.zz (id, val) values (2, 'b');
COMMIT TRANSACTION
END TRY
BEGIN CATCH
if (XACT_STATE() <> 0)
BEGIN
ROLLBACK TRANSACTION;
END
print ERROR_MESSAGE();
print ERROR_LINE();
print ERROR_SEVERITY();
END CATCH
This works fine.
If I add this 3rd insert statement:
insert into LNKSRV.INST.dbo.zz (id, val) values ('error', 'b');
...it fails correctly -- the transaction is rolled back on the remote server and control passes to the CATCH block and I get information about the error (can't convert 'error' to int).
But if I add this insert statement:
insert into LNKSRV.INST.dbo.zz (id, val) values (-1, 'b');
..and I have a check contraint on the remote table requiring values > 0 in the id column, then things do not work as I expect. The transaction DOES roll back, but control DOES NOT transfer to the catch block. Instead, execution just dies and this is printed to the output window:
The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled the distributed transaction
Why? I need to log these errors in the catch blog.
Since the distributed transaction coordinator is handling this, when the transaction fails on the distributed part of the transaction, the DTC sends a message in the form of an attention, which stops your code from executing, and which the TRY/CATCH cannot process.
SQL Server can detect on your end when you are trying to insert an incorrect data type into a table (even on a remote instance) but the constraint is processed on the linked server, which causes the attention to be sent to DTC and your TRY/CATCH to be ignored.
For more information see the first "Note" section in the "Using TRY...CATCH in Transact-SQL" section of SQL Server 2008 Books Online, located at:
http://msdn.microsoft.com/en-us/library/ms179296.aspx
Related
I faced one problem in this query execution on Microsoft SQL Server Management Studio
CREATE DATABASE myDB
USE myDB
CREATE TABLE udata(uid INT PRIMARY KEY identity(101, 2),
uname VARCHAR(25), unum INT UNIQUE)
CREATE TABLE usalary(sid INT PRIMARY KEY identity(1, 1),
salary NUMERIC(18, 0), FKuid INT REFERENCES udata(uid))
INSERT INTO udata VALUES ('yogesh', 99)
INSERT INTO udata VALUES ('tejas', 88)
INSERT INTO usalary VALUES (15000, 103)
BEGIN TRANSACTION
SAVE TRANSACTION SP1
DELETE FROM udata WHERE uid = 1
COMMIT;
ROLLBACK to SP1
SELECT * FROM udata WHERE uid=1
BEGIN TRANSACTION
SAVE TRANSACTION SP2
TRUNCATE TABLE usalary
COMMIT
ROLLBACK to SP2
SELECT * FROM usalary
here when we commit the transaction it should be saved in database but after rollback the data will come back.How's that possible??
Here my question is simple.
in SQL documentation it is mentioned that after commiting any query or transaction we can not rollback.(we can not get our previous state of database.)
like if we create savepoint a and perform delete query on our database and explicitly give commit.
the documentation say that we can't rollback from this state but if i execute rollback command here I get my data back.
The whole series of command(query) is mentioned here for ease who wants to help from create database command to rollback command.
For a query like this:
BEGIN TRANSACTION
SAVE TRAN t1
DELETE FROM udata;
COMMIT;
ROLLBACK TRANSACTION t1
You will get an error: The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. Sure, no rollback after a commit.
But if you try to wrap it in another transaction:
BEGIN TRANSACTION
BEGIN TRANSACTION
SAVE TRAN t1
DELETE FROM udata;
COMMIT;
ROLLBACK TRANSACTION t1
COMMIT
This will work fine. Why? documentation on Nesting Transactions says:
Committing inner transactions is ignored by the SQL Server Database Engine. The transaction is either committed or rolled back based on the action taken at the end of the outermost transaction.
My guess here is: either your not posting whole query here and there is a BEGIN TRANSACTION statement somewhere else or you have been testing transactions inside Management Studio and somewhere along the way you executed BEGIN TRANSACTION without matching ROLLBACK or COMMIT. In the later case, try to execute single ROLLBACK statement until you get an error. You can also just restart Management Studio.
While inserting multiple rows into a table in SQL Server, if a get an error, how can I just log that error and move on to insert the next record by bypassing that error?
You can't when all the insert run in one go (like in a insert into ... select. A database implements ACID, and one of the components (A, Atomicy) means (according to Wikipedia):
Atomicity requires that each transaction is "all or nothing"
That means that you have to separate your action in single statements, which in SQL Server are single transactions (in Oracle on the other hand, this won't still work since it runs all subsequent queries in one transaction by default).
You can although create separate insert statements and put every statement in a try catch.
Sample:
-- first
begin try
insert into ...
end try
begin catch
-- log error
end catch;
-- second
begin try
insert into ...
end try
begin catch
-- log error
end catch;
1/ The following code snippet show me the expected error: The INSERT statement conflicted with the FOREIGN KEY constraint FK_...
SET XACT_ABORT ON;
BEGIN TRANSACTION
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([Col1], [Col2])
VALUES (1200, 0)
COMMIT TRANSACTION
2/ But when I put this in a BEGIN TRY/CATCH, the error message is vague: Msg 1206, Level 18, State 118, Line 18
The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled the distributed transaction.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
-- Error is on this line
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([IdWebsite], [IdProductType])
VALUES (1200, 0)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Error' -- Code not reached
SELECT ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
IF XACT_STATE() != 0
ROLLBACK TRANSACTION
END CATCH
Any idea why this happens?
Later edit:
It works in case I remove the unneeded explicit transaction. It is still not clear why I get this error when I put BEGIN/COMMIT TRAN.
I get the same error in case I have multiple inserts in multiple tables situated on linked server.
Any comment / remark is welcomed.
From MSDN:
SYMPTOMS
Consider the following scenario. You use the SQL Native Client OLE DB provider (SQLNCLI) in SQL Server 2005 to create a linked server. You create a distributed transaction. The distributed transaction contains a query that uses the linked server to retrieve data from a table. When you commit the distributed transaction, you may receive the following error message:
Msg 1206, Level 18, State 167, Line 3
The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled
the distributed transaction.
Additionally, you may receive the following error message when you run a query after this behavior occurs:
Msg 8525, Level 16, State 1, Line 1
Distributed transaction completed. Either enlist this session in a new
transaction or the NULL transaction.
This problem occurs if the following conditions are true:
You use the SQLNCLI provider to create a linked server between two
instances of SQL Server 2005.
The XACT_ABORT option is set to ON.
In the distributed transaction, you try to release a rowset before
all rows in the rowset are processed.
Note This problem may also occur if you call the ReleaseRows method in a distributed transaction to release a rowset before you commit a distributed transaction in an application.
CAUSE
This problem occurs because the SQLNCLI provider incorrectly sends an attention signal to the linked server to roll back the distributed transaction.
WORKAROUND
To prevent the SQLNCLI provider from sending an attention signal to the server, use the SQLNCLI provider to consume fully any rowsets that the OLE DB consumer creates.
Update
you need to configure 'remote proc trans' to "1" in server parameters.
Ex:
exec sp_configure 'remote proc trans','1'
reconfigure with override
This will permmit you to execute any distributed queries.
More Update
If you are using .Net framework in front end too, then I think you can use
TransactionScope Class. Remove transaction from query and put the Transaction in code level.
I have went to through this pain!
If you are performing any CRUD operation on a single table TRANSACTION is not needed.
In this case, the problem is, XACT_STATE() returns -1 because there is an error in the active transaction. But, ROLLBACK TRANSACTION fails, since there is NO transactions happened. You did only one transaction, INSERT, which failed, so there are no other transactions to rollback.
Its always better to relay on ##TRANCOUNT than XACT_STATE() (at least in this case).
to make it work, change like this(though I don't support TRAN for single table):
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
BEGIN TRANSACTION starts a distributed transaction between the server running the statements and the linked server, since potentially you can run updates against both servers. When the INSERT fails it needs to cancel the distributed transaction, thus the error you are getting. So you have to handle errors on two levels (insert and transaction). In this scenario, you'll need two TRY/CATCH blocks as follows:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
BEGIN TRY
-- Error is on this line
INSERT INTO linkedsrv1.db1.[dbo].tbl1 ([IdWebsite], [IdProductType])
VALUES (1200, 0)
END TRY
BEGIN CATCH
SELECT 'Insert Error', ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
RAISERROR (15600,-1,-1, 'INSERT ERROR');
END CATCH
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT 'Transaction Error', ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE()
IF XACT_STATE() != 0
ROLLBACK TRANSACTION
END CATCH
I'm trying to insert a duplicate value in to a primary key column which raises a primary key violation error.I want to log this error inside the catch block .
Code Block :-
SET XACT_ABORT OFF
BEGIN TRY
BEGIN TRAN
INSERT INTO #Calender values (9,'Oct')
INSERT INTO #Calender values (2,'Unknown')
COMMIT TRAN
END TRY
BEGIN CATCH
Insert into #LogError values (1,'Error while inserting a duplicate value')
if ##TRANCOUNT >0
rollback tran
raiserror('Error while inserting a duplicate value ',16,20)
END CATCH
when i execute the above code it prints out the custom error message which is displayed in the catch block but doesn't insert the value in to the #LogError table
Error while inserting a duplicate value
But when i use SET XACT_ABORT ON i get a different error message but still it doesn't inserts the error message into the table
The current transaction cannot be committed and cannot support operations
that write to the log file. Roll back the transaction.
My question is
1.How to log error into the table
2.Why do i get different error message when i set xact_ABORT on .Is it a good practice to set XACT_ABORT on before every transaction
It does insert the record into #LogError but then you rollback the transaction which removes it.
You need to do the insert after the rollback or insert into a table variable instead (that are not affected by the rollback).
When an error is encountered in the try block this can leave your transaction in a doomed state. You should test the value of XACT_STATE() (see example c in the TRY ... CATCH topic) in the catch block to check for this before doing anything that writes to the log or trying to commit.
When XACT_ABORT is on any error of severity > 10 in a try block will have this effect.
As SqlServer doesn't support Autonomous transaction (nested and independent transaction), it's not possible (in fact, you can, under some condition, use CLR SP with custom connectstring - doing it's own, non local, connection) to use a database table to log SP execution activity/error messages.
To fix, this missing functionnality, I've developed a toolbox (100% T-SQL) based on the use of XML parameter passed as reference (OUTPUT parameter) which is filled during SP execution and can be save into a dedicated database table at the end.
Disclamer: I'm a Iorga employee (cofounder) and I've developped the following LGPL v3 toolbox. My objective is not Iorga/self promotion but sharing knowledge and simplify T-SQL developper life.
See, teach, enhance as you wish SPLogger
Today (October 19th of 2015) I've just released the 1.3 including a Unit Test System based on SPLogger.
I have needs to keep some of log data in different tables even my transaction is rolled back.
I already learned that in SQL Server it is impossible do something like this
begin tran t1
insert ...
insert ...
select ...
begin tran t2
insert into log
commit tran t2
rollback tran t1
select * from log -- IS EMPTY ALWAYS
So I try hacking SQL Server that I madded CLR which is going to export data need for LOG to local server disk in XML format. CLR Code is simple as it can be:
File.WriteAllText(fileName, xmlLog.Value.ToString());
Before I release this in production bases Ill love to hear your toughs about this technique.
Here are few questions:
Is there other better way to accomplish autonomous transaction in SQL Server 2005
How can be bad holding my transaction uncommitted while SQL Server is executing CLR (amount of data written by SQL is relative small about 50 - 60 records of 3 integers and 4 floats)
I would suggest using a Table Variable as it is not affected by the Transaction (this is one of the methods listed in the blog noted by Martin below the question). Consider doing this, which will work in SQL Server 2005:
DECLARE #TempLog TABLE (FieldList...)
BEGIN TRY
BEGIN TRAN
INSERT...
INSERT INTO #TempLog (FieldList...) VALUES (#Variables or StaticValues...)
INSERT...
INSERT INTO #TempLog (FieldList...) VALUES (#Variables or StaticValues...)
COMMIT TRAN
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN
END
/* Maybe add a Log message to note that we ran into an error */
INSERT INTO #TempLog (FieldList...) VALUES (#Variables or StaticValues...)
END CATCH
INSERT INTO RealLogTable (FieldList...)
SELECT FieldsList
FROM #TempLog
Please note that while we are making use of the fact that Table Variables are not part of the transaction, that does create a potential situation where this code does a COMMIT but errors (or server crashes) before the INSERT INTO RealLogTable and you will have lost the logging for the data that did make it in. At this point there would be a disconnect as there is data but no record of it being inserted as far as RealLogTable is concerned. But this is just the obvious trade-off for being able to bypass the Transaction.