trigrTakéDatabázový trigger, DB trigger, SpoušťPokročilý
Definice
Trigger je kus kódu uložený v databázi, který se automaticky provede při určité události nad tabulkou, typicky před nebo po operaci INSERT, UPDATE či DELETE. Aplikace ho nevolá: spouští ho sám databázový stroj v rámci téže transakce, takže slouží k auditu, kontrole integrity nebo dopočítávání odvozených hodnot.
Nezaměňujte: Trigger znamená v databázích proceduru automaticky spouštěnou událostí nad tabulkou, v CI/CD událost startující pipeline a v serverless prostředí vazbu mezi zdrojem události a funkcí.
Co trigger v databázi řeší
Trigger přesouvá pravidlo z aplikace do databáze. Pokud musí každý zápis do tabulky objednavky zanechat stopu v auditní tabulce, může to udělat každá aplikace zvlášť, nebo to jednou zajistí trigger. Databáze pak garantuje, že pravidlo platí i pro ruční SQL v konzoli, migrační skript nebo import dat, tedy pro cesty, které aplikační vrstva nikdy neuvidí.
Kdy přesně se trigger provede
Trigger má tři osy: událost (INSERT, UPDATE, DELETE, případně TRUNCATE), časování (BEFORE, AFTER, INSTEAD OF) a granularitu (FOR EACH ROW nebo FOR EACH STATEMENT). Kombinace určuje chování. BEFORE ... FOR EACH ROW může měnit hodnoty řádku ještě před zápisem nebo operaci zrušit. AFTER vidí už uložený stav a hodí se pro odvozené zápisy jinam. INSTEAD OF se v PostgreSQLu váže na pohledy a umožňuje z nich udělat zapisovatelné objekty.
Zásadní je, že tělo triggeru běží uvnitř transakce, která operaci vyvolala. Výjimka nebo RAISE EXCEPTION uvnitř triggeru shodí celý příkaz včetně původního zápisu. To je jeho síla i riziko.
Cena, kterou za automatiku platíte
Řádkový trigger se volá pro každý dotčený řádek. Hromadný UPDATE nad milionem řádků znamená milion vyvolání procedury, což u dávkových operací dokáže dobu běhu znásobit. Druhý náklad je neviditelnost: trigger se v aplikačním kódu nikde neobjeví, takže vývojář ladící podivný stav dat nemá důvod ho hledat. Třetí je pořadí: pokud nad jednou tabulkou visí několik triggerů, PostgreSQL je spouští abecedně podle názvu, což je vazba na pojmenování, ne na záměr.
Vzájemně se volající triggery navíc umí vytvořit cyklus (zápis do A spustí zápis do B a ten zpět do A). Většina strojů má limit hloubky rekurze, ale odhalí se to až za běhu.
Trigger, constraint, nebo aplikační logika?
Pro jednoduchá pravidla nad jedním řádkem je lepší CHECK, UNIQUE nebo cizí klíč: deklarativní omezení plánovač zná, umí je optimalizovat a nepřekvapí. Trigger nastupuje tam, kde pravidlo přesahuje jeden řádek nebo jednu tabulku, typicky audit, denormalizované součty, měkké mazání nebo verzování záznamů. Pokud logika volá externí služby, posílá e-maily nebo trvá stovky milisekund, do triggeru nepatří: blokuje transakci a při rollbacku už se odeslaný e-mail nevrátí. Vhodnější je zapsat úlohu do fronty (outbox) a zpracovat ji asynchronně.
Trigger mimo databáze
Slovo trigger se používá i jinde. V CI/CD označuje událost startující pipeline (push do větve, pull request, cron). V serverless světě je trigger vazba mezi zdrojem události a funkcí, například nový objekt v úložišti spustí funkci. Ve všech případech platí stejná myšlenka: kód se nespouští voláním, ale reakcí na událost.
Příklady z praxe
Auditní stopa změn ceny produktu
Obchod potřebuje vědět, kdo a kdy měnil cenu, včetně změn provedených ručně v konzoli. AFTER UPDATE trigger na tabulce produkty zapíše starou a novou hodnotu do tabulky produkty_audit. Protože běží ve stejné transakci, buď se uloží změna i její záznam, nebo nic.
CREATE FUNCTION log_cena() RETURNS trigger AS $$ BEGIN IF NEW.cena IS DISTINCT FROM OLD.cena THEN INSERT INTO produkty_audit(produkt_id, cena_stara, cena_nova, zmeneno) VALUES (OLD.id, OLD.cena, NEW.cena, now()); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER produkty_audit_trg AFTER UPDATE ON produkty FOR EACH ROW EXECUTE FUNCTION log_cena();Import dat, který trval osm hodin
Noční import 3 milionů řádků do skladové tabulky se protahoval, přestože samotný COPY byl rychlý. Příčinou byl řádkový AFTER INSERT trigger přepočítávající součty, tedy tři miliony volání plpgsql funkce. Řešením bylo trigger na dobu importu vypnout příkazem ALTER TABLE ... DISABLE TRIGGER a součty dopočítat jedním agregačním dotazem na konci.
ALTER TABLE sklad DISABLE TRIGGER sklad_soucty_trg; -- COPY sklad FROM '/data/import.csv' CSV; ALTER TABLE sklad ENABLE TRIGGER sklad_soucty_trg;
Časté omyly
- MýtusTrigger je rychlejší než stejná logika v aplikaci, protože běží přímo v databázi.
- Ve skutečnostiTrigger ušetří síťová kolečka mezi aplikací a databází, ale u řádkové granularity se jeho tělo vykoná pro každý dotčený řádek. Při dávkových operacích bývá jedno hromadné SQL z aplikace výrazně levnější než milion vyvolání procedury.
- MýtusKdyž má tabulka trigger, mám jistotu, že se pravidlo nikdy neobejde.
- Ve skutečnostiTrigger lze vypnout příkazem ALTER TABLE ... DISABLE TRIGGER a některé cesty ho míjejí úplně, například TRUNCATE u řádkových triggerů nebo hromadné operace v jiných strojích. Pro tvrdou garanci integrity slouží deklarativní omezení, ne trigger.
- MýtusTrigger je dobré místo pro odeslání e-mailu nebo zavolání API po vytvoření objednávky.
- Ve skutečnostiTrigger běží uvnitř transakce, takže blokuje zápis po dobu externího volání a při rollbacku už odeslaný e-mail nevezmete zpět. Standardním řešením je zapsat záznam do outbox tabulky a odeslání řešit samostatným workerem.
Časté dotazy
- Jak zjistím, jaké triggery jsou na tabulce v PostgreSQL?
- Triggery na konkrétní tabulce vypíše v psql příkaz \d nazev_tabulky, který je uvede v sekci pod indexy a omezeními. Programově je najdete v systémovém katalogu pg_trigger spojeném s pg_class, případně v pohledu information_schema.triggers, který je přenositelnější mezi databázemi. Při ladění nečekaného chování dat se vyplatí tento krok udělat hned na začátku: trigger v aplikačním kódu nikde nevidíte, takže se snadno stane, že hledáte příčinu změny hodnoty v místě, kde žádná není.
- Může trigger zrušit probíhající operaci?
- Trigger může operaci zrušit dvěma způsoby. BEFORE trigger s řádkovou granularitou vrátí NULL, čímž se daný řádek tiše přeskočí a zbytek příkazu pokračuje. Druhou možností je vyvolat chybu, v PL/pgSQL příkazem RAISE EXCEPTION, což ukončí celý příkaz a odroluje transakci. Tiché přeskočení řádku je zrádné, protože aplikace dostane úspěšnou odpověď, ale data se neuložila. Pokud jde o pravidlo, jehož porušení má být viditelné, je vždy lepší vyhodit výjimku s jasnou zprávou.
- Proč se trigger spustil dvakrát?
- Dvojí spuštění triggeru mívá tři obvyklé příčiny. První je situace, kdy trigger sám zapisuje do tabulky, nad níž visí, a vyvolá se rekurzivně. Druhou je definice triggeru pro více událostí najednou, například INSERT OR UPDATE, kombinovaná s příkazem typu upsert. Třetí bývá replikace nebo více triggerů se stejným tělem pod různými názvy. Diagnostiku usnadní logování názvu triggeru a hodnoty TG_OP přímo v těle funkce, případně kontrola pg_trigger na duplicity.
- Kdy je lepší použít constraint místo triggeru?
- Deklarativní omezení jako CHECK, UNIQUE, NOT NULL a cizí klíč jsou lepší volba všude, kde pravidlo popisuje platný stav jednoho řádku nebo jednoduchý vztah mezi tabulkami. Plánovač o nich ví, umí je využít při optimalizaci, nelze je obejít pořadím operací a jejich chování je jasné z definice schématu. Trigger má smysl teprve tam, kde pravidlo potřebuje procedurální logiku, přístup ke starým i novým hodnotám nebo zápis do jiné tabulky, tedy typicky u auditu, verzování a odvozených agregací.
Zdroje
- CREATE TRIGGER(otevře se v novém okně)
- Trigger Functions(otevře se v novém okně)
- CREATE TRIGGER(otevře se v novém okně)
- Database trigger(otevře se v novém okně)