The problem
The default innodb_autoinc_lock_mode
is "1". This means InnoDB only locks the auto-increment column on the table until the end of the INSERT
statement if the number of rows to be inserted cannot be determined in advance (for example in an INSERT ... SELECT
query). For a simple INSERT
like your queries, it assigns the auto-increment ID in advance and then allows other inserts to the table immediately, for faster writing.
In general this means the IDs are consecutive but if you use INSERT IGNORE
this is a simple insert so the ID will be assigned and then potentially not actually used because the row is a duplicate.
Changing the lock mode
If you absolutely must have a consecutive identifier for each row, you can change the InnoDB auto-increment lock mode by adding the following line to your my.cnf
and restarting your MySQL server.
innodb_autoinc_lock_mode = 0
There is more information about this in the manual. Even after this change, there may be gaps in the sequence if the transaction that generates the IDs is rolled back or if the rows are deleted later, but for your example the IDs generated are "1" and "2" as you expected.
Using a trigger to simulate the same effect
If you can't edit your my.cnf
or the gaps after roll backs is an issue, you could write a trigger to update the primary key instead. Something like the following works OK. It generates a warning ("Column 'UserID' cannot be null") for each insert but it inserts successfully with consecutive IDs. Unfortunately if you delete the user with the highest ID, the next user will get the same ID again, whereas if you delete a user in the middle of the sequence there will be a gap as with auto-increment.
DROP TABLE UserAccount;
CREATE TABLE UserAccount (
UserID INT NOT NULL,
Email CHAR(32) NOT NULL UNIQUE,
Password CHAR(32) NOT NULL,
Status TINYINT DEFAULT 0,
PRIMARY KEY(UserID)
);
CREATE TRIGGER UserAccount_before_insert BEFORE INSERT ON UserAccount
FOR EACH ROW SET NEW.UserId = (
SELECT COALESCE(MAX(UserId), 0) + 1 FROM UserAccount);
INSERT IGNORE INTO UserAccount VALUES (NULL, 'test@gmail.com', 3341234, 0);
INSERT IGNORE INTO UserAccount VALUES (NULL, 'test@gmail.com', 3341234, 0);
INSERT IGNORE INTO UserAccount VALUES (NULL, 'test@gmail.com', 3341234, 0);
INSERT IGNORE INTO UserAccount VALUES (NULL, 'test2@gmail.com', 3341234, 0);
I'd have to add, I'm not completely sure how this trigger would perform if you made lots of inserts concurrently from more than one connection. So if that's an issue you might need to do some more research.