The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction? - sql

Hello Every One The Following Error Comes To Me Suddenly When I Changed Simple And Few Thing On My Stored Procedure Code And It Was Working Great And If I Cleared The Updated Code It Works Fine Again So I Do Not Know What Is The Reason
And Here Is The Error "The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction"
Thanks In Advance
ALTER Procedure [dbo].[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
#English Bit = 0 ,
#ID BigInt = NULL Output ,
#Company_ID SmallInt = NULL ,
#Cust_ID NVarChar (20) = NULL ,
#Cust_Cat NVarChar (10) = NULL ,
#Debit_Limit Decimal (18,3) = NULL ,
#From_Date NVarChar (19) = NULL ,
#To_Date NVarChar (19) = NULL ,
#User_ID NVarChar(50) = NULL
As
BEGIN
Create Table #Errors (ErrorNumber int Not Null, ErrorValue nvarchar(300) Collate Arabic_CI_AS Null);
Begin Tran trn_INV_CustDebLim_setupInsert;
Begin Try
Insert Into INV_Cust_Debit_Limit_setup
( Company_ID , Cust_ID , Cust_Cat , Debit_Limit , From_Date , To_Date )
Values
( #Company_ID , #Cust_ID , #Cust_Cat , #Debit_Limit , #From_Date , #To_Date )
set #ID = ##IDENTITY
-- From Here Is New Part --
declare #str nvarchar(50)
IF(#Cust_ID IS NOT NULL)
set #str = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_Cat
set #str += ' xxxxx' +#Debit_Limit + ' xxxx xx' +#From_Date
IF(#English = 1)
BEGIN
IF(#Cust_ID IS NOT NULL)
set #str = 'Credit Limit Added On Customer ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'Credit Limit Added On Customer Category ' + #Cust_Cat
set #str = ' With Value ' +#Debit_Limit + ' From Date ' +#From_Date
END
exec usp_GLApplicationAudit #Company_ID , NULL , 10 , #User_ID , #str ,#ID ,10, NULL , NULL ,NULL ,NULL,NULL,'SAl'
-- To Here Is New Part --
End Try
Begin Catch
Insert #Errors Values(1002,ERROR_MESSAGE());
Goto Finished;
End Catch
-- Return Error Records
Finished:
-- retrieve Errors and commit/Rollbak Trans.
If (Select count(E.ErrorNumber)
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number
Where G.Err_Type=0 ) > 0
Begin
-- The Following are Errors
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Rollback Tran trn_INV_CustDebLim_setupInsert;
End
Else
Begin
-- The Following Not Errors They are Warnings or Information
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Commit Tran trn_INV_CustDebLim_setupInsert;
End
DROP Table #Errors;
END

In the CATCH code you must check the state of XACT_STATE() and act accordingly. For a procedure template that handles transactions and try/catch blocks correctly see Exception Handling and Nested Transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
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 usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end

Related

How to figure out which part of query is causing error?

After Running this query, I am getting one custom Error :
Debit account balance can not be less than 0. somemail#7dmail.com/123/xxx/123456
And Two regular errors :
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 2, current count = 0.
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
I think, that transaction errors happen because something is throwing exception before transaction is committed.
Custom Error is from another query(AddJournalEntry) which is written below. I can not see connection between these two queries.
Query:
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
--,#EntryId int OUTPUT
AS
declare #OrderTypeId nvarchar(30)
declare #MasterEntryId int
declare #NewEntryId int
declare #CustomerGuid uniqueidentifier
declare #Debit int
declare #Credit int
declare #Explanation nvarchar(100)
declare #Amount decimal(18, 8)
declare #AmountFilled decimal(18, 8)
declare #Total decimal(18, 8)
declare #TotalLeft decimal(18, 8)
declare #AmountLeft decimal(18, 8)
declare #AssetId nvarchar(30)
declare #QuoteAssetId nvarchar(30)
declare #QuotePrice decimal(18, 8)
declare #AssetReserveAccountId int
declare #AssetAccountId int
declare #EntryAmount decimal(18, 8)
BEGIN;
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET XACT_ABORT ON;
SET NOCOUNT ON;
print ''
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print 'Start RevokeOrder procedure'
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print ''
begin tran
select
#OrderTypeId = OrderTypeId,
#CustomerGuid = CustomerGuid,
#Amount = Amount,
#AmountFilled = AmountFilled,
#AssetId = AssetId,
#QuoteAssetId = QuoteAssetId,
#QuotePrice = QuotePrice,
#Total = Total,
#TotalLeft = TotalLeft
from dbo.[vOrder] WITH (HOLDLOCK, ROWLOCK)
where OrderId = #OrderId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Order not found', 16, 1)
return 3
end
set #AmountLeft = #Amount - ISNULL(#AmountFilled, 0)
if (#OrderTypeId = 'buy')
begin
--declare #Released decimal(18, 8)
--select #Released = coalesce(SUM(Total), 0)
--from dbo.[vOrder]
--where OrderId in (select FillerId from dbo.Filler where OrderId = #OrderId)
--or OrderId in (select OrderId from dbo.Filler where FillerId = #OrderId)
declare #Released decimal(18, 8)
select #Released = Amount
from dbo.Trade t join dbo.JournalEntry j on t.EntryId = j.EntryId
where SourceOrderId = #OrderId
set #Released = ISNULL(#Released, 0)
print 'Order Type = ' + #OrderTypeId
print '#InitialAmount = ' + isnull(cast (#Amount as nvarchar), 'NULL') + ' ' + #AssetId
print '#AmountFilled = ' + isnull(cast (#AmountFilled as nvarchar), 'NULL') + ' ' + #AssetId
print '#AmountLeft = ' + isnull(cast (#AmountLeft as nvarchar), 'NULL')
print ''
print '#InitialBlocked = ' + isnull(cast (#Total as nvarchar), 'NULL') + ' ' + #QuoteAssetId
print '#Released = ' + cast (#Released as nvarchar) + ' ' + #QuoteAssetId
print '#CurrentBlocked = ' + isnull(cast (#TotalLeft as nvarchar), 'NULL') + ' ' + #QuoteAssetId
print ''
set #Explanation = N'Revoke order process. Reverse blocked quote amount' --+ 'reverse' --+ cast(#EntryId as nvarchar)
DECLARE #RC int
declare #Date datetimeoffset
set #Date = sysdatetimeoffset()
set #AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 99931 and AssetId = #QuoteAssetId)
set #AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 9993 and AssetId = #QuoteAssetId)
set #EntryAmount = #Amount * #QuotePrice - #Released
print 'Order total = ' + cast (#Amount * #QuotePrice as nvarchar)
print 'Money spent to buy asset = ' + cast (#Released as nvarchar)
print 'Money to refund to buyer = ' + cast (#EntryAmount as nvarchar)
print '#Amount = ' + cast (#Amount as nvarchar)
print '#QuotePrice = ' + cast (#QuotePrice as nvarchar)
--rollback
--return 111
EXECUTE #RC = [dbo].[AddJournalEntry]
#Date
,#AssetReserveAccountId
,#AssetAccountId
,#EntryAmount
,#QuoteAssetId
,#Explanation
,'revoke'
,#OrderId
,#MasterEntryId
,#NewEntryId OUTPUT
if ##ERROR <> 0 or #RC <> 0
begin
rollback
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
if (#OrderTypeId = 'sell')
begin
print 'sell order'
set #Explanation = N'Revoke order process. Reverse blocked amount journal entry' --+ 'რევერსი' --+ cast(#EntryId as nvarchar)
set #Date = sysdatetimeoffset()
set #AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 99931 and AssetId = #AssetId)
set #AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 9993 and AssetId = #AssetId)
set #EntryAmount = #AmountLeft
print '#EntryAmount = ' + isnull(cast (#EntryAmount as nvarchar), 'NULL')
EXECUTE #RC = [dbo].[AddJournalEntry]
#Date
,#AssetReserveAccountId
,#AssetAccountId
,#EntryAmount
,#AssetId
,#Explanation
,'revoke'
,#OrderId
,#MasterEntryId
,#NewEntryId OUTPUT
if ##ERROR <> 0 or #RC <> 0
begin
rollback
raiserror ('Revoke order process. Can not add reverse blocked amount journal entry', 16, 1)
return 5
end
end
-- STEP 4
update dbo.[Order]
set OrderStatusId = 30
where OrderId = #OrderId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not set order status to REVOKED', 16, 1)
return 2
end
commit tran
return 0
END
go
Custom error I am getting is defined in query called
AddJournalEntry
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[AddJournalEntry]
#Date datetimeoffset,
#Debit int,
#Credit int,
#Amount decimal(18, 8),
#AssetId nvarchar(50),
#Explanation nvarchar(100),
#EntryType nvarchar(50),
#OrderId int = null,
#MasterEntryId int = null,
#EntryId int OUTPUT
AS
declare #DebitBalance decimal(18, 8)
declare #DebitAccountAssetId nvarchar(10)
declare #CreditAccountAssetId nvarchar(10)
declare #CreditBalance decimal(18, 8)
declare #ToIncrease nvarchar(100)
declare #DebitAccountTitle nvarchar(500)
declare #CreditAccountTitle nvarchar(500)
declare #Error nvarchar(500)
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET XACT_ABORT ON;
SET NOCOUNT ON;
print ''
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print 'Start AddJournalEntry procedure'
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print ''
begin tran
-- STEP 1
print '#Debit = ' + isnull(cast(#Debit as nvarchar), 'NULL')
print '#Credit = ' + isnull(cast(#Credit as nvarchar), 'NULL')
print '#AssetId = ' + cast(#AssetId as nvarchar(50))
print '#Amount = ' + cast(#Amount as nvarchar(50))
update dbo.Account
set Debit = Debit + #Amount, LastTransactionDate = SYSDATETIMEOFFSET()
where AccountId = #Debit
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not update debit account balance', 16, 1)
return 2
end
update dbo.Account
set Credit = Credit + #Amount, LastTransactionDate = SYSDATETIMEOFFSET()
where AccountId = #Credit
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find or update credit account balance', 16, 1)
return 3
end
select
#DebitBalance = Balance,
#ToIncrease = ToIncrease,
#DebitAccountTitle = AccountFullTitle
from dbo.vAccount
where AccountId = #Debit and AssetId = #AssetId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find debit account', 16, 1)
return 4
end
print 'New Debit Balance = ' + cast(#DebitBalance as nvarchar(50))
if (#DebitBalance < 0 and #ToIncrease = 'debit') or (#DebitBalance > 0 and #ToIncrease = 'credit')
begin
rollback
set #Error = 'Debit account balance can not be less than 0. ' + #DebitAccountTitle
raiserror (#Error, 16, 1)
return 5
end
-- STEP 4
select
#CreditBalance = Balance,
#ToIncrease = ToIncrease,
#CreditAccountTitle = AccountFullTitle
from dbo.vAccount
where AccountId = #Credit and AssetId = #AssetId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find credit account', 16, 1)
return 55
end
if (#CreditBalance > 0 and #ToIncrease = 'credit') or (#CreditBalance < 0 and #ToIncrease = 'debit')
begin
rollback
set #Error = 'Credit account balance can not be less than 0. ' + #CreditAccountTitle
raiserror ( #Error, 16, 1)
return 56
end
-- STEP 4
insert dbo.JournalEntry
select SYSDATETIMEOFFSET(), #Debit, #Credit, #Amount, #AssetId, #Explanation, #DebitBalance, #CreditBalance, #OrderId, #MasterEntryId, #EntryType, NEWID()
if ##ERROR <> 0
begin
rollback
raiserror ('Can not insert entry record', 16, 1)
return 1
end
commit tran
set #EntryId = SCOPE_IDENTITY()
return 0
END
go
You are executing multiple ROLLBACK commands when you should only execute it once. A rollback will lower the transaction count from any amount higher than 0 directly to 0, so if you execute 3 BEGIN TRANSACTION, your ##TRANCOUNT is 3 and a rollback will set it to 0. The problem is that you are executing a rollback inside the called SP (the nested one) and again after the SP returns.
You can see the problem with this example:
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 1
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
ROLLBACK
SELECT ##TRANCOUNT -- 0
ROLLBACK -- The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
And this is the failing execution route from your SP:
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
AS
begin tran -- Create a transaction here (TRANCOUNT = 1)
if (...)
begin
EXECUTE #RC = [dbo].[AddJournalEntry] -- Executes a rollback inside
if ##ERROR <> 0 or #RC <> 0
begin
rollback -- When the execution reaches this rollback, TRANCOUNT is 0 and the rollback fails
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
END
And the SP being called:
CREATE PROCEDURE [dbo].[AddJournalEntry]
AS
BEGIN
begin tran -- TRANCOUNT = 2
if (#DebitBalance < 0 and #ToIncrease = 'debit') or (#DebitBalance > 0 and #ToIncrease = 'credit')
begin
rollback -- Undoes all changes from the start of the first BEGIN TRAN and sets TRANCOUNT to 0
set #Error = 'Debit account balance can not be less than 0. ' + #DebitAccountTitle
raiserror (#Error, 16, 1)
return 5
end
END
I'd recommend using TRY/CATCH blocks and doing the ROLLBACK on the CATCH. This would be like the following:
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
AS
BEGIN TRY
begin tran
if (...)
begin
EXECUTE #RC = [dbo].[AddJournalEntry]
if ##ERROR <> 0 or #RC <> 0
begin
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 -- Might want to check XACT State also
ROLLBACK
-- Additional logging/fixing stuff
END CATCH
END
For detailed explanation on SQL Server error handling, check this post.

Asynchronous Procedure Execution with Parameters

I have been dealing with a similar issue to the one posed in this question - except in my context I am performing the command from an ASP.NET webpage.
Remus Rusanu posted an excellent solution using Service Broker Activation on his personal website, which I was able to get working great.
However I have had no luck getting his example which accepts parameters to function. It is supposed to insert data into the withParam table.
exec usp_AsyncExecInvoke #procedureName = N'usp_withParam'
, #p1 = 1.0, #n1 = N'#id'
, #p2 = N'Foo', #n2='#name'
, #p3 = 0xBAADF00D, #n3 = '#bytes'
, #token = #token output;
waitfor delay '00:00:05';
select * from AsyncExecResults;
select * from withParam;
instead it completes without error and SELECT * withParam shows an empty table.
The SELECT * AsyncExecResults table shows the command was submitted but doesn't display any error information (as it should if there was an error).
Passing Parameters to a Background Procedure
It has been suggested in the comments of the webpage that there is an issue using quotename if you use a schema it ends up wrapping it like this [MySchema.SomeFunction]
Removing quotename has not made a difference.
select #stmt = #stmtDeclarations + #stmtValues + N'
exec ' + #x.value(N'(//procedure/name)[1]', N'sysname');
As the query is executing without error I have very little to go on to debug this further. Does anyone know what else might cause this effect?
The Code
create table [AsyncExecResults] (
[token] uniqueidentifier primary key
, [submit_time] datetime not null
, [start_time] datetime null
, [finish_time] datetime null
, [error_number] int null
, [error_message] nvarchar(2048) null);
go
create queue [AsyncExecQueue];
go
create service [AsyncExecService] on queue [AsyncExecQueue] ([DEFAULT]);
GO
-- Dynamic SQL helper procedure
-- Extracts the parameters from the message body
-- Creates the invocation Transact-SQL batch
-- Invokes the dynmic SQL batch
create procedure [usp_procedureInvokeHelper] (#x xml)
as
begin
set nocount on;
declare #stmt nvarchar(max)
, #stmtDeclarations nvarchar(max)
, #stmtValues nvarchar(max)
, #i int
, #countParams int
, #namedParams nvarchar(max)
, #paramName sysname
, #paramType sysname
, #paramPrecision int
, #paramScale int
, #paramLength int
, #paramTypeFull nvarchar(300)
, #comma nchar(1)
select #i = 0
, #stmtDeclarations = N''
, #stmtValues = N''
, #namedParams = N''
, #comma = N''
declare crsParam cursor forward_only static read_only for
select x.value(N'#Name', N'sysname')
, x.value(N'#BaseType', N'sysname')
, x.value(N'#Precision', N'int')
, x.value(N'#Scale', N'int')
, x.value(N'#MaxLength', N'int')
from #x.nodes(N'//procedure/parameters/parameter') t(x);
open crsParam;
fetch next from crsParam into #paramName
, #paramType
, #paramPrecision
, #paramScale
, #paramLength;
while (##fetch_status = 0)
begin
select #i = #i + 1;
select #paramTypeFull = #paramType +
case
when #paramType in (N'varchar'
, N'nvarchar'
, N'varbinary'
, N'char'
, N'nchar'
, N'binary') then
N'(' + cast(#paramLength as nvarchar(5)) + N')'
when #paramType in (N'numeric') then
N'(' + cast(#paramPrecision as nvarchar(10)) + N',' +
cast(#paramScale as nvarchar(10))+ N')'
else N''
end;
-- Some basic sanity check on the input XML
if (#paramName is NULL
or #paramType is NULL
or #paramTypeFull is NULL
or charindex(N'''', #paramName) > 0
or charindex(N'''', #paramTypeFull) > 0)
raiserror(N'Incorrect parameter attributes %i: %s:%s %i:%i:%i'
, 16, 10, #i, #paramName, #paramType
, #paramPrecision, #paramScale, #paramLength);
select #stmtDeclarations = #stmtDeclarations + N'
declare #pt' + cast(#i as varchar(3)) + N' ' + #paramTypeFull
, #stmtValues = #stmtValues + N'
select #pt' + cast(#i as varchar(3)) + N'=#x.value(
N''(//procedure/parameters/parameter)[' + cast(#i as varchar(3))
+ N']'', N''' + #paramTypeFull + ''');'
, #namedParams = #namedParams + #comma + #paramName
+ N'=#pt' + cast(#i as varchar(3));
select #comma = N',';
fetch next from crsParam into #paramName
, #paramType
, #paramPrecision
, #paramScale
, #paramLength;
end
close crsParam;
deallocate crsParam;
select #stmt = #stmtDeclarations + #stmtValues + N'
exec ' + quotename(#x.value(N'(//procedure/name)[1]', N'sysname'));
if (#namedParams != N'')
select #stmt = #stmt + N' ' + #namedParams;
exec sp_executesql #stmt, N'#x xml', #x;
end
go
create procedure usp_AsyncExecActivated
as
begin
set nocount on;
declare #h uniqueidentifier
, #messageTypeName sysname
, #messageBody varbinary(max)
, #xmlBody xml
, #startTime datetime
, #finishTime datetime
, #execErrorNumber int
, #execErrorMessage nvarchar(2048)
, #xactState smallint
, #token uniqueidentifier;
begin transaction;
begin try;
receive top(1)
#h = [conversation_handle]
, #messageTypeName = [message_type_name]
, #messageBody = [message_body]
from [AsyncExecQueue];
if (#h is not null)
begin
if (#messageTypeName = N'DEFAULT')
begin
-- The DEFAULT message type is a procedure invocation.
--
select #xmlBody = CAST(#messageBody as xml);
save transaction usp_AsyncExec_procedure;
select #startTime = GETUTCDATE();
begin try
exec [usp_procedureInvokeHelper] #xmlBody;
end try
begin catch
-- This catch block tries to deal with failures of the procedure execution
-- If possible it rolls back to the savepoint created earlier, allowing
-- the activated procedure to continue. If the executed procedure
-- raises an error with severity 16 or higher, it will doom the transaction
-- and thus rollback the RECEIVE. Such case will be a poison message,
-- resulting in the queue disabling.
--
select #execErrorNumber = ERROR_NUMBER(),
#execErrorMessage = ERROR_MESSAGE(),
#xactState = XACT_STATE();
if (#xactState = -1)
begin
rollback;
raiserror(N'Unrecoverable error in procedure: %i: %s', 16, 10,
#execErrorNumber, #execErrorMessage);
end
else if (#xactState = 1)
begin
rollback transaction usp_AsyncExec_procedure;
end
end catch
select #finishTime = GETUTCDATE();
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
if (#token is null)
begin
raiserror(N'Internal consistency error: conversation not found', 16, 20);
end
update [AsyncExecResults] set
[start_time] = #starttime
, [finish_time] = #finishTime
, [error_number] = #execErrorNumber
, [error_message] = #execErrorMessage
where [token] = #token;
if (0 = ##ROWCOUNT)
begin
raiserror(N'Internal consistency error: token not found', 16, 30);
end
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
begin
declare #errorNumber int
, #errorMessage nvarchar(4000);
select #xmlBody = CAST(#messageBody as xml);
with xmlnamespaces (DEFAULT N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
select #errorNumber = #xmlBody.value ('(/Error/Code)[1]', 'INT'),
#errorMessage = #xmlBody.value ('(/Error/Description)[1]', 'NVARCHAR(4000)');
-- Update the request with the received error
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
update [AsyncExecResults] set
[error_number] = #errorNumber
, [error_message] = #errorMessage
where [token] = #token;
end conversation #h;
end
else
begin
raiserror(N'Received unexpected message type: %s', 16, 50, #messageTypeName);
end
end
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if (#xactState <> 0)
begin
rollback;
end;
raiserror(N'Error: %i, %s', 1, 60, #error, #message) with log;
end catch
end
go
alter queue [AsyncExecQueue]
with activation (
procedure_name = [usp_AsyncExecActivated]
, max_queue_readers = 1
, execute as owner
, status = on);
go
-- Helper function to create the XML element
-- for a passed in parameter
create function [dbo].[fn_DescribeSqlVariant] (
#p sql_variant
, #n sysname)
returns xml
with schemabinding
as
begin
return (
select #n as [#Name]
, sql_variant_property(#p, 'BaseType') as [#BaseType]
, sql_variant_property(#p, 'Precision') as [#Precision]
, sql_variant_property(#p, 'Scale') as [#Scale]
, sql_variant_property(#p, 'MaxLength') as [#MaxLength]
, #p
for xml path('parameter'), type)
end
GO
-- Invocation wrapper. Accepts arbitrary
-- named parameetrs to be passed to the
-- background procedure
create procedure [usp_AsyncExecInvoke]
#procedureName sysname
, #p1 sql_variant = NULL, #n1 sysname = NULL
, #p2 sql_variant = NULL, #n2 sysname = NULL
, #p3 sql_variant = NULL, #n3 sysname = NULL
, #p4 sql_variant = NULL, #n4 sysname = NULL
, #p5 sql_variant = NULL, #n5 sysname = NULL
, #token uniqueidentifier output
as
begin
declare #h uniqueidentifier
, #xmlBody xml
, #trancount int;
set nocount on;
set #trancount = ##trancount;
if #trancount = 0
begin transaction
else
save transaction usp_AsyncExecInvoke;
begin try
begin dialog conversation #h
from service [AsyncExecService]
to service N'AsyncExecService', 'current database'
with encryption = off;
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
select #xmlBody = (
select #procedureName as [name]
, (select * from (
select [dbo].[fn_DescribeSqlVariant] (#p1, #n1) AS [*]
WHERE #p1 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p2, #n2) AS [*]
WHERE #p2 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p3, #n3) AS [*]
WHERE #p3 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p4, #n4) AS [*]
WHERE #p4 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p5, #n5) AS [*]
WHERE #p5 IS NOT NULL
) as p for xml path(''), type
) as [parameters]
for xml path('procedure'), type);
send on conversation #h (#xmlBody);
insert into [AsyncExecResults]
([token], [submit_time])
values
(#token, getutcdate());
if #trancount = 0
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048)
, #xactState smallint;
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if #xactState = -1
rollback;
if #xactState = 1 and #trancount = 0
rollback
if #xactState = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror(N'Error: %i, %s', 16, 1, #error, #message);
end catch
end
go
-- Sample invocation example
-- The usp_withParam will insert
-- all the received parameters into this table
--
create table [withParam] (
id numeric(4,1) NULL
, name varchar(150) NULL
, date datetime NULL
, value int NULL
, bytes varbinary(max) NULL);
go
create procedure usp_withParam
#id numeric(4,1)
, #name varchar(150)
, #date datetime = NULL
, #value int = 0
, #bytes varbinary(max) = NULL
as
begin
insert into [withParam] (
id
, name
, date
, value
, bytes)
select #id as [id]
, #name as [name]
, #date as [date]
, #value as [value]
, #bytes as [bytes]
end
go
declare #token uniqueidentifier;
exec usp_AsyncExecInvoke #procedureName = N'usp_withParam'
, #p1 = 1.0, #n1 = N'#id'
, #p2 = N'Foo', #n2='#name'
, #p3 = 0xBAADF00D, #n3 = '#bytes'
, #token = #token output;
waitfor delay '00:00:05';
select * from AsyncExecResults;
select * from withParam;
go
It does appear that Remus's blog post neglected to note that we need to use SET ENABLE_BROKER to make the async tasks actually run.
This worked for me (only run this if you are OK with all connections to your DB being closed, this command is from SET ENABLE_BROKER never completes in SQL Server):
ALTER DATABASE [Database_name] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;
After running the command above I ran this and was happy to see that the "start_time", "finish_time" and if there were errors the "error_message" columns are now populated!
SELECT * FROM [dbo].[AsyncExecResults]
Edit: See also, this file for the complete code (which the author posted from his blog post) which worked for me once I used the "SET ENABLE_BROKER" command above): https://github.com/rusanu/async_tsql/blob/master/src/with_params.sql

Problems with ROLLBACK TRANSACTION inside try/catch

I'm having this error when I try to execute this code:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
I know the problem is on the sp [dbo].[QueueInsert], the sp has an error and it goes directly to the catch. It's funny because the first one (EXEC [dbo].[BacthInsert]) is inserting a summary record and after running the sp, I see the record, so is doing a commit and not doing a rollback. What's wrong with this code? thanks!
CREATE PROCEDURE [cnfg].[SendEmail]
(
,#Employees [dbo].[Employees] readonly
,#ApplicationName NVARCHAR(256)
,#ErrorMsg NVARCHAR(300) = NULL OUTPUT
)
AS
BEGIN
DECLARE #ReturnVal INT
DECLARE #ApplicationId UNIQUEIDENTIFIER
DECLARE #NewIdBatch INT
DECLARE #ID INT
DECLARE #EmployeeId INT
DECLARE #Index INT = 1
DECLARE #Total INT
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET #ReturnVal = 0;
SET #ErrorMsg = '';
SET #ApplicationId = [GetId](#ApplicationName);
IF (#ApplicationId IS NULL)
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'The Application Name does not exist in the database';
Goto ProcedureExit
END
----------------------------------------------------------
BEGIN TRY -- Start Main TRY
----------------------------------------------------------
BEGIN TRANSACTION;
EXEC [dbo].[BacthInsert]
#ParameterId = 1
,#ID = #NewSendEmailBatchId OUTPUT
,#ErrorMsg = #ErrorMsg OUTPUT
IF ( #ErrorMsg <> '' )
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'There was an error trying to insert data into [dbo].[BacthInsert] table';
RAISERROR(#ErrorMsg, 16, 1)
END
SELECT ROW_NUMBER() OVER ( ORDER BY EmployeeId ) Row,
EmployeeId
INTO #EmpIds
FROM #Employees
SELECT #Total = COUNT(*) FROM #EmpIds
WHILE ( #Index <= #Total )
BEGIN
SELECT #EmployeeId=EmployeeId FROM #EmpIds WHERE Row = #Index
EXEC [dbo].[QueueInsert]
#SendEmailBatchId = #NewIdBatch
,#ID = #ID OUTPUT
,#ErrorMsg = #ErrorMsg OUTPUT
IF ( #ErrorMsg <> '' )
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'There was an error trying to insert data into [dbo].[QueueInsert] table';
RAISERROR(#ErrorMsg, 16, 1)
END
SET #Index+=1;
END
COMMIT TRANSACTION;
----------------------------------------------------------
END TRY -- End Main TRY
----------------------------------------------------------
----------------------------------------------------------
BEGIN CATCH -- Start Main CATCH
----------------------------------------------------------
SELECT ERROR_MESSAGE()
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION;
END;
----------------------------------------------------------
END CATCH -- End Main CATCH
----------------------------------------------------------
ProcedureExit:
RETURN #ReturnVal;
END

does it run anything after a catch block?

I'm working with SQL Server Express 2012 and I have this stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[usp_AsyncExecActivated]
as
begin
set nocount on;
declare #h uniqueidentifier
, #messageTypeName sysname
, #messageBody varbinary(max)
, #xmlBody xml
, #procedureName sysname
, #startTime datetime
, #finishTime datetime
, #execErrorNumber int
, #execErrorMessage nvarchar(2048)
, #xactState smallint
, #token uniqueidentifier;
begin transaction;
begin try;
receive top(1)
#h = [conversation_handle]
, #messageTypeName = [message_type_name]
, #messageBody = [message_body]
from [AsyncExecQueue];
if (#h is not null)
begin
if (#messageTypeName = N'DEFAULT')
begin
-- The DEFAULT message type is a procedure invocation.
-- Extract the name of the procedure from the message body.
--
select #xmlBody = CAST(#messageBody as xml);
select #procedureName = #xmlBody.value(
'(//procedure/name)[1]'
, 'sysname');
update dbo.Configurations with (serializable) set conf_value = 1
where sp_name = #procedureName
if ##rowcount = 0
begin
insert dbo.Configurations(sp_name, conf_value) values (#procedureName, 1)
end
save transaction usp_AsyncExec_procedure;
select #startTime = GETUTCDATE();
begin try
exec #procedureName;
end try
begin catch
-- This catch block tries to deal with failures of the procedure execution
-- If possible it rolls back to the savepoint created earlier, allowing
-- the activated procedure to continue. If the executed procedure
-- raises an error with severity 16 or higher, it will doom the transaction
-- and thus rollback the RECEIVE. Such case will be a poison message,
-- resulting in the queue disabling.
--
select #execErrorNumber = ERROR_NUMBER(),
#execErrorMessage = ERROR_MESSAGE(),
#xactState = XACT_STATE();
if (#xactState = -1)
begin
rollback;
raiserror(N'Unrecoverable error in procedure %s: %i: %s', 16, 10,
#procedureName, #execErrorNumber, #execErrorMessage);
end
else if (#xactState = 1)
begin
rollback transaction usp_AsyncExec_procedure;
end
end catch
select #finishTime = GETUTCDATE();
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
if (#token is null)
begin
raiserror(N'Internal consistency error: conversation not found', 16, 20);
end
update [AsyncExecResults] set
[start_time] = #starttime
, [finish_time] = #finishTime
, [error_number] = #execErrorNumber
, [error_message] = #execErrorMessage
where [token] = #token;
if (0 = ##ROWCOUNT)
begin
raiserror(N'Internal consistency error: token not found', 16, 30);
end
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
begin
declare #errorNumber int
, #errorMessage nvarchar(4000);
select #xmlBody = CAST(#messageBody as xml);
with xmlnamespaces (DEFAULT N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
select #errorNumber = #xmlBody.value ('(/Error/Code)[1]', 'INT'),
#errorMessage = #xmlBody.value ('(/Error/Description)[1]', 'NVARCHAR(4000)');
-- Update the request with the received error
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
update [AsyncExecResults] set
[error_number] = #errorNumber
, [error_message] = #errorMessage
where [token] = #token;
end conversation #h;
end
else
begin
raiserror(N'Received unexpected message type: %s', 16, 50, #messageTypeName);
end
end
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if (#xactState <> 0)
begin
rollback;
end;
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
raiserror(N'Error: %i, %s', 1, 60, #error, #message) with log;
end catch
update dbo.Configurations with (serializable) set conf_value = 0
where sp_name = #procedureName
end
I have to do this:
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
Every time before stored procedure ends. I'll check dbo.Configurations to see if usp_AsyncExecActivated is running or not.
Do I have to add that update on CATCH BLOCK and after the CATCH BLOCK?
I'm not sure if after catch block runs anything else or it ends stored procedure execution.
It depends on severity, if there's a session sborting error it will stop executing the procedure. Otherwise, it will continue after CATCH block. See this simplified example:
create proc x
as
begin try
select 1/0
end try
begin catch
select error_message()
raiserror (N'Received unexpected message type', 16, 50);
end catch
select 'after catch'
go
exec x;

Getting Request Timeout sometimes from only one SP?

I have a lot of stored procedures. But I am only getting Request Timeout sometimes only for this SP ?
ALTER PROCEDURE [dbo].[Insertorupdatedevicecatalog]
(#OS NVARCHAR(50)
,#UniqueID VARCHAR(500)
,#Longitude FLOAT
,#Latitude FLOAT
,#Culture VARCHAR(10)
,#Other NVARCHAR(200)
,#IPAddress VARCHAR(50)
,#NativeDeviceID VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
DECLARE #OldUniqueID VARCHAR(500) = ''-1'';
SELECT #OldUniqueID = [UniqueID] FROM DeviceCatalog WHERE (#NativeDeviceID != '''' AND [NativeDeviceID] = #NativeDeviceID);
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION Insertorupdatedevicecatalog;
DECLARE #Geo GEOGRAPHY = geography::STGeomFromText(''POINT('' + CONVERT(VARCHAR(100), #Longitude) + '' '' + CONVERT(VARCHAR(100), #Latitude) + '')'', 4326);
IF EXISTS(SELECT 1 FROM DeviceCatalog WHERE [UniqueID] = #UniqueID)
BEGIN
DECLARE #OldGeo GEOGRAPHY
,#OldCity NVARCHAR(100)
,#OldCountry NVARCHAR(100)
,#OldAddress NVARCHAR(100);
SELECT #OldGeo = [LastUpdatedLocationFromJob]
,#OldCity = [City]
,#OldCountry = [Country]
,#OldAddress = [Address]
FROM DeviceCatalog
WHERE [UniqueID] = #UniqueID;
UPDATE DeviceCatalog
SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
WHERE [UniqueID] = #UniqueID;
IF (#OldGeo IS NULL OR #OldAddress IS NULL OR #OldCity IS NULL OR #OldCountry IS NULL OR ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50)
BEGIN
UPDATE DeviceCatalog
SET [Lastmodifieddate] = Getdate()
WHERE [UniqueID] = #UniqueID;
END
END
ELSE
BEGIN
INSERT INTO DeviceCatalog
([OS]
,[UniqueID]
,[Location]
,[Culture]
,[Other]
,[IPAddress]
,[NativeDeviceID])
VALUES (#OS
,#UniqueID
,#Geo
,#Culture
,#Other
,#IPAddress
,#NativeDeviceID);
IF(#OldUniqueID != ''-1'' AND #OldUniqueID != #UniqueID)
BEGIN
EXEC DeleteOldAndroidDeviceID #OldUniqueID, #UniqueID;
END
END
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 Insertorupdatedevicecatalog;
RAISERROR (''Insertorupdatedevicecatalog: %d: %s'', 16, 1, #error, #message) ;
END CATCH
END
The timeout occurs due to two updates to same table inside same transaction. You could avoid it with a case statement. Also whole IF ELSE can be replaced with a merge.
MERGE INTO DeviceCatalog DC
USING (SELECT #UniqueID AS UniqueID) T ON (DC.UniqueID = T.UniqueID)
WHEN MATCHED THEN
UPDATE SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
,[Lastmodifieddate] = (CASE
WHEN (LastUpdatedLocationFromJob IS NULL OR [Address] IS NULL OR [City] IS NULL OR [Country] IS NULL OR ISNULL(#Geo.STDistance(LastUpdatedLocationFromJob) / 1000,0) > 50)
THEN Getdate()
ELSE [Lastmodifieddate]
END)
WHEN NOT MATCHED THEN
INSERT INTO DeviceCatalog
([OS]
,[UniqueID]
,[Location]
,[Culture]
,[Other]
,[IPAddress]
,[NativeDeviceID])
VALUES (#OS
,#UniqueID
,#Geo
,#Culture
,#Other
,#IPAddress
,#NativeDeviceID)
WHEN NOT MATCHED BY SOURCE AND #OldUniqueID != ''-1'' AND #OldUniqueID != #UniqueID THEN
DELETE;
Try it and check whether this is what you expected.
Already discussed here
You can achieve it using sp_getapplock in TSQL.
But you need a wrapper storedproc or batch for this. Check the following example it will help you to desing your wrapper sp/batch statement.
Sample Code Snippet
Create table MyTable
(
RowId int identity(1,1),
HitStartedAt datetime,
HitTimestamp datetime,
UserName varchar(100)
)
Go
Create proc LegacyProc (#user varchar(100), #CalledTime datetime)
as
Begin
Insert Into MyTable
Values(#CalledTime, getdate(), #user);
--To wait for 10 sec : not required for your procedures, producing the latency to check the concurrent users action
WAITFOR DELAY '000:00:10'
End
Go
Create Proc MyProc
(
#user varchar(100)
)
as
Begin
Declare #PorcName as NVarchar(1000), #CalledTime datetime
Begin Tran
--To get the Current SP Name, it should be unique for each SP / each batch
SET #PorcName = object_name(##ProcID)
SET #CalledTime = Getdate()
--Lock the Current Proc
Exec sp_getapplock #Resource = #PorcName, #LockMode = 'Exclusive'
--Execute Your Legacy Procedures
Exec LegacyProc #user, #CalledTime
--Release the lock
Exec sp_releaseapplock #Resource = #PorcName
Commit Tran
End
You are doing two seperate updates on the DeviceCatalog table where [UniqueID] = #UniqueID in the same transaction.
I bet your locking/request timeout issue is happening when:
IF (#OldGeo IS NULL OR #OldAddress IS NULL OR #OldCity IS NULL OR #OldCountry IS NULL OR ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50) is true.
Try something like this in place of the two updates.
Obviously test in dev first.
In the else clause, you want to have it insert something if the when is false. Here I am just inserting the current before update field contents.
UPDATE DeviceCatalog
SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
,[Lastmodifieddate] =
case when (
#OldGeo is NULL
OR
#OldAddress is NULL
OR
#OldCity is NULL
OR
#OldCountry is NULL
OR
ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50
) then Getdate()
else [Lastmodifieddate]
end
WHERE [UniqueID] = #UniqueID