SQL database triggers thread safe? - sql-server

I have been wondering about the semantics of concurrency with regards to SQL Server database triggers. I have a database trigger that runs after an update to a table. The trigger sets a 'dateModified' column to the current time. This allows me to always know that the most recent update to the table occurred at X time. Below is what it would look like:
ALTER TRIGGER base.TR_myTrigger ON base.myTable
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #dateModified AS DATETIMEOFFSET
SET #dateModified = SYSDATETIMEOFFSET()
UPDATE base.myTable
Set dateModified = #dateModified
WHERE id in (SELECT DISTINCT(id) FROM inserted)
END
In my scenario, the table can be updated at any time by any number of threads. Is this trigger safe within a multi-threaded context? If thread A updates the table, and thread B reads from it immediately after, will B see a state of the table with A's updates but not trigger's updates too? Or do triggers protect the table from being read from until all actions + their triggers have been performed?
Any insight into what exactly is happening under the hood with SQL database triggers would be appreciated. Thanks!

If thread A updates the table, and thread B reads from it immediately after, will B see a state of the table with A's updates but not trigger's updates too? Or do triggers protect the table from being read from until all actions + their triggers have been performed?
What this boils down to is: are the underlying operation and the trigger operation treated as an atomic unit or are they separate? The answer is: atomic. That is, you'll never see the results of the insert or update (in your case) and not see the dateModified column being dealt with via the trigger. That is, they both commit in one transaction. From the documentation:
The trigger and the statement that fires it are treated as a single transaction...

Triggers do not have any special properties regarding concurrency. They run as if you had manually executed that code.
Your trigger is safe because all rows that you read and write have been X-locked already by the triggering DML.

Related

Can I conditionally avoid hitting a trigger completely?

We had to use a trigger to sync an old system to the new system until we can fully deprecate the old system. The new system doesn't need this trigger at all and, in fact, exits out immediately on the condition that it's the new app.
The impact on the old system is acceptable.
However, the impact on the new system is not because the new system processes many, many more records on a single update. Merely executing the trigger changes an update from 10 seconds (already "UGH") to over a minute and a half.
The new system performs acceptably by disabling the trigger in code (VS Core with EntityFramework btw), running the update and then re-enabling the code, all within a transaction. There is disagreement among my colleagues about whether or not the trigger is disabled for the other application while the transaction is being processed.
I have already seen this post:
https://dba.stackexchange.com/questions/204339/sql-server-how-to-disable-trigger-for-an-update-only-for-your-current-session
And the first answer is the solution I am using. My colleagues tell me that won't work. I believe it will. But the answers 2-whatever seem to contradict the first answer.
My testing proved out the first answer as well but I need to be 100% sure on this.
TIA
However, the impact on the new system is not because the new system processes many, many more records on a single update.
You should find a way to batch the updates into fewer statements. The trigger fires per statement, not per row. EG EF Core does batching automatically, or you can use a TVP or SqlBulkCopy into a temp table, etc.
DISABLE TRIGGER within a transaction eliminates the possibility of other users updating while the trigger is disabled
Yes. You can easily verify that disabling the trigger takes a Sch-M lock on the table for the duration of the transaction, which is incompatible with all other table access.
eg
use tempdb
drop table if exists t
create table t(id int primary key)
go
create trigger t_t on t after insert
as
begin
select 'trigger running' msg
end
go
begin transaction
go
disable trigger t_t on t
go
select object_name(resource_associated_entity_id) table_name, resource_lock_partition, request_mode, request_status
from sys.dm_tran_locks
where request_session_id = ##spid
and resource_type = 'OBJECT'
order by 1,2
rollback

SQL Trigger - After Insert - Update statement and RAISERROR not working

