What are the risks of putting significant business logic in triggers?
Quick Answer
Triggers execute invisibly relative to the statement that fired them, which makes system behavior harder to trace, test, and reason about; they add write-path overhead that's easy to forget is even happening; and they risk cascading/recursive chains across tables that are difficult to debug. The general rule: reserve triggers for invariants or side effects that genuinely must be enforced regardless of the caller, and keep anything resembling actual business workflow logic in application code instead.
Detailed Answer
This is a deliberate follow-up to the general trigger question, focused specifically on why teams tend to regret over-relying on them for business logic.
Invisibility / "action at a distance"
UPDATE orders SET status = 'cancelled' WHERE id = 501;
Reading this single line gives no indication that it might also, via triggers: restock inventory, send a cancellation event to a queue, recompute a customer's lifetime-value rollup, and write an audit record. Anyone debugging unexpected inventory numbers has to know to go looking in the schema's trigger definitions — a much less discoverable place than reading the code path that issued the UPDATE.
Harder to test in isolation
Application-layer business logic can typically be unit-tested with mocked dependencies, run in CI, and reviewed as a diff. Trigger logic requires a real (or realistically simulated) database to exercise, is often excluded from the same test suites as application code, and changes to it don't show up in the same code review flow unless the team has specifically built tooling to track schema/trigger changes as first-class artifacts.
Cascading and recursive complexity
A trigger on table A writing to table B, which itself has a trigger writing to table C (or back to A), creates an execution graph that's difficult to trace statically just by reading any single trigger's definition — and if not carefully guarded, can produce infinite loops or very deep, hard-to-predict cascades from what looked like a simple single-row update.
Write-path overhead that's easy to forget
Every trigger adds cost to every matching write, and that cost is invisible from the application's perspective — a query that "should" be a cheap single-row UPDATE might actually be doing substantial additional work under the hood, making performance regressions hard to attribute to their real cause without specifically knowing to check for triggers.
Deployment and rollback friction
Trigger definitions live in the database schema, so changing or removing significant behavior requires a schema migration rather than a simple application code deployment/rollback — this can slow down iteration on business logic that changes frequently, compared to logic that lives in ordinarily-deployed application code.
When triggers are still the right call
None of this means triggers are always wrong — they remain the right tool for invariants that must hold regardless of which system or code path writes to the table (a true database-level guarantee, like an audit trail that must exist even if written to via a raw psql session) or for keeping a tightly-coupled derived value in sync where the alternative (every application code path remembering to update it) is more fragile than the trigger itself. The risk is specifically in using triggers for logic that's really workflow, not invariant enforcement — that logic almost always belongs in application code where it's visible, testable, and easy to change.