Does ##IDENTITY in an SP using triggers result in a race condition? - sql

If I were to use ##IDENTITY in a SQL sp which also activated a trigger, would it result in a race condition?
Say I perform an INSERT INTO TABLE_A which triggers an INSERT INTO TABLE_B then, in the current scope I use ##IDENTITY
Would I always get the resulting identity from the trigger (TABLE_B) or would it depend on which thread finished first?
Note: I know about SCOPE_IDENTITY, this question is hypothetical....

I believe you'll always get the identity from table B, because your transaction in Table A isn't considered complete and committed (and your T-SQL isn't allowed to advance) until the insert and trigger are both complete. Even if you're using an AFTER trigger, you're still waiting until the trigger code is done before you get to the next line and ask for the ##identity.

Related

SQL Server Prevent Duplication

SQL Table contains a table with a primary key with two columns: ID and Version. When the application attempts to save to the database it uses a command that looks similar to this:
BEGIN TRANSACTION
INSERT INTO [dbo].[FirstTable] ([ID],[VERSION]) VALUES (41,19)
INSERT INTO [dbo].[SecondTable] ([ID],[VERSION]) VALUES (41,19)
COMMIT TRANSACTION
SecondTable has a foreign key constraint to FirstTable and matching column names.
If two computers execute this statement at the same time, does the first computer lock FirstTable and the second computer waits, then when it is finished waiting it finds that the first insert statement fails and throws an error and does not execute the second statement? Is it possible for the second insert to run successfully on both computers?
What is the safest way to ensure that the second computer does not write anything and returns an error to the calling application?
Since these are transactions, all operations must be successful otherwise everything gets rolled back. Whichever computer completes the first statement of the transaction first will ultimately complete its transaction. The other computer will attempt to insert a duplicate primary key and fail, causing its transaction to roll back.
Ultimately there will be one row added to both tables, via only one of the computers.
Ofcourse if there are 2 statements then one of them is going to be executed and the other one will throw an error . In order to avoid this your best bet would be to use either "if exists" or "if not exists"
What this does is basically checks to see if there is data already present in the table if not then you insert , else just select.
Take a look at the flow shown below :
if not exists (select * from Table with (updlock, rowlock, holdlock) where
...)
/* insert */
else
/* update */
commit /* locks are released here */
The transaction did not rollback automatically if an error was encountered. It needed to have
set xact_abort on
Found this in this question
SQL Server - transactions roll back on error?

Do ##Identity And Scope_Identity Always Return The Record Just Added?

I understand the differences between ##IDENTITY and SCOPE_IDENTITY, I think, but I'm struggling to find out exactly how they're generated.
All the documentation tells me that these functions return the ID of the last record added to a table, but if I have a Stored Procedure containing an INSERT statement, and that procedure is part of a heavily-used database that could be getting executed by multiple users at the same time, if those two users both insert a record into the same table fractions of a second apart, is it possible that if I call ##IDENTITY or SCOPE_IDENTITY from the Stored Procedure right after the INSERT statement, they could actually return the ID of a record inserted by a different user?
I think the answer is that SCOPE_IDENTITY would avoid this because, as the name suggests, it gets the identity of the last record added from within the scope of the call to SCOPE_IDENTITY (in this case, from within the same Stored Procedure), but since I'm not entirely sure what the definition of the scope is, I don't know if I'm right in thinking this.
Both ##identity and scope_identity() will return the id of a record created by the same user.
The ##identity function returns the id created in the same session. The session is the database connection, so that is normally the same thing as the user.
The scope_identity() function returns the id created in the same session and the same scope. The scope is the current query or the current stored procedure.
So, the difference between them is if you for example call a procedure from another procedure; if the called procedure inserts a record, using ##identity after the call will return that id, but scope_identity() will not.
Way back before I knew better I ran a query like
select max(id) from table
to get the ID of a record I just inserted. When you use something like this in a production environment where you have multiple users adding records concurrently, bad things happen.
You are implying that ##Identity and scope_identity() work the same way as the query above. That's not the case. They both return the value of identity columns generate via inserts WITHIN THE CURRENT USER'S SESSION ONLY!. Scope_Identity() is useful if you have tables that have triggers on them and the trigger logic does it's own inserts. In those cases ##Identity would return the identity value generated within the trigger, which is probably not what you want. It's for that reason I almost always prefer Scope_Identity() to ##Identity

Simple IF EXISTS in TRIGGER fails

I've created a simple TRIGGER:
ALTER TRIGGER [dbo].[SprawdzZajetosc] ON [dbo].[Wypozyczenia]
FOR insert, update
AS
BEGIN
IF EXISTS (SELECT * FROM Wypozyczenia)
BEGIN
RAISERROR('Wybrany pojazd został już wypożyczony w wybranym przedziale czasu.', 16, 1)
ROLLBACK TRANSACTION
END
END
I can't understand why 'if' is returning me TRUE even if table 'Wypozyczenia' is empty? It doesn't matter what 'Wypozyczenia' contains - it always returns me TRUE.
I tried with count(*) it always returns me a value > 0.
Why is that?
I am not 100% sure of this, but it sounds logical to me - The trigger is an insert/update trigger. As soon as something is being inserted, the trigger is triggered and the condition is TRUE. Since there is a ROLLBACK TRANSACTION fired, the inserted row is then rolled back and hence you get an empty table. What are you actually trying to achieve here ?
Apart from the reasons why you're doing this, the cause for IF EXISTS() to be always TRUE in your case is very simple it's because you're using an AFTER or FOR trigger.
CREATE TRIGGER
AFTER specifies that the DML trigger is fired only when all operations
specified in the triggering SQL statement have executed successfully.
All referential cascade actions and constraint checks also must
succeed before this trigger fires.
Meaning the row(s) you're trying to insert are already in the table. It's just a transaction has not been committed yet.
Here is SQLFiddle demo
Your IF EXISTS() check might've worked only in INSTEAD OF INSERT trigger but then you should've take into consideration that triggers in SQL Server are statement based. Meaning it fires once per statement and you can insert more than one row in one statement.
Here is SQLFiddle demo
As far as FOR UPDATE clause goes in your trigger definition it doesn't make any sense at all. If you're updating something it should be in the table. Thus table is not empty.

After Delete Trigger Fires Only After Delete?

I thought "after delete" meant that the trigger is not fired until after the delete has already taken place, but here is my situation...
I made 3, nearly identical SQL CLR after delete triggers in C#, which worked beautifully for about a month. Suddenly, one of the three stopped working while an automated delete tool was run on it.
By stopped working, I mean, records could not be deleted from the table via client software. Disabling the trigger caused deletes to be allowed, but re-enabling it interfered with the ability to delete.
So my question is 'how can this be the case?' Is it possible the tool used on it futzed up the memory? It seems like even if the trigger threw an exception, if it is AFTER delete, shouldn't the records be gone?
All the trigger looks like is this:
ALTER TRIGGER [sysdba].[AccountTrigger] ON [sysdba].[ACCOUNT] AFTER DELETE AS
EXTERNAL NAME [SQL_IO].[SQL_IO.WriteFunctions].[AccountTrigger]
GO
The CLR trigger does one select and one insert into another database. I don't yet know if there are any errors from SQL Server Mgmt Studio, but will update the question after I find out.
UPDATE:
Well after re-executing the same trigger code above, everything works again, so I may never know what if any error SSMS would give.
Also, there is no call to rollback anywhere in the trigger's code.
after means it just fires after the event, it can still be rolled back
example
create table test(id int)
go
create trigger trDelete on test after delete
as
print 'i fired '
rollback
do an insert
insert test values (1)
now delete the data
delete test
Here is the output from the trigger
i fired
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
now check the table, and verify that nothing was deleted
select * from test
The CLR trigger does one select and
one insert into another database. I
don't yet know if there are any errors
from SQL Server Mgmt Studio, but will
update the question after I find out.
Suddenly, one of the three stopped
working while an automated delete tool
was run on it.
triggers fire per batch/statement not per row, is it possible that your trigger wasn't coded for multi-row operations and the automated tool deleted more than 1 row in the batch? Take a look at Best Practice: Coding SQL Server triggers for multi-row operations
Here is an example that will make the trigger fail without doing an explicit rollback
alter trigger trDelete on test after delete
as
print 'i fired '
declare #id int
select #id = (select id from deleted)
GO
insert some rows
insert test values (1)
insert test values (2)
insert test values (3)
run this
delete test
i fired
Msg 512, Level 16, State 1, Procedure trDelete, Line 6
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.
check the table
select * from test
nothing was deleted
An error in the AFTER DELETE trigger will roll-back the transaction. It is after they are deleted but before the change is committed. Is there any particular reason you are using a CLR trigger for this? It seems like something that a pure SQL trigger ought to be able to do in a possibly more lightweight manner.
Well you shouldn't be doing a select in trigger (who will see the results) and if all you are doing is an insert it shouldn't be a CLR trigger either. CLR is not generally a good thing to have in a trigger, far better to use t-SQL code in a trigger unless you need to do something that t-sql can't handle which is probably a bad idea in a trigger anyway.
Have you reverted to the last version you have in source control? Perhaps that would clear the problem if it has gotten corrupted.

Are Sql Triggers synchronous or asynchronous?

I have a table that has an insert trigger on it. If I insert in 6000 records into this table in one insert statement from a stored procedure, will the stored procedure return before the insert trigger completes?
Just to make sure that I'm thinking correctly, the trigger should only be called (i know 'called' isn't the right word) once because there was only 1 insert statement, right?
My main question is: will the sproc finish even if the trigger hasn't completed?
Your insert trigger will run once for the entire insert statement. This is why it is important to use the inserted temporary table to see what has actually been inserted, and not just select the most recent single record, or something like that.
I just tested an insert and update trigger and indeed, they are considered part of the insert by sql server. the process will not finish until the trigger finishes.
Triggers are part of the transaction that called them.
One important thing about triggers that you must be aware of is that the trigger fires once for each transaction (at least in SQL server, you should check other dbs, but even if it will process row by row, that is usually a poor idea), so if you insert 6000 records the trigger fires once not 6000 times. Many people are not aware of this and write triggers as if they will process multiple record inserts one record at a time. This is not true and your trigger must account for handing the multiple record insert.
The trigger call is not asynchronous. Each call to your insert procedure will result in the trigger being fired, and the procedure will not return until the trigger finishes.
Take a look at the query plan to see how it works. You'll see that the statements in the trigger will be called for each call to the procedure.
The thing is, every time the TRIGGER criteria is met, the TRIGGER fires. It fires once in batch processing or Transaction. See my lesson 101 on TRIGGER
You should use cursor in insert statement to process trigger row.
Because Triggers in SQL Server fire once per statement, not once per row.