kiat has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I think I'm missing something in the code below which involves operations on a number of tables. I'm trying to make use of rollback to restore all the affected tables to the pre-operation state should any of the operations fail.

I tried to test whether rollback works by tweaking the last_id. The operation to insert $last_id into preference should fail as a result of a foreign key constraint. That works (i.e. when I changed the last_id to an arbitrary higher number, the insert operation on preference failed and so none of the previous operations was effected.)

After that tweaking test, I commented out $last_id (so the code is back to normal) and called the new_account subroutine. Strangely, no change was effeced in any of the tables.

I found out I the changes can only be effected if I shut down the MySQL server and restart it.

Update 1

Thanks to pg! As he advised, I moved commit out of the eval block and now after I removed the last_id tweak, the code works normally :)

Update 2

I realised I can't have AUTO_COMMIT with rollback (the error said: Rollback ineffective while AutoCommit is on). The problem was that with AUTO_COMMIT, when the last_id was tweaked, the table members was erroneously updated. With that error message, the solution now is to disable AUTO_COMMENT as an attribute of members.

Update 3

I moved commit to after eval and so far so good.

#Platforms: Windows 98 #Perl version: 5.8.0 #MySQL version: 4.0.16 sub create_account { my ($username, $level) = @_; $dbh->{RaiseError} = 1; $dbh->begin_work; eval { ## (1) Insert username into members (TYPE=INNODB) $sth = $dbh->prepare(qq{ INSERT INTO members VALUES(?, ?, ?) }); $sth->execute(undef,$username,$level); ## (2) Select from members to get last id $sql = qq{ SELECT LAST_INSERT_ID() FROM members }; $sth = $dbh->prepare($sql); my $last_id = $sth->execute(); # Tweaking $last_id to test rollback #$last_id = 5; ## (3) Insert last_id into preference (TYPE=INNODB) $sth = $dbh->prepare(qq{ INSERT INTO preference VALUES(?, ?, ?) }); $sth->execute($last_id,undef,undef); }; # eval if ($@) { eval { $dbh->rollback(); # Call bail_out and die bail_out("Error at new_accout."); }; } $dbh->commit(); $dbh->{RaiseError} = 0; } ## Definitions of tables DROP TABLE IF EXISTS members; CREATE TABLE members ( member_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(20) NULL, level CHAR(2) NULL, ) TYPE=INNODB; DROP TABLE IF EXISTS preference; CREATE TABLE preference ( member_id MEDIUMINT UNSIGNED NOT NULL, pref1 VARCHAR(20) NOT NULL, pref2 CHAR(10) NOT NULL, PRIMARY KEY (member_id, pref1), FOREIGN KEY (member_id) REFERENCES members (member_id) ON DELETE C +ASCADE, UNIQUE (pref1) ) TYPE=INNODB;
Could someone enlighten me?

Replies are listed 'Best First'.
Re: perl-mysql question - rollback...
by perrin (Chancellor) on Dec 11, 2003 at 04:07 UTC
    Is that really your code? You spelled "preference" wrong in the SQL.

    Assuming that was a typo in putting it in here, the problem may be in your understanding of NOT NULL constraints in MySQL. It actually allows you to insert NULL in a NOT NULL column, but silently changes it to a default value (probably zero for a MEDIUMINT). To test this properly put in a crazy number for $last_id instead of undef.

      Thanks perrin, that was a typo. I modified the code from the original to reduce the number of fields to make it look less cluttered and hence the typo slipped in

      The rollback worked when I inserted a tweaked $last_id (i.e. none of the changes to the tables was effected, which is the desired result).

      #$last_id = 10;
      However, after I commented the tweaking of $last_id (so the code is back to normal) and called new_account, for some reasons, the changes weren't effected. I had to shut down the MySQL server to get make calling new_account work.

      updated

      The member_id is defined as AUTO_INCREMENT. With the code as it is (i.e. inserting undef), the member_id is automatically increased.

Re: perl-mysql question - rollback...
by pg (Canon) on Dec 11, 2003 at 05:02 UTC

    Your commit should be outside your first eval block, and put at the same indent level as rollback, so that you only commit if everything is okay, and rollback when something is wrong.

Re: perl-mysql question - rollback...
by mugwumpjism (Hermit) on Dec 11, 2003 at 05:42 UTC

    To test if rollback is supported, set $dbh->{AutoCommit} to 0, then read it again; if it's still 0, then you have transactions.

    You should have the entire transaction - from when you turned autocommit off / issued a START TRANSACTION command (ok, $dbi->begin_work() :-)) to where you call $dbi->commit() - inside one big eval(), and replay the block if it fails, say, up to three times - to help it stumble along in the case of high concurrency / locking issues.

    This might be RDBMS dependant, but you don't have to issue a ->rollback() if the commit() failed; that is implicit. ->rollback() is for when you change your mind about the query halfway through.

    $h=$ENV{HOME};my@q=split/\n\n/,`cat $h/.quotes`;$s="$h/." ."signature";$t=`cat $s`;print$t,"\n",$q[rand($#q)],"\n";