tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Jak presne na transakce v dibi

před 8 lety

darthcz
Člen | 114

Dobrý den,

nikde jsem nenašel popsanou dokumentaci transakcí v dibi. Chtěl bych se proto zeptat, jak přesně to s transakcemi v dibi je. Potřeboval bych následující věc:

Zakládám klienta – zjistím selectem, zda existuje a pokud ne potřeboval bych začít transakci:

insert do tabulky klientů (zjištění id), vložení do N:M tabulky a dále vložení různých údajů do dalších tabulek. Potřeboval bych ale při zjištění chyby v kterémkoli procesu ze zmíněných, aby byly všechny inserty vraceny.

Napadlo mě:

begin transakce

  1. insert
  2. insert
  3. insert

commit

Nevím ale, jak vrátit všechny předchozí inserty. Například nastane výjimka u insertu 3. Mám tedy v ošetření výjimky provést 2* rollback?

Nebo to funguje jako v doctrine? Tím myslím začnu transakci a všechny dotazy se ukládají do fronty, která se provede až najednou při zavolání commitu.

Za odpověď děkuji

před 8 lety

Milo
Nette Core | 1119

Asi nejjednodušší způsob je:

$db = dibi::connect(...);

$client = $db->query('SELECT xxx FROM yyy WHERE zzz')->fetch();
if (klient neexistuje)
{
    $db->begin();
    try
    {
        $db->query('INSERT INTO ....');
        $db->query('INSERT INTO ....');
        $db->query('INSERT INTO ....');
        $db->commit();
    }
    catch (DibiException $e)
    {
        $db->rollback();
        // zpracovani vyjimky
    }
}

před 8 lety

darthcz
Člen | 114

Děkuji za odpověď,

zkoušel jsem a vypadá to, že změny se provedou skutečně až po zavolání funkce commit. Překvapilo mě, že je to schopné předpovědět id uživatele, který fyzicky vlastně ani nebyl vložen bez commitu. Taktéž nebylo potřeba volat rollback, což také nechápu :) Rollback vrátí zpět úplně všechny změny, nebo jen tu poslední?

před 8 lety

Milo
Nette Core | 1119

Transakce neprobíhá na úrovni dibi, ale na úrovni databáze. Dibi (resp. jakýkoliv klient) pouze žádá server o začátek transakce, něco provede a pak požádá buď o potvrzení (COMMIT) nebo o zrušení (ROLLBACK). Při rollbacku se změněné tabulky (INSERT, UPDATE, DELETE) vrací do původního stavu, ale např. sekvence už ne. V průběhu transakce můžeš se změněnými daty počítat tak, jako kdyby se zapsali normálně.

Během transakce umí některé DB vytvářet takzvané SAVEPOINTy a při ROLLBACK se vracet pouze k nim.

Tohle je hodně zhruba napsané, existují ještě úrovně izolace transakce, atd… to je ale nad rámec fóra a nic není lepší, než dokumentace, např. PostegreSQL.

před 8 lety

Václav M.
Člen | 34

… začal jsem uvažovat o tom, že některé operace upravím na transakce, ale nějak si nejsem jist, jak u tohoto kódu

$Dotaz_Delete = array("Uzivatele_Email", "Uzivatele_Heslo", "Uzivatele_Info", "Uzivatele_Jmeno", "Uzivatele_Prava");

foreach($Dotaz_Delete as $Tabulka)
{
    $Vysledek = dibi::delete($Tabulka)
            ->where("Jmeno_ID = %s", $_POST['RusenyUzivatel'])
            ->execute();
}

bez vyjímek zajistit, že se všechna smazání provedla správně tak, aby mohl být použit commit (nebo v případě neúspěchu rollback)- a zároveň vypsán status vykonání úprav

if( ... )
{
    dibi:commit();
    return HlaseniSpravnehoVysledku("...");
}
else
{
    dibi:rollback();
    return HlaseniChybyAkce("...");
}

Já bych se totiž bez vyjímek velmi rád obešel (co nejdéle). Obzvlášť, když nepotřebuji (a nechci) vypisovat co se stalo špatně.

před 8 lety

Milo
Nette Core | 1119

Nevidím do Tvé aplikace, ale ta struktura tabulek mi přijde dost zvrácená. Pro 100% spolehlivost smazání všech řádků bych doporučil použití cizích klíčů.

Co si jinak představuješ pod pojmem „…se všechna smazání provedla správně…“? Vyjímka Ti vyskočí pouze pokud máš chybu v syntaxi SQL. Jinak musíš zjistit počet smazaných řádků a rozhodntou se, jestli to je OK.

