4

While demonstrating the INSERT statement to the students of my SQL course, we've come up on some odd behavior in MySQL 8.0. Please help us learn what is happenning. (No need for workarounds as we're aware of a few and this is for learning, not for production. Thank you)

We are creating a new database and copying some rows from the well-known Sakila sample DB, like so:

CREATE DATABASE simpsons;

USE simpsons;

CREATE TABLE `character` (
    character_id smallint unsigned NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(20) NOT NULL,
    last_name VARCHAR(20),
    shoe_size INT,
    PRIMARY KEY (character_id));

INSERT INTO `character` 
        (first_name, last_name)
    SELECT 
        first_name, last_name 
    FROM 
        sakila.actor;

When we do this and SELECT * FROM ``character`` we see that all 200 records from sakila.actor have been copied correctly over to the new character table.

The last row gets the value 200 for its character_id auto-incremented PK. The output window shows no errors in any of the above commands.

Then, when we immediately add one more record manually:

INSERT INTO `character`
    (first_name, last_name, shoe_size)
VALUES
    ('Bart', 'Simpson', 35);

Quite oddly, we find that this record gets the value 256 as its character_id and not 201.

This is despite the fact that running SHOW VARIABLES LIKE 'auto_inc%'; shows that both auto_increment_increment and auto_increment_offset are set to 1.

We would like to learn why does MySQL skip 56 numbers?


Please note, this question is different from MySQL InnoDB auto_increment value increases by 2 instead of 1. Virus? and MySQL autoincrement column jumps by 10- why? because auto_incerement_increment is 1, there are no DELETE operations in our (easily reproducible) scenario and we each are the only users of our prospective DBs. Plus none of the answers to that question are conclusive as to what actually happened. Finally, please see @Postman's wonderful answer which references a root cause not mentioned in any of the answers to the above questions. Thank you

urig
  • 16,016
  • 26
  • 115
  • 184
  • 2
    A) Who cares? B) Possibly: Abandoned transactions. C) To find out what the next value *should* be, check `SHOW CREATE TABLE`. D) You can always switch it to whatever you want with `ALTER TABLE`. – tadman May 13 '20 at 19:51
  • Try and use `VARCHAR(255)` as a default and only restrict that if you have a very compelling reason. For many things, like names and email addresses, shorter fields can cause huge hassles for your users. – tadman May 13 '20 at 19:51
  • 1
    Does this answer your question? [MySQL InnoDB auto\_increment value increases by 2 instead of 1. Virus?](https://stackoverflow.com/questions/3590391/mysql-innodb-auto-increment-value-increases-by-2-instead-of-1-virus) – Jorge Campos May 13 '20 at 19:58
  • @tadman, thank you. A) Obviously, I do. B) Which abandoned transactions? From which command? C) + D) I don't see how these help answer my question, sadly. – urig May 16 '20 at 19:14
  • @jorge-campos. Thank you, I've gone over that answer before posting. `auto_increment_increment` is 1. – urig May 16 '20 at 19:18
  • 1
    Does this answer your question? [MySQL autoincrement column jumps by 10- why?](https://stackoverflow.com/questions/206751/mysql-autoincrement-column-jumps-by-10-why) – Schwern May 16 '20 at 19:29
  • @Schwern thank you, but it doesn't. `auto_incerement_increment` is set to `1` and we're not using `IGNORE`. – urig May 16 '20 at 19:35
  • 1
    Can confirm this behavior for MySQL version 8.0.20 – Progman May 16 '20 at 19:38
  • @Progman thank you :). Was starting to feel like maybe my students and I were having a shared a hallucination LOL. – urig May 16 '20 at 19:47
  • 2
    How to reproduce: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=230c603619b55a3e0dbd0590ce91e398 – Progman May 16 '20 at 20:02
  • 1
    @Progman This is curious. Repeated insert into select doesn't change targetTable's auto_increment. https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=c4201c799c775cab1337b2d616d6bd70 – Schwern May 16 '20 at 20:31
  • 1
    @Schwern Might be related to https://stackoverflow.com/questions/52705477/mysql-show-table-status-auto-increment-is-not-correct. When you use a simple `SELECT` you see the correct allocated ids. – Progman May 16 '20 at 20:50
  • @Progman thanks but I don't see how the stats caching could (normally) have this effect. I've tried `SET PERSIST information_schema_stats_expiry = 0;` with both our repro and yours and the results are still the same (256 and 8 respectively). – urig May 16 '20 at 21:14

