Mayank
Mayank

Reputation: 5728

Postgresql: PREPARE TRANSACTION

I've two DB servers db1 and db2.

db1 has a table called tbl_album
db2 has a table called tbl_user_album

CREATE TABLE tbl_album
(
id    PRIMARY KEY,
name  varchar(128)
...
);

CREATE TABLE tbl_user_album
(
id          PRIMARY KEY,
album_id    bigint
...
);

Now if a user wants to create an album what my php code needs to do is:

Is it possible to keep these two statements in a transaction? I'm ok with a php solution too. I mean I'm fine if there is a solution that needs php code to retain db handles and commit or rollback on those handles.

Any help is much appreciated.

Upvotes: 2

Views: 4994

Answers (3)

Peter Krauss
Peter Krauss

Reputation: 13920

It will be easier with PDO...

The main advantage of PDO is to capture errors (by PHP error line or returning SQL error messages) of each single SQL statment in the transaction. See pdo.begintransaction, pdo.commit, pdo.rollback and pdo.error-handling.

Example:

$dbh->beginTransaction();
/* Do SQL */
$sth1 = $dbh->exec("CREATE TABLE tbl_album (..)");
$sth2 = $dbh->exec("CREATE TABLE tbl_user_album(..)");
/* Commit the changes */
$dbh->commit();

Upvotes: 0

filiprem
filiprem

Reputation: 7114

Yes it is possible, but do you really need it?

Think twice before you decide this really must be two separate databases.

You could just keep both connections open and ROLLBACK the first command if the second one fails.

If you'd really need prepared transactions, continue reading.

Regarding your schema - I would use sequence generators and RETURNING clause on database side, just for convenience.

CREATE TABLE tbl_album (
  id    serial PRIMARY KEY,
  name  varchar(128) UNIQUE,
  ...
);
CREATE TABLE tbl_user_album (
  id          serial PRIMARY KEY,
  album_id    bigint NOT NULL,
  ...
);

Now you will need some external glue - distributed transaction coordinator (?) - to make this work properly.

The trick is to use PREPARE TRANSACTION instead of COMMIT. Then after both transactions succeed, use COMMIT PREPARED.

PHP proof-of-concept is below.

WARNING! this code is missing the critical part - that is error control. Any error in $db2 should be caught and ROLLBACK PREPARED should be executed on $db1 If you don't catch errors you will leave $db1 with frozen transactions which is really, really bad.

<?php
$db1 = pg_connect( "dbname=db1" );
$db2 = pg_connect( "dbname=db2" );
$transid = uniqid();

pg_query( $db1, 'BEGIN' );
$result = pg_query( $db1, "INSERT INTO tbl_album(name) VALUES('Absolutely Free') RETURNING id" );
$row = pg_fetch_row($result);
$albumid = $row[0];
pg_query( $db1, "PREPARE TRANSACTION '$transid'" );
if ( pg_query( $db2, "INSERT INTO tbl_user_album(album_id) VALUES($albumid)" ) ) {
    pg_query( $db1, "COMMIT PREPARED '$transid'" );
}
else {
    pg_query( $db1, "ROLLBACK PREPARED '$transid'" );
}
?>

And again - think before you will use it. What Erwin proposes might be more sensible.

Oh and just one more note... To use this PostgreSQL feature, you need to set max_prepared_transactions config variable to nonzero value.

Upvotes: 5

Erwin Brandstetter
Erwin Brandstetter

Reputation: 656231

If you can access db2 from within db1, then you could optimize the process and actually keep it all inside a transaction. Use dblink or SQL MED for that.

If you roll back a transaction on the local server, what has been done via dblink on a remote server will not be rolled back. (That is one way to make changes persistent even if a transaction is rolled back.)

But you can execute code on the remote server that rolls back if not successful, and only execute it, if the operation in the local db has been successful first. If the remote operation fails you can roll back locally, too.

Also, use the RETURNING clause of INSERT to return id from a serial column.

Upvotes: 3

Related Questions