Insert / update Deadlock with SQL Server - sql

I have a table A (id int, domain nvarchar, status nvarchar) and a trigger A_trigger after insert on table A. The trigger calls a stored procedure and depending on the result of the procedure, updates the status on the newly inserted row.
When I run it in two sessions, I end up with a deadlock issue:
Isolation level: read committed
INSERT statement:
INSERT INTO dbo.TEST_TRIGGER (DOMAIN)
VALUES ('toto')
Trigger:
CREATE TRIGGER dbo.dim_trigger
ON db.dbo.TEST_TRIGGER
AFTER INSERT
AS
DECLARE #status nvarchar(200),
#domain nvarchar(200),
#trackingId int
BEGIN
SET NOCOUNT ON;
-- This code assumes we insert one and only one row at a time.
SELECT #trackingId = id, #domain = domain FROM INSERTED;
DECLARE #toCallProcName NVARCHAR(200);
SET #toCallProcName = 'db.dbo.'+#domain+'_proc';
EXEC #toCallProcName #status out;
UPDATE db.dbo.TEST_TRIGGER
SET status = #status
WHERE id = #trackingId;
END
I tried to:
issue the update statement with WITH (UPDLOCK), but that's not working
Creating an index on (ID) works, but I'm concerned about this solution!!
EDIT1:
Table schema:
CREATE TABLE [dbo].[TEST_TRIGGER]
(
[DOMAIN] [NVARCHAR](200) NOT NULL,
[ID] [BIGINT] IDENTITY(1,1) NOT NULL,
[STATUS] [NVARCHAR](100) NULL
)
Stored procedure:
CREATE PROCEDURE [dbo].[toto_proc]
#res NVARCHAR(200) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
WAITFOR DELAY '00:00:5'
PRINT 'This is me: '+CONVERT(VARCHAR(8), GETDATE(), 108) ;
SET #res = 'OK'
END
Any help?
Thanks

Reason for your issue is, Triggers keep holding the lock on the base table. Due to multiple session and same resource this is bound to happen. Having no index on the table will cause table scan every time for each different session and cause deadlock. Applying the index is the right move, because in this way your update statement will hit the granular level. So this should work. I suggest an additional hint of RowLock in your update statement.
Important : I am assuming, each insert will have only one value to be inserted. Other wise this Trigger has issues. (As mentioned by marc_s)

What you need to do is to create an index on your table
example:
CREATE INDEX IX_Test_Trigger ON dbo.TEST_TRIGGER(Id)
Read this for more info about Index and its impact on locking mechanism:
https://www.mssqltips.com/sqlservertip/2517/using-a-clustered-index-to-solve-a-sql-server-deadlock-issue/

Related

I have a trigger to execute a SP when one column is updated. If I add an insert operation it runs the SP for any changes to the table

