SQL Server Trigger Best Practices: Why Triggers Are Invisible, and When to Avoid Them

Written by

in

SQL Server Trigger Best Practices: Why Triggers Are Invisible, and When to Avoid Them

Triggers are powerful precisely because they’re automatic — which is also exactly why they need to be used deliberately, not as a default habit. This closing lesson of the chapter is about judgment: having built both DML and DDL/logon triggers, here’s when that power is worth the tradeoff and when it isn’t.

The Trigger You Can’t See(same UPDATE, a hidden consequence below)▲ visible▼ invisibleUPDATE AccountSET balance = 5500!TRIGGER firesno line of app code showsthis is happening at allAccountAudit row writtenlegitimate use — guaranteed…which can fire ANOTHER triggerindirect cycle — not blocked byRECURSIVE_TRIGGERS OFFsys.triggersthe only way to see itwithout reading app codeGotcha: RECURSIVE_TRIGGERS OFF blocks direct self-fire only —an indirect two-table loop can still run forever. 📌

Four Real Pitfalls

Pitfall Fix
Assuming single-row operation Always write set-based logic joining to inserted/deleted (the exact bug proven in Lesson 1)
Recursive triggers firing themselves Know your nested/recursive triggers DB settings; guard with logic to detect and skip re-entry
Hidden performance cost Document clearly; keep triggers fast; avoid heavy logic on hot-path, high-write tables
Multiple triggers, no guaranteed order Prefer one trigger per table/event; use sp_settriggerorder if unavoidable

Recursive Triggers, Concretely

-- A trigger on Account that updates Account itself can re-fire the same trigger,
-- if RECURSIVE_TRIGGERS is on for the database (off by default):
ALTER DATABASE CURRENT SET RECURSIVE_TRIGGERS OFF; -- the safe default

-- Even with recursion off, an INDIRECT loop is still possible:
-- trigger on Account updates Order → trigger on Order updates Account → fires the first trigger again
-- RECURSIVE_TRIGGERS OFF only blocks DIRECT self-triggering, not this indirect cycle
Common mistake: Assuming RECURSIVE_TRIGGERS OFF (the default) makes trigger loops impossible. It only prevents a trigger from directly re-firing itself — an indirect cycle through a second table’s trigger is still entirely possible and won’t be caught by this setting.

The Biggest Philosophical Pitfall

UPDATE Account SET balance = 5500 Looks like a simple update… no visible sign a trigger will fire EXEC usp_UpdateBalance … An explicit, visible decision to run specific logic

A developer reading application code that runs a plain UPDATE has no way to know a trigger will also fire, unless they separately go check the database schema. A stored procedure call, by contrast, is a visible, greppable, explicit decision to invoke specific logic — anyone reading the call site immediately knows exactly what runs. This invisibility is triggers’ single biggest real-world cost, independent of performance.

-- A genuinely good way to discover what triggers exist on a table you've inherited:
SELECT name, is_disabled, OBJECT_DEFINITION(object_id) AS definition
FROM sys.triggers WHERE parent_id = OBJECT_ID('dbo.Account');

When Triggers Are Still the Right Call

Use triggers when you genuinely need guaranteed enforcement regardless of write path — auditing (Lesson 2’s DDL example), cross-table integrity that CHECK constraints can’t express (Chapter 5), or a rule that must apply even to ad-hoc scripts run directly by a DBA, bypassing any application or stored procedure entirely. Avoid them for things a stored procedure or application layer could handle just as reliably, and far more visibly, since “guaranteed no matter what” is the specific property that justifies accepting the invisibility tradeoff — don’t pay that cost for a rule nothing will ever actually bypass.

Practice tip: Run the sys.triggers query above against any table you’ve built triggers on across this chapter, and read back the definitions via OBJECT_DEFINITION. Getting comfortable discovering triggers this way is a genuinely useful skill for working with a database you didn’t design yourself.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.