Portál AbcLinuxu, 13. května 2025 00:50

Dotaz: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql

4.4.2015 07:12 bek
Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Přečteno: 874×
Odpovědět | Admin
Ahoj. Pokud je k mé aplikaci veliké množství přístupů najednou, pak se čas od času objeví chyba "There is no active transaction". V PHP používám "vnořování" transakcí pomocí obalové třídy nad PDO, konkrétně metody:
	public function beginTransaction() {
		if ($this->transLevel === 0) {
			$this->connection->beginTransaction();
		} else {
			$this->connection->exec("SAVEPOINT LEVEL{$this->transLevel}");
		}
		$this->transLevel++;
	}

	public function commit() {
		$this->transLevel--;
		if ($this->transLevel <= 0) {
			$this->transLevel = 0;
			$this->connection->commit();
		} else {
			$this->connection->exec("RELEASE SAVEPOINT LEVEL{$this->transLevel}");
		}
	}

	public function rollBack() {
		$this->transLevel--;
		if ($this->transLevel <= 0) {
			$this->transLevel = 0;
			$this->connection->rollBack();
		} else {
			$this->connection->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->transLevel}");
		}
	}
V kódu se to používá takto:
for ($raceI = 0; $raceI <= 10; $raceI++) {
	$inTransaction = false;
	try {
		$this->dbConnection->beginTransaction();
		$inTransaction = true;

		... NEJAKE DOTAZY, KTERE MOHOU ROVNEZ OBSAHOVAT TAKOVE KONSTRUKCE

		$this->dbConnection->commit();
		break;
	} catch (\PDOException $e) {
		if ($inTransaction) {
			$this->dbConnection->rollBack();
		}
		if ($e->getCode() != 40001 || $raceI === 10) { // pouzivam serialize izolaci, proto vice pokusu
			throw $e;
		}	
	}
}
Konkrétní problém je ten, že očividně proběhne někdy více rollbacků než je otevřených transakcí. Co mám na tomto kódu špatně? Jedině snad, že by beginTransaction ve skutečnosti někdy tiše selhala, ale to snad není ani možné ne?
Nástroje: Začni sledovat (0) ?Zašle upozornění na váš email při vložení nového komentáře.

Odpovědi

5.4.2015 03:14 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Odpovědět | | Sbalit | Link | Blokovat | Admin
Tak jsem na to asi přišel. I commit může vrátit chybu a postgresql může samo od sebe ukončit transakci, proto rollBack už nefunguje, když na commitu se to ukončilo. Po dlouhém debugování jsem přišel na to, že před tou chybou se objeví:
SQLSTATE[40001]: Serialization failure: 7 ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
Poradí mi někdo zkušený, zda je zaručeno, že commit zkončený chybou vždy způsobí ukončení transakce resp. daného SAVEPOINT?
okbob avatar 5.4.2015 07:55 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Podle dokumentace by to tak mělo být http://www.postgresql.org/docs/9.4/static/sql-begin.html
Use COMMIT or ROLLBACK to terminate a transaction block.
5.4.2015 09:23 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
OK, díky. Mimochodem toto odhalilo i ten fakt, že se občas nějaká transakce nepovede ani pokud ji opakuji třeba 20x. Konkrétně se jedná o problémy při SELECTU. Zkoušel jsem dávat i sleep mezi opakování transakce, rovněž bez úspěchu. Problém občas nastává při současném připojení k aplikaci. Vzhledem k tomu, že se mi zaloguje jen jeden error, tak to znamená, že selže transakce pouze na jednom spojení a na ostatních se po zopakování provedou. Dá se tohle nějak pořešit? Snižovat úroveň transakcí není dobré řešení.
5.4.2015 12:02 Kit | skóre: 45 | Brno
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Při SELECTu přece transakci nepotřebuješ.
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
7.4.2015 18:22 j
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Zalezi co selectuje a jak to pak hodla pouzit.
okbob avatar 5.4.2015 13:30 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Chyba "serialization failure" znamená, že došlo k race condition, a že je nutné transakci zopakovat. Na úrovni izolace transakce SERIALIZATION je tato chyba běžná. Jediné, co je možné, je nespoléhat se na Postgres, a přejít na úroveň REPEATABLE READ, a race condition si pořešit ručně pomocí SELECT FOR UPDATE - cenou je asi potenciálně větší čekání na zámky.
5.4.2015 14:00 Kit | skóre: 45 | Brno
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Obávám se, že jen dělá jednu ze základních chyb: Dělá SELECT jen proto, aby na základě získaných dat provedl UPDATE.
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
okbob avatar 5.4.2015 14:31 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Což SERIALIZABLE řeší - za cenu, že to občas řízeně spadne.
5.4.2015 18:18 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Pouze v 1 případě. V transakci mi to běží proto, že potřebuji konzistentní pohled na data bez fantomů během načítání dat do ORM. Příklad:
  1. Zahájím transakci
  2. SELECT id FROM tabulka1 INNER JOIN ... INNER JOIN ... WHERE sloupec NOT IN (SELECT MAX .. FROM) .. AND sloupec2 < ...
  3. Pro každý vrácený výsledek načtu objekty: ...findById(id) - i tato fce používá různé agregace při načítání
  4. Ukončím transakci