$tabulky = array('tabulka1', 'tabulka2');
dibi::begin();
$deleted = 0;
foreach ($tabulky AS $tabulka) {
    $deleted += dibi::query('DELETE FROM %n WHERE id = %i', $tabulka, $id);
}

if ($deleted === 5) {
    dibi::commit();
    echo "Smazano 5 zaznamu";

} else {
    dibi::rollback();
    echo "Chyba";
}

EDIT: Resp. vyjímka vyskočí nejen při syntaktické chybě, ale i při chybě při provádění dotazu. Nicméně úspěch smazaných řádků musíš zjistit ručně.

Editoval Milo (6. 11. 2011 21:32)

před 8 lety

Václav M.
Člen | 34

Na ty cizí klíče se podívám.

A k těm tabulkám: proč by měla být ta struktura zvrácená? Je možné, že by se některé z těch tabulek daly sloučit do jedné, takže by nakonec místo pěti tabulek by jen dvě, ale … to je asi tak všechno, co se s tím dá udělat.

Editoval Václav M. (6. 11. 2011 22:39)

před 8 lety

HosipLan
Moderator | 4693

To je asi tak účel. Vybírat data z jedné tabulky je rychlejší než joinovat hromadu tabulek. Obzvláště to pocítíš v momentě, kdy ti nakynou.

před 8 lety

Václav M.
Člen | 34

… divné …
Nejdříve jsem to zkusil takhle

dibi::begin();

$Vysledek = 0;
foreach($Dotaz_From as $Tabulka)
{
    $Vysledek += dibi::delete($Tabulka)
            ->where($Dotaz_Where)
            ->execute();
}

if($Vysledek == count($Dotaz_From))
{
    dibi::commit();
    return HlaseniSpravnehoVysledku("Sekce byla zrušena");
}
else
{
    dibi::rollback();
    return HlaseniChybyAkce("Zrušení sekce se nezdařilo");
}

… ale zkusil jsem i

if($Vysledek === 4)

a

if($Vysledek === count($Dotaz_From))

a akce vždcky skončila rollbackem … ale když jsem zkusil použít vyjímku, akce skončila commitem. Což mi přijde naprosto šílené.

před 8 lety

HosipLan
Moderator | 4693

Tvoje logika je chybná. Počítáš s tím, že součet smazaných řádků bude stejný jako počet tabulek. To tě napadlo jak?

před 8 lety

Milo
Nette Core | 1119

A jak jsi zkoušel použít vyjímku? Pokud předpokládáš, že v každé tabulce bude vždy jeden řádek pro každého uživatele, potom může být vše v jediné tabulce a máš po starostech.

před 8 lety

Václav M.
Člen | 34

HosipLan napsal(a):

Tvoje logika je chybná. Počítáš s tím, že součet smazaných řádků bude stejný jako počet tabulek. To tě napadlo jak?

Milo to napsal přesně – v každé té zasažené tabulce pro jednoho uživatele jeden řádek (zatím).

Milo napsal(a):

A jak jsi zkoušel použít vyjímku? Pokud předpokládáš, že v každé tabulce bude vždy jeden řádek pro každého uživatele, potom může být vše v jediné tabulce a máš po starostech.

  1. try – catch
  2. Zatím by to šlo, mít vše v jedné tabulce

před 8 lety

Milo
Nette Core | 1119

Princip try – catch chápu, ale jak to použiješ s delete? Použij cizí klíče a tyhle věci vůbec nemusíš řešit.

před 8 lety

Václav M.
Člen | 34

… jestli tomu rozumím dobře, tak ty cizí klíče způsobí, že když se změní jedna tabulka (když tam zmizí určitý řádek), tak se automaticky tyto položky odstraní i z těch dalších tabulek, které jsou na ní navázány bez nutnosti zásahu člověka.

Takže potom v věci delete není nutné použít více než jeden příkaz – a odpadá nutnost použití transakce.

-->
Ovšem s těmi cizími klíči mám docela problém – server mi odmítá tabulky s cizími klíči vytvořit (a vyhazuje errno 150) – a selhává i pokus o jejich dodatečné nastavení. To samozřejmě není problém dibi, ale serveru samotného (protože jsem ty příkazy zkusil zadat i v PMA – s stejným výsledkem) a ani s pomocí lidí z fora zive.cz se to zatím nepodařilo vyřešit.

Editoval Václav M. (8. 11. 2011 16:44)