ramblings on PHP, SQL, the web, politics, ultimate frisbee and what else is on in my life
back

A rollback is a rollback is a rollback?

While there are some major annoyances with transactions in MySQL like causing an implicit commit when doing DDL statements, there are also slightly more subtle annoyances. A user just stumbled over a little annoyance in MySQL transaction handling and autoincrement. If you generate a new ID inside a transaction and rollback, the autoincrement counter is not decremented.

Of course I know that autoincrement is just about generating new unique id's and I should not worry about gaps and that for most people the value range for INT is more than they will ever need, not to mention BIGINT (though BIGINT really sucks in PHP as its larger than the natively supported value range for integers in PHP). However this issue goes to show again that a ROLLBACK is not the magic undo-wand that people often expect.

mysql> create table autoinc (sequence int auto_increment primary key) engine=innodb;
Query OK, 0 rows affected (0.59 sec)

mysql> show create table autoinc\G
*************************** 1. row ***************************
       Table: autoinc
Create Table: CREATE TABLE `autoinc` (
  `sequence` int(11) NOT NULL auto_increment,
  PRIMARY KEY  (`sequence`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into autoinc (sequence) VALUES (NULL);
Query OK, 1 row affected (0.03 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+
1 row in set (0.04 sec)

mysql> rollback;
Query OK, 0 rows affected (0.06 sec)

mysql> select * from autoinc;
Empty set (0.00 sec)

mysql> insert into autoinc (sequence) VALUES (NULL);
Query OK, 1 row affected (0.04 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

Comments



Re: A rollback is a rollback is a rollback?

You're right, it's not the magic wand people expect, but for good reason. Consider two concurrent transactions, both inserting a row into your autoinc table.

Transaction 1:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into autoinc (sequence) values (null);
Query OK, 1 row affected (0.00 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 1 |
+------------------+
1 row in set (0.00 sec)

Transaction 2:

mysql> insert into autoinc (sequence) values (null);
Query OK, 1 row affected (0.00 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 2 |
+------------------+
1 row in set (0.00 sec)

Now, if you commit Transaction 2 then roll back Transaction 1, what happens to `sequence`? ;)

Re: A rollback is a rollback is a rollback?

This is a pretty common thing. Same way in Microsoft SQL Server. You also have to consider replication issues. Unfortunately nothing is ever as simple as we'd like... sigh.

Re: A rollback is a rollback is a rollback?

@Dave: sure but in my case I did not. All I did was do an insert and a reasonable expectation would be that if I rollback and execute the same insert again, with no concurrency that it would give me the same as if I would not have rolled back. And you could take the position that a sequence or autoincrement counter is nothing more than a normal data field that is incremented. This position is probably more so valid for autoincrement/identity columns.

Anyways I probably should not have labeled it annoyance. When looking at it from the perspective of preventing any issues with concurrent transactions it does make sense for a unique ID generating facility to not bother with any special rollback handling.

PostgreSQL also does not seem to rollback changes done to a sequences via nextval() and setval().

Re: A rollback is a rollback is a rollback?

Same in every database which supports sequences, generators, identities or AUTO_INCREMENT fields. Simply all numbers retreived from seqeunce are allways retreived outside of transaction, so you cannot rollback them.

Re: A rollback is a rollback is a rollback?

This is by-design in every database engine that I know of for a good reason. I'm confused why you would expect it to be otherwise. ROLLBACK is not a magic UNDO, but rather it does not merge isolated transaction level changes with the database. By design sequences must go outside of the isolated transaction to grab a unique ID.

Think of sequences as static variables and transactions as objects. Multiple transactions/objects need to access the same instance of a static variable, but none of the transactions/objects should try to "undo" any changes to that variable because they don't know what state it might currently be in.

Re: A rollback is a rollback is a rollback?

And you could take the position that a sequence
or autoincrement counter is nothing more than a
normal data field that is incremented.

in that case auto increment actions would become serialized
as transaction locks would have to be set on the sequence
object (or whatever you name it) ... no two transactions
running in parallel could obtain a new sequence value at
the same time

Re: A rollback is a rollback is a rollback?

Excellent post. I didn't know this before, and it's something to remember. I agree that it shouldn't make a lot of difference in the long run.

Re: A rollback is a rollback is a rollback?

If you use this table:

CREATE TABLE ids (
    id SERIAL PRIMARY KEY
);
-- the sequence "ids_id_seq" will be created implicitly

and you want to rollback an insert and reset the serial value, you can do this:

-- this'll bump up the sequence
BEGIN;
INSERT INTO ids() VALUES();
ROLLBACK;

-- reset the sequence directly
SELECT setval('ids_id_seq', (SELECT MAX(id) FROM ids));

I wouldn't recommend doing this on a regular basis, though. The only time I've ever had to reset a sequence is for database restoration or after a bulk `COPY` command.