Možná to jen špatně chápu a stačilo by mi REPEATABLE READ. Mám záruku, že 2x načtu stejná data za stejných podmínek i kdyby ta data jiná transakce zatím mazala jinde? Vím, že v postgresql by i REPEATABLE READ mělo být bez fantomů, ale na http://www.postgresql.org/docs/9.1/static/transaction-iso.html je v 13.2.3 příklad s počítáním SUM, který chápu tak, že pokud transakce A provede úpravu, pak to může ovlivnit i druhou běžící transakci. Nebo jak to je?
5.4.2015 18:54 Filip Jirsák
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Repeatable read vidí stav databáze, jaký byl v okamžiku začátku transakce. Při opakovaném čtení v jedné transakci tedy budete dostávat stejná data (proto se to jmenuje repeatable read).

Ten příklad neznamená, že by se transakce navzájem ovlivňovaly, právě naopak - při repeatable read se může stát, že ta transakce A nijak neovlivní transakci B, přestože by asi bylo žádoucí, aby ji ovlivnila.
5.4.2015 19:28 Kit | skóre: 45 | Brno
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Aha. Když používáš ORM, tak se tomu asi nevyhneš...
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
okbob avatar 5.4.2015 20:36 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
samotný select to nezpůsobí - někde musíte používat UPDATE, INSERT - ve vašem kódu dochází k potenciálním race condition. Můžete jim předejít REPEATABLE READ a explicitním zamykáním.
5.4.2015 21:45 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
To asi ano, jenže díky tomu pak selže i ten select.
okbob avatar 6.4.2015 08:08 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
To je na databázi, kterou transakci vybere jako tu špatnou - a kterou zabije. Případně můžete optimalizovat způsob práce - a transakce, které jsou čistě čtecí omarkovat jako READ ONLY.
5.4.2015 23:36 Filip Jirsák
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Předpokládám, že aktualizace se provádějí v jiné transakci, než čtení. Takže stejně musí mít ošetřeno, že se mezi transakcemi nezměnila data, která chce aktualizovat - k tomu izolace transakce nestačí.
6.4.2015 00:22 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Myslíte detekci změn probíhajících mimo transakce? Tzn. uživatel A načte objekt (ORM), uživatel B načte objekt, uživatel A uloží objekt, uživatel B nemůže, protože někdo jiný něco změnil? To mám ošetřeno zvlášť hlídáním hodnoty sloupce určujícím verzi dat.
okbob avatar 6.4.2015 08:05 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Tak pak možná nepotřebujete SERIALIZABLE
okbob avatar 6.4.2015 08:06 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Nový SERIALIZABLE level (počínaje 9.2) by na to stačit měl.
6.4.2015 09:46 Filip Jirsák
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Když uživatel A načte v transakci 1 stav účtu X do webového formuláře, uživatel B načte v transakci 2 stav účtu X do webového formuláře, uživatel A k zůstatku na účtu X přičte 10 Kč a v transakci 3 zůstatek uloží, a konečně uživatel B přičte k zůstatku na účtu X 10 Kč a v transakci 4 to uloží, žádná úroveň izolace transakcí to nezachrání a vždy budete mít na účtu X na konci +10 Kč a ne +20 Kč. Protože k tomu konkurenčnímu přístupu došlo mimo transakce a mimo databázi.

