PHP & PostgreSQL-Transactions

Hi,

Ich bastle gerade an einer kleinen Web-App, bei der (mehrere) Benutzer gleichzeitig an einer Datenbank arbeiten. Jeder soll dabei aber in einer Transaction arbeiten, die während seiner Session mitläuft. Am Ende soll er dann selbst entscheiden können, ob er die Änderungen übernimmt (COMIMT) oder verwirft (ROLLBACK).

Ich habe das Problem mal testweise (verwende eigentlich CakePHP) in ein einfaches PHP-Skript heruntergebrochen.

Nicht abschrecken lassen. Das Skript ist eingentlich ganz einfach =)
(Erklärung folgt nach dem Code)

PHP:
<pre>
<?
	function query ($c, $sql) {
		(pg_query($c, $sql) === false) ? "Query ($sql) ERROR" : "Query ($sql) OK"; 
	}

	$conn  = "host='localhost' port='5432' dbname='lists' ";
	$conn .= "user='xxx' password='xxx'";

	//$connection = pg_connect($conn, PGSQL_CONNECT_FORCE_NEW);
	$connection = pg_pconnect($conn); //Persistent

	if ($connection) {
		echo "Connected successfully!\n";
		pg_query($connection, "SET search_path TO public");
	}
		
	//Check transaction state
	echo "PostgreSQL Transaction Status:\nActive " . PGSQL_TRANSACTION_ACTIVE;
	echo "\nIDLE " . PGSQL_TRANSACTION_IDLE;
	echo "\nINERROR " . PGSQL_TRANSACTION_INERROR;
	echo "\nINTRANS " . PGSQL_TRANSACTION_INTRANS;
	echo "\nUknown " . PGSQL_TRANSACTION_UNKNOWN;
	echo "\n\nStatus(before)=" . pg_transaction_status($connection) . "\n";
	
		
	switch($_GET['c']) {
		case 'begin':
       			query($connection, "BEGIN;");
			break;
		case 'insert':
			query($connection, "INSERT INTO list (name, text) VALUES('test', 'transaction');");
			break;
		case 'rollback':
			query($connection, "ROLLBACK;");
			break;
		case 'commit':
			query($connection, "COMMIT;");
			break;
		case 'bic':
			query($connection, "BEGIN;");
			query($connection, "INSERT INTO list (name, text) VALUES('test', 'transaction');");
			query($connection, "COMMIT;");
			break;
		case 'bir':
			query($connection, "BEGIN;");
			query($connection, "INSERT INTO list (name, text) VALUES('test', 'transaction');");
			query($connection, "ROLLBACK;");
			break;
		default:
			echo "\nplease click one of the links below";
			break;
	}
    
	//Check transaction state
	echo "\n\nStatus(after)=" . pg_transaction_status($connection);
?>
</pre>

<a href="?c=begin">Begin</a><br>
<a href="?c=insert">Insert</a><br>
<a href="?c=rollback">Rollback</a><br>
<a href="?c=commit">Commit</a><br>
<hr>
<a href="?c=bic">Begin, Insert & Commit</a><br>
<a href="?c=bir">Begin, Insert & Rollback</a><br>

