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é.

  1. BEGIN Aplikace otevře transakci a databáze jí vytvoří pracovní kontext pro čtení a změny.
  2. 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ě.
  3. Lokální zápisy Vzniknou nebo se upraví řádky, vztahy a auditní záznam. Constrainty hlídají povolený stav i proti jinému procesu.
  4. 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.
  5. 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.

Zavolejte mi

Zavolám vám následující pracovní den mezi 9:00 a 17:00.

Můžete mi také zavolat rovnou.

+420 605 181 728

Nechte mi telefonní číslo a pošlete žádost o zpětné zavolání.

Odesláním souhlasíte se zpracováním údajů pro vyřízení žádosti.