防止 InnoDB 在 DUPLICATE KEY 上自动递增

Prevent InnoDB auto increment ON DUPLICATE KEY

我目前遇到主键 ID 设置为 auto increment 的问题。它不断递增 ON DUPLICATE KEY.

例如:

ID | field1     | field2

1  | user       | value

5  | secondUser | value

86 | thirdUser  | value

从上面的描述中,您会注意到我在其中有 3 个输入 table,但由于每次更新时自动递增,第三个输入的 ID 为 86。

有没有办法避免这种情况?

下面是我的 mySQL 查询:

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

这是我的 table 的样子;

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 ;

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

我们中的一个人一定是误解了这个问题,或者你在歪曲它。 ON DUPLICATE KEY UPDATE 从不创建新行,因此它不能递增。来自 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.

现在可能是插入时自增,没有发现重复键。如果我假设这就是正在发生的事情,我的问题是:为什么这是个问题?

如果您绝对想控制主键的值,请更改您的 table 结构以删除 auto-increment 标志,但将其保留为必需的非空字段。它会迫使你自己提供密钥,但我敢打赌这会让你更头疼。

不过我真的很好奇:为什么你需要填补 ID 值中的所有漏洞?

您可以将 innodb_autoinc_lock_mode 配置选项设置为 "0" 用于 "traditional" 自动增量锁定模式,这保证所有 INSERT 语句将为 AUTO_INCREMENT 列。

也就是说,您不应该依赖应用程序中连续的自动递增 ID。他们的目的是提供唯一的标识符。

此行为在下面使用 innodb_autoinc_lock_mode = 1 (“consecutive” lock mode). Please also reference the fine manual page entitled AUTO_INCREMENT Handling in InnoDB 的默认设置很容易看出。更改此值将降低并发性和性能,因为“传统”锁定模式设置为 0,因为它使用 table 级别的 AUTO-INC 锁。

也就是说,下面是默认设置 = 1。

我将向您展示 四个 个示例,说明创建间隙是多么容易。

示例 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 |
+----+--------------------+---------+

Gap(id=2 被跳过)是由于 INNODB 引擎的少数操作和怪癖以及神经抽搐之一。在其默认的高性能并发模式下,它为发送给它的各种查询执行范围间隙分配。最好有充分的理由来更改此设置,因为这样做会影响性能。 MySQL 的更高版本提供给你的东西,由于 Hyper Focusing 在打印输出表中的空白(以及说 "Why do we have gaps" 的老板),你关闭了。

在插入重复键更新 (IODKU) 的情况下,它假设有 1 个新行并为其分配一个插槽。请记住,并发性,以及您的同行执行相同的操作,可能并发数百个。当 IODKU 变成 Update 时,好吧,您的连接和其他任何人都将使用 id=2 的已放弃且从未插入的行。

示例 2:

在我的 中,Insert ... Select From 期间也会发生同样的情况。由于报告计数、最小值、最大值,我特意在其中使用 MyISAM,否则范围间隙怪癖将分配而不是全部填满。这些数字看起来很奇怪,因为该答案处理的是实际数字。因此,较旧的引擎 (MyISAM) 可以很好地实现紧密的无间隙。请注意,在那个答案中,我试图做一些快速和安全的事情,并且 table 可以在事后用 ALTER TABLE 转换为 INNODB 。如果我一开始就在 INNODB 中完成了那个例子,就会有很多空白(在默认模式下)。如果我使用 INNODBInsert ... Select From 在答案中产生空白的原因是由于计数的不确定性,引擎选择的安全机制(不确定的)范围分配。 INNODB 引擎自然知道该操作,知道必须创建一个 AUTO_INCREMENT id 的安全池,具有并发性(其他用户要考虑),并且差距蓬勃发展。这是事实。使用 INNODB 引擎尝试示例 2,看看您对 min、max 和 count 想出了什么。最大值不等于计数。

示例 3 和 4:

有多种情况导致 INNODB Percona 网站上记录了差距,因为他们偶然发现了更多差距并记录下来。例如,由于在此 1452 Error image. Or a Primary Key error in this 1062 Error image.

中看到的外键约束,它发生在插入失败期间

请记住,INNODB 差距是系统性能和安全引擎的副作用。为了更严格的 id 范围,人们真的想关闭它(性能、更高的用户统计、更高的并发性、缺少 table 锁)吗?无论如何,在删除上有漏洞的范围。我建议不要用于我的实现,默认的 Performance 就很好。

我回答了here: 要解决自动递增问题,请在 insert/on 复制更新部分之前使用以下代码并一起执行它们:

    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; 

一起并在一个声明中应该如下所示:

    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";

您可以从

更改您的查询
INSERT INTO table ( f1, f2 ) VALUES (:v1, :v2) ON DUPLICATE KEY UPDATE f1 = :v1, f2 = :v2

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

这将尝试

  • 使用上次未使用的 ID 插入新数据(不是自动递增)
  • 如果在唯一字段中发现重复条目​​,请忽略它
  • 否则正常插入新数据

    (但此方法不支持在发现重复条目​​时更新字段)