Thoughts on tackling InnoDB’s auto increment
As I mentioned in my previous entry, InnoDB has it’s own auto increment counter which it uses to generate the next value for the database kernel (as we call it in Drizzle). At Drizzle project, we came across a suspicion that InnoDB doesn’t increment it’s internal counter on row updates. So what can this mean to you as a database admin?
Well, consider this simple table:
CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, val INT ) ENGINE=InnoDB;
and the following statements:
drizzle> INSERT INTO t1 (val) VALUES (1); Query OK, 1 row affected (0.01 sec) drizzle> UPDATE t1 SET a=4 WHERE a=1; Query OK, 1 row affected (0.01 sec)
We should now have a table with one row where the primary key value is 4:
drizzle> SELECT * FROM t1; +---+------+ | a | val | +---+------+ | 4 | 1 | +---+------+ 1 row IN SET (0.00 sec)
So far so good! Now remember our hypothesis that InnoDB doesn’t temper with the internal counter on an update. This would mean that our next row should have a primary key value of 2 since the counter variable should still be at 1 from our initial INSERT statement:
drizzle> INSERT INTO t1 (val) VALUES (1); Query OK, 1 row affected (0.01 sec) drizzle> SELECT * FROM t1; +---+------+ | a | val | +---+------+ | 2 | 1 | | 4 | 1 | +---+------+ 2 rows IN SET (0.00 sec) drizzle> SELECT LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 2 | +------------------+ 1 row IN SET (0.00 sec)
As expected, we have a new row with a primary key value of 2. But hey, what happens if we keep inserting into this table? specifically twice (assuming that you haven’t changed the auto-increment-increment system variable).
drizzle> INSERT INTO t1 (val) VALUES (1); Query OK, 1 row affected (0.01 sec) drizzle> INSERT INTO t1 (val) VALUES (1); ERROR 1062 (23000): Duplicate entry '4' FOR KEY 'PRIMARY'
Yep, a duplicate key error! This isn’t really surprising since the increment was resumed from a value less than what we had updated the first value to. I’m not even sure if this sort of operation would be done in production but it shows how this behavior can be dangerous.
Something to note here is that, even on the INSERT error that we just experienced, InnoDB will update the internal count because get_auto_increment() was called when processing the query. So, this means that the next INSERT query will work fine since we’ve now passed the maximum value:
drizzle> INSERT INTO t1 (val) VALUES (1); Query OK, 1 row affected (0.02 sec) drizzle> SELECT * FROM t1; +---+------+ | a | val | +---+------+ | 2 | 1 | | 3 | 1 | | 4 | 1 | | 5 | 1 | +---+------+ 4 rows IN SET (0.00 sec)
Great! now we can hopefully keep incrementing happily.
I’ve confirmed the internal behavior of this example with GDB and also verified the same behavior in MySQL 5.1.30. Whether this behavior is inappropriate or not deserves a discussion of it’s own and you could of course, absorb this problem in the application layer but the important question here is, what is the right behavior for Drizzle?
Well, take a look at how MyISAM handles the same set of queries:
drizzle> INSERT INTO t2 (val) VALUES (1); Query OK, 1 row affected (0.00 sec) drizzle> UPDATE t2 SET a=4 WHERE a=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 drizzle> INSERT INTO t2 (val) VALUES (1); Query OK, 1 row affected (0.00 sec) drizzle> SELECT * FROM t2; +---+------+ | a | val | +---+------+ | 4 | 1 | | 5 | 1 | +---+------+ 2 rows IN SET (0.00 sec) drizzle> SELECT last_insert_id(); +------------------+ | last_insert_id() | +------------------+ | 5 | +------------------+ 1 row IN SET (0.00 sec)
As you can see above, MyISAM will resume incrementing from the updated primary key value. To me this is the correct behavior that should be consistently provided to DBAs. So the next question is, how can we make InnoDB behave that way?
Ideas on tackling this issue
A simple solution would be to make InnoDB update the internal count on updates (e.g. ha_innobase::update_row) when it deals with with auto increment. This approach, however can end up being complicated (code wise) and worst of all, it can create a minor overhead to the update operation on InnoDB which may not be favorable for our target users.
So after briefly discussing this issue with Monty Taylor, seems the appropriate fix for this is to leave ha_innobase::update_row() as it is (don’t increment the internal count) and handle this on INSERT. Specifically speaking, on a row that doesn’t have a user specified auto increment value.
This means that we can recalculate the auto increment value on duplication/collision since the user is expecting an auto-generated discrete value. Needless to say, an error must be thrown if a collision occurs on a user-specified value.
So yeah, I’m flying to the US in couple of days so this should be something fun to hack on amongst other work related tasks on my boring flight :)