I have a problem with a SQL trigger. It is an "AFTER INSERT" trigger. It works for every IF EXISTS block, except for the one with the update and raise error combinations as shown below. It either updates and moves on or stops and doesn't update.
Code: (where I left off - multiple different attempts at it and they all failed)
IF EXISTS ( SELECT
[RPS].[Slip]
FROM
[DC].[dbo].[Slips] AS [RPS]
WHERE
[RPS].[Slip] = #ps
AND [RPS].[Status] = 0 )
BEGIN
BEGIN TRAN;
UPDATE
[DC].[dbo].[Slips]
SET
[Slip].[Status] = 1
FROM
[DC].[dbo].[Slips]
WHERE
[Slips].[Slip] = #ps;
SET #msg = ' ' + #NewLine + 'Inv. Decremented - Rollback' + #NewLine + 'Contact HD.' + #NewLine;
RAISERROR (#msg,16,1);
COMMIT TRAN;
RETURN;
END;
The goal is to update the table to a status of 1 when the IF EXISTS is caught and fire the RAISERROR. The RAISERROR is picked up by the java code and stops the processing from occurring. If I take the update out, the trigger raises the error and stops. If I take the raise error out, the trigger updates but moves on - and I don't want that... I want my cake and eat it too!
Thoughts?
In order to prevent the update to the underlying triggered table while allowing the update to the Slips table you will need an INSTEAD OF INSERT trigger, a VIEW, and an ownership chain to prevent direct modification of the underlying table by users or their application code. You cannot get there using a traditional AFTER UPDATE trigger for the reasons mentioned previously in the comments namely because both the triggering insert and the triggered update are wrapped in the same transaction, they must either both take effect or have neither take effect. You cannot commit only part of a transaction and a nested transaction is part of the outermost transaction it is contained within.
Now for the solution...
Part 1 - The View
Create a view with the same column structure and naming as the table that has the insert trigger that is giving you all this trouble. Something of the form:
CREATE VIEW [same-name-as-the-table-you-are-using]
AS
SELECT <list-all-columns-explicitly-please-dont-use-star>
FROM <original-table-with-slightly-different-name-now>
If the original table is named XXX then rename it something like XXX_SINK and use XXX for the view. The users and application developers should consider this view the "table" they are using.
Part 2 - The Ownership Chain
An ownership chain is established in SQL Server when a referencing object and a referenced object have the same owner. When a different party (not the owner) accesses the referencing object (in this case the view) the permissions for that party are evaluated against the referencing object as usual but are not evaluated against the referenced object (in this case the table). This has been a feature of SQL Server since its beginning but is not known or well-understood by a number of SQL developers. You can get more information about ownership chains here.
You will want to deny permissions to the user base to the table and grant them to the view. This means users can only insert or update rows through the view and not directly into the table. This is important because you don't want them bypassing what you will do in the next part...
Part 3 - The INSTEAD OF Trigger
Create an INSTEAD OF trigger on the view. The syntax is just like that of an AFTER trigger except the words INSTEAD OF appears in place of AFTER and that no insert operation has occurred yet at the point it is fired and unless the trigger itself effects an update on the "sink" table, no update will be performed at all. This trigger can mix and match whatever it wishes. Like the AFTER trigger there is an implied transaction but only explicit data modification operations in the trigger itself will be performed.
Remember that the trigger has to explicitly perform the insert into the underlying sink table. The rows to be inserted can be retrieved from the inserted special table just like in the case of the AFTER trigger. Remember that there may (as far as SQL Server is concerned at least) be more than one row to be inserted (in fact there can be zero rows in the case of a zero-row insert statement). You will need to decide whether or not to allow the good rows or deny insertion of all the rows. Given your requirements, I suspect the later.
As a matter of sound database design, I would strongly recommend against -
The trigger restricting inserts to single rows, and
Using any form of cursor inside the trigger to deal with multi-row inserts. Use set-oriented DML instead.
Even though the current application may only insert rows one at a time, the database should not impose such a restriction.
A reasonable RAISERROR (that is one with sensible severity and state values) will not cause anything to be aborted or rolled back.
Doing these things in this combination should produce the result you desire.

How to avoid deadlock while updating table with two different updates statements on same table in SQL server

I am using update statement in a script task of SSIS which updates a particular row of a table. This process happens in a loop for multiple rows.
Sometimes it happens that another application also fires an update on that same table for a different set of rows. For this application I'm getting deadlock exception on lock.
How can I avoid this situation? I want both updates to work at same time as the row sets being updated are different.
Is the anything to lock only that row which is getting updated?
Regards,
Solo
From the MSDN site with a search on TSQL, UPDATE RowLock. There are also PageLock and TableLocks. You can also use transactions if you need to update more than one table. On your reads you may want to use (NOLOCK) if dirty reads are OK (the up side is no read locks).
update Production.Location with (ROWLOCK)
set CostRate = 100.00
where LocationID = 1

Simultaneous Triggers in SQL Server

In Microsoft SQL Server :
I've added an insert trigger my table ACCOUNTS that does an insert into table BLAH based upon the inserted values.
The inserts come from only one place, and those happen one at a time. (By that, I mean, that there's never two inserts in a transaction - two web users could, theoretically click submit and have their inserts done in a near-simulataneous way.)
Do I need to adapt the trigger to handle more than one row being in inserted, the special table created for triggers - or does each individual insert transaction launch the trigger separately?
Each insert calls the trigger. However, if a single insert adds more than one row the trigger is only called once, so your trigger has to be able to handle multiple records.
The granularity is at the INSERT statement level not at the transaction level.
So no, if you have two transactions inserting into the same table they will each call the trigger ATOMICALLY.
BOb
in your situation each insert happens in its own transaction and fires off the trigger individually, so you should be fine. if there was ever a circumstance where you had two inserts within the same transaction you would have to modify the trigger to do either a set based insert from the 'inserted' table or some kind of cursor if additional processing is necessary.
If you do only one insert in a transaction, I don't see any reason for more rows to be in inserted, except if there was a possibility of recursive trigger calls.
Still, it could cause you troubles if you'd change the behavior of your application in future and you forget to change the triggers. So just to be sure, I would rather implement the trigger as if it could contain multiple rows in inserted.

When do triggers fire and when don't they

Pretty general question regarding triggers in SQL server 2005.
In what situations are table triggers fired and what situations aren't they?
Any code examples to demonstrate would be great.
I'm writing a audit based databases and just want to be aware of any situations that might not fire off the triggers that I have set up for update, delete and insert on my tables.
A example of what I mean,
UPDATE MyTable SET name = 'test rows' WHERE id in (1, 2, 3);
The following statement only fires the update trigger once.
When do you want them to fire?
CREATE TRIGGER AFTER ACTION
That runs after the action (insert update delete) being committed. INSTEAD OF fires the trigger in place of the action.
One of the biggest gotchas with triggers is that they fire whenever an action is performed, even if no rows are affected. This is not a bug, and it's something that can burn you pretty quickly if you aren't careful.
Also, with triggers, you'll be using the inserted and deleted tables. Updated rows are listed in both. This throws a lot of folks off, because they aren't used to thinking about an update as a delete then insert.
The MSDN documentation actually has a pretty in-depth discussion about when triggers fire and what effect they have here.
On 2008 you can use built in Change Data Capture
Also There are quite a few situations when triggers do not fire, such as:
· A table is dropped.
· A table is truncated.
· Settings for nested and/or recursive triggers prevent a trigger from firing.
· Data is bulk loaded, bypassing triggers.
The following statement only fires the update trigger once.
Any action type statement only fires the trigger once no matter how many rows are affected, triggers must be written to handle multiple row inserts/updates/deletes.
If your trigger depends on only one row at a time being in the inserted or deleted pseudotables, it will fail. And worse it will not fail with an error, it will simply not affect all the rows you want affected by whatever the trigger does. Do not fix this through a loop or a cursor in a trigger, change to set-based logic. A cursor in a trigger can bring your entire app to a screeching halt while a transaction of 500,000 records processes and locks up the table for hours.
Bulk inserts by pass triggers unless you specify to use them. Be aware of this because if you let them by pass the trigger you will need code to make sure whatever happens in the trigger also happens after the bulk insert. Or you need to call the bulk inserts with the FIRE_TRIGGERS option.
I thought I'd highlight from the link Eric posted a situation in which a trigger would not fire:
Although a TRUNCATE TABLE statement is in effect a DELETE, it cannot activate a trigger because the operation does not log individual row deletions. However, only those with permissions on a table to execute a TRUNCATE TABLE need be concerned about inadvertently circumventing a DELETE trigger with a TRUNCATE TABLE statement.

Resources