Ablauf:
  1. Verbindungsaufbau (Entweder persistent oder nicht, je nach aktiver pg_*connect-Zeile)
  2. Anzeigen des Transaction-State
  3. Durchführen der gewählten Operation
  4. Erneutes anzeigen des Transaction-State
    [/list=1]

    Es gibt folgende Operationen:
    • Transaction beginnen
    • Datensatz speichern
    • Commit
    • Rollback

    Und dann gibt es noch 2 zusätzliche Ops für Begin, Insert & Commit/Rollback.

    So nun zum Problem ?(

    Gewünschtes Verhalten wäre, dass man zuerst Beginn drückt, dann im nächsten Aufruf Insert, dann Rollback und im Nachhinein wäre der neue Eintrag nicht in der Datenbank zu sehen...tatsächlich scheint mir die Transaction am Seitenende flöten zu gehen und der Eintrag steht nach dem Rollback immernoch drin :(

    Das Begin, Insert & Rollback (in einem) funktioniert nämlich wie gewünscht.

    Wie komme ich dorthin?

    Scheut euch nicht zu antworten, auch nur um mir zu sagen, dass ich auf dem Holzweg bin ;)

    mfg

    Nachtrag - die Versionen:
    XAMPP (Apache 2.2.11, PHP 5.2.8)
    PostgreSQL 8.3.5
 
Ich kenne CakePHP ueberhaupt nicht, und habe auch noch nie was mit PostgreSQL gemacht, aber ich hab eine Ahnung.
Mal ganz abgesehen davon das die Schreib und Leselocks fehlen, welche eine Transaktion eigentlich auch beinhalten muessen(werden evtl von BEGIN gesetzt,ka), passiert glaube ich folgendes:

Du machst insert und Rolleback.
Eine Verbindung,Server weiss was getan wurde, und kann es rückgängig machen.
Du machst ein Insert, das Skript laeuft durch, und beim nächsten mal machst du ein Rolleback, was nun passiert ist, das das DBMS nicht weiss was es zurrueck nehmen soll, da neue Verbindung.
mfg

sw33t
 
Danke für die Antwort!

Ich denke auch nicht, dass es an CakePHP liegt, da es schon in diesem Bsp ohne Cake nicht funktioniert =/

Du machst insert und Rolleback.
Eine Verbindung,Server weiss was getan wurde, und kann es rückgängig machen.
Du machst ein Insert, das Skript laeuft durch, und beim nächsten mal machst du ein Rolleback, was nun passiert ist, das das DBMS nicht weiss was es zurrueck nehmen soll, da neue Verbindung.
Ja ich fürchte das ist das Problem. Deswegen hab ich das auch mit einer persistenen Verbindung probiert (pg_pconnect anstelle von pg_connect), aber auch kein Erfolg.
Gibt es irgendeine Möglichkeit das mit Transactions zu realisieren oder sollte ich umdenken und eine eigene Lösung programmieren?
 
Original von vis.p
Jeder soll dabei aber in einer Transaction arbeiten, die während seiner Session mitläuft. Am Ende soll er dann selbst entscheiden können, ob er die Änderungen übernimmt (COMIMT) oder verwirft (ROLLBACK).

Ganz ehrlich, dein ganzes Vorgehen ist zugebenermaßen eine suboptimale Idee ums mal nett auszudrücken. Schon mal was von Lost Updates gehört? Oder das große Transaktionen Gift sind für eine performante Anwendung? Nur mal die Spitze des Eisbergs zu erwähnen. Ich finde diese Art von Softwareentwicklung sollte man sich erst gar nicht angewöhnen.

odigo
 
Da wäre auch noch das Phänomen des Phantom, aber egal...

Das mit der Klasse wird nicht so funktioniere, da man in PHP keine Ressourcesn speichern kann, und eine Verbindung gilt in PHP als eine Ressource und die kannst du nun mal nicht im $_SESSION-array speichern.

Ich hatte mal ein ähnliches Problem.
Bei mir lag der Fall aber so, das ich eine feste Anzahl von Tables hatte, aber ich nur eine bestimmte Klasse von Einträgen, wegen Datenkonsistens, nicht freigeben wollte.

Problem habe ich dadurch geloest das ich die Schluessel der Einträge in eine weitere Tabelle geschrieben habe, und ich die Einträge dann nur zum Zugriff freigab wenn sie nicht in der Tabelle standen.
mfg

sw33t
 
Ja, dass das eine suboptimale Idee ist hab ich befürchtet...hat vom Prinzip her recht einfach ausgesehen. Was wäre z.B. wenn User A einen Eintrag ändert und User B ihn in seiner Transaktion löscht? Gibt dabei wohl zuviele Probleme...

Jetzt bleibt mir nur es entweder selbst zu programmieren (extra Tabelle), oder auf das Feature zu verzichten ;)

Sollte ich es mit der Tabelle lösen, werde ich gern den Weg hier posten.

Danke!
 
Zurück
Oben