7

I am currently having problems with a primary key ID which is set to auto increment. It keeps incrementing ON DUPLICATE KEY.

For Example:

ID | field1     | field2

1  | user       | value

5  | secondUser | value

86 | thirdUser  | value

From the description above, you'll notice that I have 3 inputs in that table but due to auto increment on each update, ID has 86 for the third input.

Is there anyway to avoid this ?

Here's what my mySQL query looks like:

INSERT INTO table ( field1, field2 ) VALUES (:value1, :value2)
            ON DUPLICATE KEY
            UPDATE field1 = :value1, field2 = :value2 

And here's what my table looks like;

CREATE TABLE IF NOT EXISTS `table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `field1` varchar(200) NOT NULL,
  `field2` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `field1` (`field1`),
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
0x0
  • 363
  • 1
  • 4
  • 19
  • 1
    It should never be critical to have sequential ids, only unique.... if you need sequential, then there's something wrong with your design – Mark Baker Jul 13 '16 at 09:03
  • @MarkBaker I have edited post to include my table design structure – 0x0 Jul 13 '16 at 09:22
  • 1
    Make another column on your table where you can manually add an integer and then you can keep that column sequential, such as an `sID` column and then you can find the MAX value and then +1 to that value for your latest insert. (you can probably automatically do this in the SQL query) – Martin Jul 13 '16 at 09:29
  • @youngbobby if one of the answers helped you, please select it (and upvote if you wish). It takes time to research, test and write up these answers. Not very cool to ask & run. – BeetleJuice Jul 23 '16 at 02:05

5 Answers5

8

You could set the innodb_autoinc_lock_mode config option to "0" for "traditional" auto-increment lock mode, which guarantees that all INSERT statements will assign consecutive values for AUTO_INCREMENT columns.

That said, you shouldn't depend on the auto-increment IDs being consecutive in your application. Their purpose is to provide unique identifiers.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
4

This behavior is easily seen below with the default setting for innodb_autoinc_lock_mode = 1 (“consecutive” lock mode). Please also reference the fine manual page entitled AUTO_INCREMENT Handling in InnoDB. Changing this value will lower concurrency and performance with the setting = 0 for “Tranditional” lock mode as it uses a table-level AUTO-INC lock.

That said, the below is with the default setting = 1.

I am about to show you four examples of how easy it is to create gaps.

Example 1:

create table x
(   id int auto_increment primary key,
    someOtherUniqueKey varchar(50) not null,
    touched int not null,
    unique key(someOtherUniqueKey)
);
insert x(touched,someOtherUniqueKey) values (1,'dog') on duplicate key update touched=touched+1;
insert x(touched,someOtherUniqueKey) values (1,'dog') on duplicate key update touched=touched+1;
insert x(touched,someOtherUniqueKey) values (1,'cat') on duplicate key update touched=touched+1;

select * from x; 
+----+--------------------+---------+
| id | someOtherUniqueKey | touched |
+----+--------------------+---------+
|  1 | dog                |       2 |
|  3 | cat                |       1 |
+----+--------------------+---------+

The Gap (id=2 is skipped) is due to one of a handful of operations and quirks and nervous twitches of the INNODB engine. In its default high performance mode of concurrency, it performs range gap allocations for various queries sent to it. One had better have good reasons to change this setting, because doing so impacts performance. The sorts of things later versions of MySQL delivers to you, and you turn off due to Hyper Focusing on gaps in printout sheets (and bosses that say "Why do we have gaps").

In the case of an Insert on Duplicate Key Update (IODKU), it is assuming 1 new row and allocates a slot for it. Remember, concurrency, and your peers doing the same operations, perhaps hundreds concurrently. When the IODKU turns into an Update, well, there goes the use of that abandoned and never inserted row with id=2 for your connection and anyone else.

Example 2:

The same happens during Insert ... Select From as seen in This Answer of mine. In it I purposely use MyISAM due to reporting on counts, min, max, otherwise the range gap quirk would allocate and not fill all. And the numbers would look weird as that answer dealt with actual numbers. So the older engine (MyISAM) worked fine for tight non-gaps. Note that in that answer I was trying to do something fast and safe and that table could be converted to INNODB with ALTER TABLE after the fact. Had I done that example in INNODB to begin with, there would have been plenty of gaps (in the default mode). The reason the Insert ... Select From would have creates gaps in that Answer had I used INNODB was due to the uncertainty of the count, the mechanism that the engine chooses for safe (uncertain) range allocations. The INNODB engine knows the operation naturally, knows in has to create a safe pool of AUTO_INCREMENT id's, has concurrency (other users to think about), and gaps flourish. It's a fact. Try example 2 with the INNODB engine and see what you come up with for min, max, and count. Max won't equal count.

Examples 3 and 4:

There are various situations that cause INNODB Gaps documented on the Percona website as they stumble into more and document them. For instance, it occurs during failed inserts due to Foreign Key constraints seen in this 1452 Error image. Or a Primary Key error in this 1062 Error image.

Remember that the INNODB Gaps are there as a side-effect of system performance and a safe engine. Is that something one really wants to turn-off (Performance, Higher user statisfaction, higher concurrency, lack of table locks), for the sake of tighter id ranges? Ranges that have holes on deletes anyway. I would suggest not for my implementations, and the default with Performance is just fine.

Community
  • 1
  • 1
Drew
  • 24,851
  • 10
  • 43
  • 78
-2

I am currently having problems with a primary key ID which is set to auto increment. It keeps incrementing ON DUPLICATE KEY

One of us must be misunderstanding the problem, or you're misrepresenting it. ON DUPLICATE KEY UPDATE never creates a new row, so it cannot be incrementing. From the docs:

If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that would cause a duplicate value in a UNIQUE index or PRIMARY KEY, MySQL performs an UPDATE of the old row.

Now it's probably the case that auto-increment occurs when you insert and no duplicate key is found. If I assume that this is what's happening, my question would be: why is that a problem?

If you absolutely want to control the value of your primary key, change your table structure to remove the auto-increment flag, but keep it a required, non-null field. It will force you to provide the keys yourself, but I would bet that this will become a bigger headache for you.

I really am curious though: why do you need to plug all the holes in the ID values?

BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • 1
    You can't plug the holes in a real system. Because at some point, you delete a row. And you can't re-shift down all the PK's and FK's in all the child tables (or at least one of the id's as a hot replacement into the new hole). Cannot means cannot while still staying sane. So people without OCD just leave well enough alone. – Drew Jul 13 '16 at 22:51
-2

I answered it here: to solve the auto-incrementing problem use the following code before insert/on duplicate update part and execute them all together:

    SET @NEW_AI = (SELECT MAX(`the_id`)+1 FROM `table_blah`);
    SET @ALTER_SQL = CONCAT('ALTER TABLE `table_blah` AUTO_INCREMENT =', @NEW_AI);
    PREPARE NEWSQL FROM @ALTER_SQL;
    EXECUTE NEWSQL; 

together and in one statement it should be something like below:

    SET @NEW_AI = (SELECT MAX(`the_id`)+1 FROM `table_blah`);
    SET @ALTER_SQL = CONCAT('ALTER TABLE `table_blah` AUTO_INCREMENT =', @NEW_AI);
    PREPARE NEWSQL FROM @ALTER_SQL;
    EXECUTE NEWSQL; 
    INSERT INTO `table_blah` (`the_col`) VALUES("the_value")
    ON DUPLICATE KEY UPDATE `the_col` = "the_value";
-2

You can change your query from

INSERT INTO table ( f1, f2 ) VALUES (:v1, :v2) ON DUPLICATE KEY UPDATE f1 = :v1, f2 = :v2

to

insert ignore into table select (select max(id)+1 from table), :v1, :v2 ;

This will try

  • insert new data with last unused id (not autoincrement)
  • if in unique fields duplicate entry found ignore it
  • else insert new data normally

    ( but this method not support to update fields if duplicate entry found )
a55
  • 376
  • 3
  • 13