I am looking to get the SP run only when the Instructions column has something added or altered.
This code works for updating of the Instructions column
AFTER Update
AS BEGIN
SET NOCOUNT on
if update ([Instructions])
BEGIN
DECLARE #ID INT
SELECT #ID = (SELECT [ID] FROM inserted)
EXEC [dbo].[Gem_AddNoteToDelivery] #ID
END
END
GO
ALTER TABLE [dbo].[DeliveryNote] ENABLE TRIGGER [Gemini_DeliveryNote_AddNote]
GO
My Stored Procedure is this
ALTER PROCEDURE [dbo].[Gem_AddNoteToDelivery]
#ID INT output
AS
BEGIN
SET NOCOUNT ON;
DECLARE #DInst VARCHAR (4000)
DECLARE #DELNN VARCHAR (32)
Select #Dinst = [Instructions],
#DELNN = [DelNoteNumber]
FROM [dbo].[DeliveryNote] WHERE [id]=#ID
INSERT INTO [dbo].[Notes]
--([UserID],[OperatorID],[UserName],[DateDB],[ModuleType],[RecordID],[RecordNo],[Flags],[Details],[Priority],[NotesType])
VALUES
(115,0,'Automation',getdate(),4,#ID,#DELNN,
0x0000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000,#DInst,1,Null)
END
GO
If I alter the Trigger to read
AFTER Update,Insert
then the SP runs for updates to all columns not just the Instructions one.
The database is not mine so I am unable to alter tables.
Any help most appreciated.

Multiple rows are getting inserted into a table (which is not desired) as part of a stored procedure

Update: This still remain a mystery. Checked the calling code and we did not find anything that would make the SP run in a loop.
For now we have split the SP into two which seems to have arrested the issue although not able to reason how that has helped out.
Database: MS SQL Server.
I have a SP which performs few operations - i.e inserts a row into 3 tables based on certain status as part of that SP being called.
It is getting called from our web application based on a user action.
We have cases, few times a day where the same row gets inserted multiple times (sometime more than 50+) with the same values in each row except that if you look at the datetime when the row was inserted there is a difference of few milliseconds. So it is unlikely that the user is initiating that action.
This SP is not running in a Transaction or with any locks however it is getting called probably concurrently multiple times as we have many concurrent users on the web application invoking this action.
My question is what is causing the same row to insert so many times? If concurrent execution of SP was an issue where we are updating same row then it is understood one may overwrite the other. However in this case each user calls in the SP with different parameters.
I have put the said operation in a Transaction to monitor the behavior however was looking to find out what exactly causes these kind of multiple inserts with same value just a few milliseconds apart?
USE [ABC]
GO
/****** Object: StoredProcedure [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog]
(
--Insert using bulk
#stdfrm_id int,
#course_id int,
#stdfrm_scrt_apprvby int,
#stdfrm_scrt_apprvcomment varchar(max),
#sRemainingDocs varchar(max),
#DTProcessAdmittedDocUploadScrutiny AS dbo.MyDTProcessAdmittedDocUploadScrutiny READONLY
)
AS
BEGIN
DECLARE #result char
SET #result='N'
--New
declare #AuditCount int=0;
select #AuditCount=count(scrtaudit_id) from tbl_ProcessAdmittedScrutinyAuditLog
where stdfrm_id=#stdfrm_id and stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
and stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment and convert(date,stdfrm_scrt_apprvon,103)=convert(date,getdate(),103)
--Checked extra conditon to avoid repeatation
if(#AuditCount=0)
BEGIN
--Call Insert
BEGIN TRY
/*Remaining Documents----------*/
DECLARE #sdtdoc_id Table (n int primary key identity(1,1), id int)
if(#sRemainingDocs is not null)
begin
--INSERT INTO #sdtdoc_id (id) SELECT Name from splitstring(#sRemainingDocs)
INSERT INTO #sdtdoc_id (id) SELECT [Value] from dbo.FN_ListToTable(#sRemainingDocs,',')
end
Declare #isRemaining int=0;
SELECT #isRemaining=Count(*) FROM #sdtdoc_id
/*Calculate stdfrm_scrt_apprvstatus*/
Declare #stdfrm_scrt_apprvstatus char(1)='A';--Approved
Declare #TotalDescripancies int;
select #TotalDescripancies=count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y'
if(#isRemaining>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
else if exists (select count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y')
begin
if(#TotalDescripancies>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
end
/* Check if Discrepancies Found first time then assign to Checker o.w assign to direct college like grievance*/
if(#stdfrm_scrt_apprvstatus='H')
begin
declare #countAuditLog int=0;
select #countAuditLog=count(stdfrm_id) from tbl_ProcessAdmittedScrutinyAuditLog where stdfrm_id =#stdfrm_id
if (#countAuditLog=0)
begin
set #stdfrm_scrt_apprvstatus='G'--'E';--Discrepancies Found set Edit request assign to Checker
end
--else if (#countAuditLog=1)
-- begin
--set #stdfrm_scrt_apprvstatus='G';--Discrepancies Found set Grievance assign to college
-- end
end
/*----------------------*/
/*Update status in original table-----*/
Update tbl_ProcessAdmitted set stdfrm_scrt_apprvstatus=#stdfrm_scrt_apprvstatus
,stdfrm_scrt_apprvon=getdate(),stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
,stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment
where stdfrm_id =#stdfrm_id
/*Add in Main Student Log-----------*/
/********* The row here gets inserted multiple times *******************/
INSERT into tbl_ProcessAdmittedScrutinyAuditLog
(stdfrm_id, stdfrm_scrt_apprvstatus, stdfrm_scrt_apprvon, stdfrm_scrt_apprvby, stdfrm_scrt_apprvcomment )
values
(#stdfrm_id, #stdfrm_scrt_apprvstatus, getdate(), #stdfrm_scrt_apprvby, #stdfrm_scrt_apprvcomment)
DECLARE #scrtaudit_id int =##identity
/*Completed -------------------------*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutiny WHERE stdfrm_id =#stdfrm_id
SET NOCOUNT ON;
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutiny
(stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Scrutiny Document Log -------------------------*/
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutinyAuditLog
(scrtaudit_id,stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #scrtaudit_id,#stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Remaining Documents Insert into table*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutinyRemiaing WHERE stdfrm_id =#stdfrm_id
DECLARE #Id int,#doc_id int
WHILE (SELECT Count(*) FROM #sdtdoc_id) > 0
BEGIN
Select Top 1 #Id = n,#doc_id=id From #sdtdoc_id
--Do some processing here
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaing(stdfrm_id, doc_id )
values (#stdfrm_id,#doc_id)
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaingAuditLog
(scrtaudit_id, stdfrm_id, doc_id )
values (#scrtaudit_id,#stdfrm_id,#doc_id)
DELETE FROM #sdtdoc_id WHERE n = #Id
END --Begin end While
/*End Remaining Documents-----------*/
SET #result=#stdfrm_scrt_apprvstatus
END TRY
BEGIN CATCH
SET #result='N'
insert into tbl_ErrorSql( ErrorMessage, stdfrm_id)
values(coalesce(Error_Message(),ERROR_LINE()),#stdfrm_id)
END CATCH;
--End of Call Insert
END
SELECT #result
END

List of foreign keys in an insert SQL Server

I am trying to set up a stored proc that will have three variables
FK_List
String_of_Info
CreateId
I need to insert into the table one entry per foreign key from the FK_List. I was curious what the best way to structure the stored procedure to do this efficiently.
EDIT: Code snippet added
CREATE PROCEDURE StackOverFlowExample_BulkAdd
#FKList VARCHAR(MAX),
#Notes NVARCHAR(1000),
#CreateId VARCHAR(50)
AS
BEGIN
INSERT INTO [dbo].[StackOverflowTable] WITH (ROWLOCK)
([FKID], [Notes], [CreateId], [UpdateId])
VALUES (#FKList, <---- this is the problem spot
#Notes, #CreateId, #CreateId)
END
GO
Based off your comments, you simply need a slight edit
CREATE PROCEDURE StackOverFlowExample_BulkAdd
#Notes nvarchar(1000),
#CreateId varchar(50)
AS
BEGIN
INSERT INTO [dbo].[StackOverflowTable] WITH (ROWLOCK)
([FKID]
,[Notes]
,[CreateId]
,[UpdateId])
select
someID
,#Notes
,#CreateId
,#CreateId
from FKListTable
END
GO
Here is a simple demo
This will insert a row into your table for each FK reference in the reference table with the parameters you pass in. That's all there is to it!
Here's another demo that may be more clear as I use a GUID for the primary key on the secondary table.
SECOND EDIT
Based off your comments, you will need a string splitter. I have added a common one which was created by Jeff Moden. See the example here
The final proc, after you create the function, will be like below. You need to change the comma in the function to what ever the delimiter is for your application. Also, you should start using table valued parameters.
CREATE PROCEDURE StackOverFlowExample_BulkAdd
#FKList VARCHAR(MAX),
#Notes nvarchar(1000),
#CreateId varchar(50)
AS
BEGIN
INSERT INTO [dbo].[StackOverflowTable] WITH (ROWLOCK)
([FKID]
,[Notes]
,[CreateId]
,[UpdateId])
select item
,#Notes
,#CreateId
,#CreateId
from dbo.DelimitedSplit8K(#FKList,',')
END
And you can call it like so:
declare #FKList varchar(1000) = '1,2,3,4,5,6'
declare #Notes varchar(1000) = 'here is my note'
declare #CreatedId int = 1
exec StackOverFlowExample_BulkAdd #FKList, #Notes, #CreatedId

Trigger not calling for huge rows insert

I have one table which consists of one trigger which will be called if any insert or update operation performed on that table.
This trigger will insert a new row in other physical table.
First I am taking the entire data to be inserted into a temporary table and then I am inserting data into my physical table(which has trigger).
After performing insert operation all the records in the temporary table are getting inserted into physical table but the trigger is executing for only first record, for rest of the records it is not executing.
Can anyone please help me with this issue.
NOTE : With cursor it is working fine but for performance issue I don't want to use cursor.
ALTER TRIGGER [dbo].[MY_TRG]
ON [dbo].[T_EMP_DETAILS]
FOR INSERT , UPDATE
AS
BEGIN
IF UPDATE(S_EMPLOYEE_ID)OR UPDATE(S_GRADE_ID)OR UPDATE(D_EFFECTIVE_DATE) OR UPDATE(S_EMPLOYEE_STATUS)
BEGIN
DECLARE #EmpId varchar(6)
DECLARE #HeaderId Int
DECLARE #FYStartYear varchar(4)
DECLARE #EffDate Smalldatetime
DECLARE #UpdatedBy varchar(10)
DECLARE #ActionType varchar(1)
DECLARE #RowCount Int
DECLARE #EmpRowCount Int
DECLARE #AuditRowsCount Int
DECLARE #EMP_STATUS VARCHAR(1)
DECLARE #D_FIN_START_YEAR DATETIME
DECLARE #Food_Count int
SELECT #FYStartYear = CAST(YEAR(D_CURRENT_FY_ST_DATE)AS VARCHAR) FROM dbo.APPLICATION WHERE B_IS_CURRENT_FY = 1
SELECT #UpdatedBy = 'SHARDUL'
select #EmpId = S_EMPLOYEE_ID from inserted
select #HeaderId = N_HEADER_TXN_ID from inserted
select #EffDate = D_EFFECTIVE_DATE from inserted
select #FLEXI_AMT = N_FLEX_BASKET_AMT from inserted
select #EMP_STATUS = S_EMPLOYEE_STATUS from inserted
select #D_FIN_START_YEAR=D_FIN_START_DATE from inserted
SELECT #RowCount = count(*) from T_EMP_DETAILS
WHERE S_EMPLOYEE_ID = #EmpId and
SUBSTRING(CAST(D_EFFECTIVE_DATE AS VARCHAR),1,11) = SUBSTRING(CAST(#EffDate AS VARCHAR),1,11)
BEGIN
exec INSERT_DEFAULT_VALUES #EmpId,#HeaderId,#UpdatedBy
END
That's one of many reasons Bulk is so fast :). Read Bulk Insert syntax and you'll see FIRE_TRIGGERS parameter. Use it.
As I wrote in my comment - you are using inserted in improper way. As written now it will work only for 1 row.
The second one is a WEIRD number of variables, and only few are used, why?
Third - you are using SP in the end of batch, you need to post it's code, I bet there is some insert in it, maybe you could avoid using this SP and insert directly in some table from inserted.

problem with raiseerror()

what I want to do is to create a stored procedure that executes insert statement.There is a possibility the execution to fail because of a check constraint for the table Employee.In that case I want to handle a user-defined error.Obviously the following procedure is not working properly because it always raises my error,but not only when insertion fails.
EXEC sp_addmessage 50001, 16, N'Title must be one of the following - Captain,Engineer,Flight-attendant,Purser,First-officer';
CREATE PROCEDURE InsertIntoEmployee
#firstName nvarchar(30),
#familyName nvarchar(30),
#title nvarchar(50),
#address nvarchar(50),
#chiefID int ,
#salary money ,
#FK_IDCrew int,
#FK_DepartmentID int
AS
BEGIN
declare #err_num int;
declare #err_sev int;
declare #err_msg int;
begin try
insert into Employee(firstName, familyName, title, address, chiefID, salary, FK_IDCrew,
FK_DepartmentID)
values(#firstName, #familyName, #title, #address, #chiefID, #salary, #FK_IDCrew,
#FK_DepartmentID);
raiserror(50001,16,1);
END try
begin catch
set #err_num=ERROR_NUMBER();
set #err_sev=ERROR_SEVERITY();
set #err_msg=ERROR_STATE();
raiserror(#err_num,#err_sev,#err_msg);
end catch
end
GO
In this case:
Title should be a lookup to another table and a foreign key
In the CATCH block you can trap the FK constraint violation separately if you want...
...but you'd only allow rows from the new table in your client so I wouldn't personally
No need for a sys.messages entry
Your code will also always hit the RAISERROR too which doesn't add any value,
I hope that the dimensions mentioned in the parameter list is sycn with table columns length.
Before insertion, You should check take care of following points.
Check the existence of #FK_IDCrew value in it's table.
Check the existence of #FK_DepartmentID value in it's table.
It should be like below.
If Not Exists(Select IDCrewColumnName From Table Where columnName = #FK_IDCrew)
Begin
return here from the stored procedure.
End
In case any of them fails to meet the conditions, you should show some user friendly message to user that
(a) Crew ID, you are going to insert, either deleted or does not exists in the database.
(b) DepartmentID, you are going to insert, either deleted or does not exists in the database.
In this way the probability of error will also come to an end.