I'm using MySQL's AUTO_INCREMENT field and InnoDB to support transactions. I noticed when I rollback the transaction, the AUTO_INCREMENT field is not rollbacked? I found out that it was designed this way but are there any workarounds to this?
-
2Just a note: But the auto_increment values will reset to the max+1 of the column after a server reset. – J.D. Fitz.Gerald Mar 24 '09 at 10:15
-
1It's not mysql-specific, Postgres behaves the same way. The explanations make sense. – Nils May 07 '12 at 05:34
11 Answers
It can't work that way. Consider:
- program one, you open a transaction and insert into a table FOO which has an autoinc primary key (arbitrarily, we say it gets 557 for its key value).
- Program two starts, it opens a transaction and inserts into table FOO getting 558.
- Program two inserts into table BAR which has a column which is a foreign key to FOO. So now the 558 is located in both FOO and BAR.
- Program two now commits.
- Program three starts and generates a report from table FOO. The 558 record is printed.
- After that, program one rolls back.
How does the database reclaim the 557 value? Does it go into FOO and decrement all the other primary keys greater than 557? How does it fix BAR? How does it erase the 558 printed on the report program three output?
Oracle's sequence numbers are also independent of transactions for the same reason.
If you can solve this problem in constant time, I'm sure you can make a lot of money in the database field.
Now, if you have a requirement that your auto increment field never have gaps (for auditing purposes, say). Then you cannot rollback your transactions. Instead you need to have a status flag on your records. On first insert, the record's status is "Incomplete" then you start the transaction, do your work and update the status to "compete" (or whatever you need). Then when you commit, the record is live. If the transaction rollsback, the incomplete record is still there for auditing. This will cause you many other headaches but is one way to deal with audit trails.

- 18,754
- 7
- 41
- 61
Let me point out something very important:
You should never depend on the numeric features of autogenerated keys.
That is, other than comparing them for equality (=) or unequality (<>), you should not do anything else. No relational operators (<, >), no sorting by indexes, etc. If you need to sort by "date added", have a "date added" column.
Treat them as apples and oranges: Does it make sense to ask if an apple is the same as an orange? Yes. Does it make sense to ask if an apple is larger than an orange? No. (Actually, it does, but you get my point.)
If you stick to this rule, gaps in the continuity of autogenerated indexes will not cause problems.

- 118,853
- 40
- 150
- 176
-
7Can you qualify this a bit? I mean, why add a whole other column, along with all the overhead that that entails (index management, disk use, more IO, etc., etc.), when there's already a perfectly good value to use in the autoinc column? By definition, it offers a unique value that never repeats and always increases as records are added. The only thing it's not is continuous, but as long as you assume gaps in the sequence, I see no problems using it to do things like sorting a set of records by their insert order. In fact, I'd say it's the best way, in terms of performance and clarity. – mr. w Aug 20 '12 at 21:35
-
57
-
Does the addition of date added happens at the time of commit or at the time of insert. Could it ever happen in case of multiple threads inserting into the table a later timestamp is visible before an earlier timestamp because of the delay in execution times? – user2505915 Jun 01 '16 at 12:28
-
Being a developer for many years, after reading this answer, I feel like, I've been enlightened! – evilReiko Sep 28 '17 at 08:45
-
@JoaquínL.Robles the advice is so good that I ended up forgetting about the answer – Peter Chaula Feb 07 '18 at 15:26
I had a client needed the ID to rollback on a table of invoices, where the order must be consecutive
My solution in MySQL was to remove the AUTO-INCREMENT and pull the latest Id from the table, add one (+1) and then insert it manually.
If the table is named "TableA" and the Auto-increment column is "Id"
INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)

- 317
- 3
- 4
-
4May be this would be better `(SELECT MAX(id) AS maxval FROM table_name;)+1` – vinsa Dec 25 '14 at 19:22
-
16This won't be an atomic operation, there would definitely be small window when more than one query gets same maxID. – Ouroboros Aug 22 '15 at 07:40
-
3
-
2
-
@MichaelCorrigan why we lock a table for this very stupid thing because an application may require many tables requiring continuous auto_increments. Now if we start locking a single instance of application can be used once – Ravinder Payal Apr 18 '16 at 20:22
-
It smells with troubles. Why not creating an additional field next to id like fakeId for handling this? You would manage making it consecutive – Tebe Dec 01 '16 at 05:53
-
3You could use LOCK TABLES, but that means only one transaction at a time can have access to your table. In many apps, there are dozens or hundreds of concurrent sessions inserting or updating rows in the table. The auto-increment does not hold a lock until you commit your transaction, it only locks briefly, long enough to increment the id. (this is an old answer but it just popped up in my feed today) – Bill Karwin May 14 '19 at 11:47
Why do you care if it is rolled back? AUTO_INCREMENT key fields are not supposed to have any meaning so you really shouldn't care what value is used.
If you have information you're trying to preserve, perhaps another non-key column is needed.