1 Answers1

5

This behavior has something to do with "bulk inserts" and the innodb_autoinc_lock_mode setting.

As far as I understand it (the documentation isn't quite clear about this), when you use a INSERT INTO ... SELECT statement, MySQL cannot know how many rows are actually being inserted before running the query, but the IDs for the new AUTO_INCREMENT values have to be reserved when using innodb_autoinc_lock_mode=1 (consecutive) or 2 (interleaved). From my observation it reserves a set of AUTO_INCREMENT numbers where the count is a power of 2 (cannot confirm this, only a guess). See the following example:

CREATE TABLE sourceTable(
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20)
);

CREATE TABLE targetTable(
    id INT AUTO_INCREMENT PRIMARY KEY,
    original VARCHAR(30)
);

INSERT INTO sourceTable(name) VALUES ('one');
INSERT INTO sourceTable(name) VALUES ('two');
INSERT INTO sourceTable(name) VALUES ('three');
INSERT INTO sourceTable(name) VALUES ('four');
INSERT INTO sourceTable(name) VALUES ('five');

INSERT INTO targetTable(original) SELECT name FROM sourceTable;

INSERT INTO targetTable(original) VALUES ('manual');

SELECT * FROM targetTable;

This will generate the following output:

+----+----------+
| id | original |
+----+----------+
|  1 | one      |
|  2 | two      |
|  3 | three    |
|  4 | four     |
|  5 | five     |
|  8 | manual   |
+----+----------+

When inserting the 5 rows from the source table, it reserves the next 8 possible AUTO_INCREMENT values because that is the closest power of 2 number greater than 5. However, it will use only 5 of them since you insert only 5 rows.

In your case, you are inserting 200 rows, so the closest power of 2 number greater than 200 would be 256. So you have a "gap" of 56 missing AUTO_INCREMENT values and the next entry gets the ID 256.

Progman
  • 16,827
  • 6
  • 33
  • 48
  • Now I'm curious - How does MySQL know which power of 2 to use? – urig May 18 '20 at 18:19
  • @urig Unknown (and keep in mind that my assumption is just speculation). Maybe it's some kind of iteration process where bigger slot ranges are reserved while reading the source data. So it might reserve `1` at the beginning, than one additional `1`, then `2`, then `4`, then `8`,... until it reserves a final slot range of `128` (to the already used `128` IDs) to fill the remaining rows from the original `200` rows. But again, just speculation. – Progman May 18 '20 at 19:11
  • From having read the article (https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html#innodb-auto-increment-lock-modes) I thought the reservation of auto-inc values was done once, at the beginning of execution? How come it's done iteratively? And why are the increments throttling up? I wish someone who knows the code would chime in LOL – urig May 18 '20 at 19:22
  • @urig I may be wrong, but I think it is simply finding the next power of 2 that is greater than the current max(auto_inc). So for instance, I have a table that had 10,013 rows and the max id# was also 10,013. Then I did a bulk insert, and my next id# was 16,384. (10^13 = 8,192 < 10,013 .... 10^14 = 16,384 > 10,013, so 16,384 is chosen) – snoski Sep 13 '22 at 18:11
  • 1
    This answer confirms the theory that this is a power of 2, and includes a link to the source code: https://dba.stackexchange.com/a/326639 – Matt Aug 16 '23 at 19:33