Slovník pojmů
Databázová transakce
Transakce sjednotí související databázové změny do jednoho výsledku. Neřeší však sama vzdálenou platbu, e-mail ani zprávu odeslanou do jiné služby.
Stručná definice
Buď platí celý lokální krok, nebo žádná jeho část.
Databázová transakce začíná před souvisejícími čteními a zápisy. Pokud vše projde, COMMIT změny potvrdí a zpřístupní je dalším transakcím. Při chybě, konfliktu nebo vědomém rozhodnutí ROLLBACK dosavadní změny této transakce zahodí. Typicky tak spolu vznikne objednávka, její položky a rezervace skladu, nikoli jen první z nich.
Transakce je vlastnost konkrétní databáze a jejího připojení. Neznamená automaticky transakci přes HTTP, frontu zpráv, Redis ani API dopravce. Její hranice proto patří kolem malé konzistentní lokální operace, ne kolem dlouhého importu, síťového volání nebo čekání na člověka.
K čemu se používá
Kde by neúplný zápis poškodil stav aplikace
Transakce chrání změny, které musí být v lokální relační databázi viditelné společně.
- založení objednávky, položek, ceny a vazby na zákazníka
- rezervace skladu spolu se změnou dostupného množství
- uložení importované objednávky a jejího jedinečného externího identifikátoru
- změna stavu reklamace spolu s auditním záznamem
- převod peněz mezi dvěma lokálními účty vedenými v jedné databázi
Praktický příklad
Import objednávky z marketplace
Importer obdrží objednávku, jejíž odpověď se může kvůli timeoutu opakovat. V krátké transakci založí objednávku a integrační záznam; položky by vznikaly stejným lokálním krokem. Unikátní constraint nad marketplace a external_order_id chrání proti druhému založení; při duplicitě aplikace načte už existující objednávku.
Po commitu se samostatný worker postará o odeslání informace do dalších systémů. Pokud odeslání selže, nevrací se již hotová objednávka zpět. Retry pracuje s uloženým outbox záznamem a idempotentním významem události.
try {
$connection->beginTransaction();
$connection->insert('marketplace_order', [
'marketplace' => 'example-market',
'external_order_id' => $externalOrderId,
'status' => 'new',
]);
$connection->insert('outbox_event', [
'type' => 'order.imported',
'payload' => json_encode(['externalOrderId' => $externalOrderId], JSON_THROW_ON_ERROR),
]);
$connection->commit();
} catch (\Throwable $exception) {
if ($connection->isTransactionActive()) {
$connection->rollBack();
}
throw $exception;
}
Jak funguje
Hranice změny od začátku k potvrzení
Přesný průběh se liší podle databáze a izolace, základní rozhodnutí je ale stejné.
- BEGIN Aplikace otevře transakci a databáze jí vytvoří pracovní kontext pro čtení a změny.
- Kontrola stavu Načtou se potřebná data a vyhodnotí pravidla. Samotné čtení bez constraintu nebo vhodného zámku nemusí zabránit paralelní změně.
- Lokální zápisy Vzniknou nebo se upraví řádky, vztahy a auditní záznam. Constrainty hlídají povolený stav i proti jinému procesu.
- COMMIT nebo ROLLBACK Při úspěchu se změny potvrdí; při selhání se vrátí. Některé konflikty je nutné zachytit a operaci bezpečně zopakovat.
- Navazující práce Událost pro externí systém se obvykle uloží jako lokální outbox záznam a odešle až samostatný worker.
Hlavní části a pojmy
Atomita nestačí bez správného návrhu souběhu.
Pojmy ACID jsou užitečná zkratka, ale konkrétní chování určuje databáze, izolace i dotaz.
Atomita a trvanlivost
Atomita znamená, že lokální skupina změn není potvrzena jen napůl. Trvanlivost znamená, že potvrzená data databáze chrání i proti výpadku podle své konfigurace a provozních záruk.
Izolace a MVCC
Současné transakce nemusí vidět stejná data ve stejný okamžik. PostgreSQL používá MVCC; vyšší izolace může omezit anomálie, ale při serializačním konfliktu vyžadovat retry celé transakce.
Constraint, zámek a retry
UNIQUE nebo FOREIGN KEY chrání konkrétní invariant. Cílené zamknutí může chránit kritickou řádku. Ani jedno nenahrazuje ošetření chyby, konfliktu a bezpečné opakování.
Hranice transakce
Má být krátká a zahrnovat jen nutná databázová data. Dlouhá transakce drží zdroje, komplikuje úklid starých verzí a zvyšuje pravděpodobnost konfliktu.
Nested transakce a savepoint
Vnořený aplikační blok nebývá vždy nezávislá databázová transakce. Některé knihovny používají savepoint, který dovolí vrátit část práce, ale vnější transakce stále určuje konečný commit.
Vztah k ostatním nástrojům
Transakce je databázová hranice v širším procesu.
Aplikační vrstva musí vědět, co se stane před commitem, po něm a mimo databázi.
- PostgreSQL
- Databáze poskytuje transakční model, izolaci, constrainty a diagnostiku souběžných dotazů.
- Doctrine ORM a DBAL
- PHP vrstva může vymezit hranici transakce, ale nesmí skrýt chování výsledného SQL a výjimky databáze.
- Idempotence
- Retry po timeoutu vyžaduje, aby opakovaný vstup nevytvořil další objednávku nebo platbu.
- Fronta zpráv
- Odeslání zprávy nelze považovat za potvrzené jen proto, že se potvrdila databázová transakce; pomáhá outbox a samostatné doručení.
Výhody a omezení
Pevnější konzistence má cenu v souběhu a době běhu.
Přínosy
- ochrana před částečně zapsaným lokálním stavem
- jasná hranice pro constrainty, audit a ošetření chyb
- předvídatelnější práce paralelních requestů a workerů
- možnost vrátit lokální změny při očekávané chybě
Omezení a časté chyby
- síťové volání nebo e-mail uvnitř transakce zbytečně prodlužuje zámky
- představa, že commit databáze potvrzuje i externí API
- chybějící UNIQUE constraint u idempotentního importu
- ignorování deadlocku nebo serializačního konfliktu bez promyšleného retry
- příliš široká transakce přes celý HTTP request
Kdy dává smysl
Pro změnu, která má lokální a jasně vyjádřená pravidla.
Transakce je přirozená pro objednávku, stav skladu, uživatelská oprávnění nebo interní proces, v němž musí souhlasit několik tabulek. U jednoduchého jediného INSERTu může databáze použít implicitní transakci, ale návrh stále musí počítat s constrainty a chováním při duplicitě.
Není vhodná jako mechanismus pro dlouhé importy, pomalé generování souboru nebo čekání na odpověď dopravce. Taková práce se rozdělí na malé idempotentní kroky; lokální stav se potvrdí a worker následně zpracuje navazující úkol. Distribuovanou konzistenci nelze získat jen roztažením jedné SQL transakce přes celý systém.
Na co myslet
Hranice musí odpovídat skutečnému pravidlu dat.
Dobrá transakce je krátká, měřitelná a umí selhat čitelným způsobem.
- zapisovat do transakce jen data, která musejí změnit stav společně
- chránit důležité invarianty databázovým constraintem, ne jen podmínkou v PHP
- nevolat pod zámkem vzdálené API, e-mail ani pomalý souborový systém
- logovat a řešit deadlocky, timeouty a konflikty podle typu operace
- navrhnout retry spolu s idempotentním klíčem nebo unikátním constraintem
Časté otázky
Transakce v provozované aplikaci
Vrátí rollback i odeslaný e-mail nebo požadavek na API?
Ne. Rollback vrací změny v dané databázové transakci. Vzdálené vedlejší účinky potřebují samostatný návrh, například outbox, retry a idempotentní API.
Je každá SQL operace transakcí?
Databáze může pro samostatný příkaz použít implicitní transakci. Výslovná transakce je nutná, když má jako celek platit více závislých kroků.
Vyřeší transakce všechny souběžné chyby?
Ne. Izolace, constrainty, zámky a retry řeší různé situace. Dvě transakce mohou stále soutěžit o stejný businessový stav.
Proč nemá být transakce dlouhá?
Dlouho držené zdroje zvyšují riziko čekání, konfliktů a provozních problémů. Do transakce patří jen nezbytný lokální databázový krok.
Jak pracuji s databázemi v praxi
Datový stav navrhuji s jasnými pravidly a hranicemi změny.
V e-commerce a integračních aplikacích řeším transakce, constrainty, importy i dohledání stavu tak, aby se kritická data neměnila jen napůl.