Samozřejmě existují jednoduché způsoby, jak tohle opravit, ale izolace transakcí v databázi s tím opravdu nic neudělá. Jedině že byste měl transakci od načtení dat až po jejich zápis. Jenže transakce zahrnující interakci s uživatelem, zvlášť s uživatelem u webového prohlížeče, u kterého nevíte, jestli tam ještě sedí a dumá nad tím číslem, nebo jestli už vytáhl počítač ze zásuvky a šel domů - taková transakce spolehlivě zlikviduje jakýkoli systém.
okbob avatar 6.4.2015 11:21 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
To už je jiná situace, která je za hranicí toho, co řeší ACID. I když si myslím, že by se to dalo na ACID napasovat - jen se musí vždy přehrát celá sekvence SQL příkazů.
okbob avatar 6.4.2015 11:31 okbob | skóre: 30 | blog: systemakuv_blog | Benešov
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Což, když si to tak promítnu vede trochu i k optimistickému zamykání.
7.4.2015 18:27 j
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Tohle se ovsem resi zcela trivialne tak, ze ani uzivatel A ani uzivatel B nedela zadny update, ale oba udelaji insert. V databazi pak muze byt sumarni pole, ktere vyjadruje stav k nejakemu okamziku, ale zaroven mam kdykoli zcela aktualni stav, vcetne historie zmen.
6.4.2015 00:41 Ivan
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
na tohle prece skore zadnou transakci nepotrebujes, vzdyt jde o samy cteni. nestacilo by jen pouzit `select max .. for update` u tabulky u ktere se pouziva not in. Tim by se to synchronizovalo, konkretni idcka by se zamknuly a commit na konci transakce by je zase odemknul.

PS: co je to za ORM, ze nezvladne konzistentni nacitani sam?
6.4.2015 07:45 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Často se zapisuje, ale většinou havaruje to čtení kvůli souběžnému zápisu. ORM používám takovou vlastní implementaci a ne na všechno, jinak by to bylo pomalé. To, že to teď občas havarovalo kvůli SERIALIZABLE spíš značí, že můj testovací stroj má slabý HW, zase díky tomu je to dobré na testování. No nicméně jsem přepnul na REPEATABLE READ s explicitním zamykáním určitých řádků a je to už OK. I když je SERIALIZABLE bezpečnější, tak se zdá nemohu moc použít. Ona ta postgresql implementace REPEATABLE READ jak jsem prozkoumal postačí.
6.4.2015 09:35 Filip Jirsák
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Nikoli, to, že transakce SERIALIZABLE občas nedoběhne kvůli konkurenčnímu přístupu, to není kvůli slabému hardwaru, to je prostě vlastnost této úrovně izolace transakcí. Stejně tak může skončit chybou i REPEATABLE READ. Transakce vám nijak nepomůžou, když nevíte, jak je používat - a vnořování transakcí to nezachrání. Prostě když používáte SERIALIZABLE nebo REPEATABLE READ, musíte počítat s tím, že transakce může selhat, a v aplikaci na to nějak zareagovat. Zrovna při čtení dat je to triviální, prostě tu čtecí sekvenci spustíte znova od začátku.
6.4.2015 18:36 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
To ano, ale na silnějším hw je větší šance, že po zopakování proběhne. protože se jiná rychleji dokončí - kromě toho teď debuguji také tím, že hromady logů zapisuji do databáze (někdy stovky řádků za vteřinu) a analyzuji - běžný soubor už by to nedával. Proto to také HW ne úplně stíhá. To, že budu muset transakce opakovat vím, s tím jsem počítal u SERIALIZABLE. Stejně mě ale chování postgresql dokázalo překvapit, do teď jsem dělal s mysql. Ale po přepnutí na REPEATABLE READ to funguje perfektně. Mělo by to stačit a snad tato úroveň opravdu v postgresql vylučuje fantomy.
6.4.2015 21:16 bek
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Odpovědět | | Sbalit | Link | Blokovat | Admin
Děkuji všem za reakce. Znovu jsem si přečetl dokumentace nejnovější verze postgresql a už jsem tu jejich implementaci myslím dostatečně pochopil. Musím uznat, že jsem byl trochu zmatený tím, že jsem doteď dělal s mysql a popis transakcí v postgresql jsem původně evidentně studoval podle dokumentace k verzím, kdy SERIALIZABLE bylo ještě implementováno jako REPEATABLE READ. Celé jsem to vyřešil přepnutím na REPEATABLE READ. Takže vzhledem k tomu, že při návrhu aplikace jsem počítal s tím, že SERIALIZABLE funguje jako REPEATABLE READ, tak to nevadí.
9.4.2015 13:05 podlesh | skóre: 38 | Freiburg im Breisgau
Rozbalit Rozbalit vše Re: Jak správně ošetřit transakce a jejich chybové hodnoty v potgresql
Pokud aplikace používá Postgresql, tak by měla být napsána tak aby se každá transakce dala automaticky zopakovat. Nejelegantnější je nějaká forma AOP nebo funkcionálně.

Založit nové vláknoNahoru

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.