- 6,253
- 3
- 28
- 36
I do not know of any way to do that. According to the MySQL Documentation, this is expected behavior and will happen with all innodb_autoinc_lock_mode lock modes. The specific text is:
In all lock modes (0, 1, and 2), if a transaction that generated auto-increment values rolls back, those auto-increment values are “lost.” Once a value is generated for an auto-increment column, it cannot be rolled back, whether or not the “INSERT-like” statement is completed, and whether or not the containing transaction is rolled back. Such lost values are not reused. Thus, there may be gaps in the values stored in an AUTO_INCREMENT column of a table.

- 22,558
- 8
- 42
- 71
If you set auto_increment
to 1
after a rollback or deletion, on the next insert, MySQL will see that 1
is already used and will instead get the MAX()
value and add 1 to it.
This will ensure that if the row with the last value is deleted (or the insert is rolled back), it will be reused.
To set the auto_increment to 1, do something like this:
ALTER TABLE tbl auto_increment = 1
This is not as efficient as simply continuing on with the next number because MAX()
can be expensive, but if you delete/rollback infrequently and are obsessed with reusing the highest value, then this is a realistic approach.
Be aware that this does not prevent gaps from records deleted in the middle or if another insert should occur prior to you setting auto_increment back to 1.

- 53,009
- 9
- 91
- 143
-
2Also note that if you do this, you should call `ANALYZE TABLE tbl` after, otherwise the old AUTO_INCREMENT value will still appear in information_schema.TABLES until it expires. – hackel Apr 08 '20 at 21:05
Concrete answer to this specific dilemma (which I also had) is the following:
1) Create a table that holds different counters for different documents (invoices, receipts, RMA's, etc..); Insert a record for each of your documents and add the initial counter to 0.
2) Before creating a new document, do the following (for invoices, for example):
UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'
3) Get the last value that you just updated to, like so:
SELECT LAST_INSERT_ID()
or just use your PHP (or whatever) mysql_insert_id() function to get the same thing
4) Insert your new record along with the primary ID that you just got back from the DB. This will override the current auto increment index, and make sure you have no ID gaps between you records.
This whole thing needs to be wrapped inside a transaction, of course. The beauty of this method is that, when you rollback a transaction, your UPDATE statement from Step 2 will be rolled back, and the counter will not change anymore. Other concurrent transactions will block until the first transaction is either committed or rolled back so they will not have access to either the old counter OR a new one, until all other transactions are finished first.

- 144
- 1
- 8
SOLUTION:
Let's use 'tbl_test' as an example table, and suppose the field 'Id' has AUTO_INCREMENT attribute
CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;
Let's suppose that table has houndred or thousand rows already inserted and you don't want to use AUTO_INCREMENT anymore; because when you rollback a transaction the field 'Id' is always adding +1 to AUTO_INCREMENT value. So to avoid that you might make this:
- Let's remove AUTO_INCREMENT value from column 'Id' (this won't delete your inserted rows):
ALTER TABLE tbl_test MODIFY COLUMN Id int(11) NOT NULL FIRST;
- Finally, we create a BEFORE INSERT Trigger to generate an 'Id' value automatically. But using this way won't affect your Id value even if you rollback any transaction.
CREATE TRIGGER trg_tbl_test_1 BEFORE INSERT ON tbl_test FOR EACH ROW BEGIN SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1; END;
That's it! You're done!
You're welcome.

- 438
- 6
- 6
-
I like your solution, how will it behave with concurrent requests? Especially the case described by @Ouroboros at https://stackoverflow.com/questions/449346/mysql-auto-increment-does-not-rollback#comment52195829_6295036, does it workaround it? – KumZ Aug 28 '19 at 19:11
-
@KumZ this might help for your answer https://stackoverflow.com/a/42683736/4564384 – mld.oscar Sep 14 '19 at 00:24
If you need to have the ids assigned in numerical order with no gaps, then you can't use an autoincrement column. You'll need to define a standard integer column and use a stored procedure that calculates the next number in the insert sequence and inserts the record within a transaction. If the insert fails, then the next time the procedure is called it will recalculate the next id.
Having said that, it is a bad idea to rely on ids being in some particular order with no gaps. If you need to preserve ordering, you should probably timestamp the row on insert (and potentially on update).

- 524,688
- 99
- 697
- 795
-
This means two transactions hitting the same table at once can never successfully both commit. That can be a huge bottleneck. And if you have a lot of foreign keys, you could easily end up with deadlocks when they have many to many relationships. My answer for gap handling only hurts a little. :-) – jmucchiello Jan 16 '09 at 03:15
$masterConn = mysql_connect("localhost", "root", '');
mysql_select_db("sample", $masterConn);
for($i=1; $i<=10; $i++) {
mysql_query("START TRANSACTION",$masterConn);
$qry_insert = "INSERT INTO `customer` (id, `a`, `b`) VALUES (NULL, '$i', 'a')";
mysql_query($qry_insert,$masterConn);
if($i%2==1) mysql_query("COMMIT",$masterConn);
else mysql_query("ROLLBACK",$masterConn);
mysql_query("ALTER TABLE customer auto_increment = 1",$masterConn);
}
echo "Done";