如果不能忽略MySQL“重复密钥更新时插入…”导致的“自动增量”间隙,该怎么办?

如果不能忽略MySQL“重复密钥更新时插入…”导致的“自动增量”间隙,该怎么办?,mysql,insert-update,Mysql,Insert Update,在MySQL中的InnoDB上执行重复密钥更新时,INSERT…,我们经常被告知忽略auto_increment列中的潜在间隙。如果这种差距很可能存在,而且不能被忽视,那该怎么办 例如,假设有一个表rating,用于存储用户对项目的评分。桌子方案类似于 CREATE TABLE rating ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, item_id INT NOT NULL, rating INT NOT

在MySQL中的InnoDB上执行重复密钥更新时,
INSERT…
,我们经常被告知忽略
auto_increment
列中的潜在间隙。如果这种差距很可能存在,而且不能被忽视,那该怎么办

例如,假设有一个表
rating
,用于存储用户对项目的评分。桌子方案类似于

CREATE TABLE rating (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  item_id INT NOT NULL,
  rating INT NOT NULL,
  UNIQUE KEY tuple (user_id, item_id),
  FOREIGN KEY (user_id) REFERENCES user(id),
  FOREIGN KEY (item_id) REFERENCES item(id)
);
可能有许多用户和许多项目,而用户可能会经常更改他们以前已经评分的项目的评分。每次更改评级时,如果我们在重复键更新时使用
INSERT…
,就会产生一个间隙,否则我们将不得不查询两次(首先执行
选择
),这会影响性能,或者检查
受影响的行
,这不能容纳多个记录
INSERT

对于某些系统,每个10万用户对10个项目进行评分,并且每天更改一半的评分,那么
自动增量
id将在两年内用完。那么,在实践中,我们应该做些什么来预防它呢?

完整答案

没关系!只需使用更大的id字段,例如BIGINT。不要试图重复使用间隙。这是个坏主意。在这种情况下,不要考虑性能或优化。这是浪费时间

另一种解决方案是将复合密钥作为主密钥。在您的情况下,您可以删除id字段,并使用pair(user\u id,item\u id)作为主键

在“评级”的情况下,最常见的查询是“按用户id删除”和插入。因此,您并不真正需要此“id”主键来实现功能。但您始终需要在表中显示任何主键

此方法的唯一缺点是,现在当您只想从表中删除一行时,您需要使用查询,例如:

DELETE FROM rating WHERE user_id = 123 AND item_id=1234
而不是旧的

DELETE FROM rating WHERE id = 123

但在这种情况下,在应用程序中更改一行代码并不困难。此外,在大多数情况下,人们并不需要这样的功能。

我们在一个大表中工作,在某些表中有1亿条记录的表。我们反复使用
INSERT IGNORE
INSERT。。在重复键上
。将列设置为
unsigned bigint
将避免id问题

但我建议你们也考虑一下长期的解决方案。有一些已知的事实

  • 选择
    插入
    /
    更新
    通常比在重复键上插入要快,这同样取决于您的数据大小和其他因素
  • 如果您有两个唯一键(或一个主键和一个唯一键),那么您的查询可能并不总是可预测的。如果使用基于语句的复制,则会出现复制错误
  • ID不是大型表的唯一问题。如果您有一个记录超过3亿条的表,性能会急剧下降。您需要尽快考虑对数据库/表进行分区/集群/分片

我个人建议不要使用
INSERT。。在重复键上
。如果您计划使用高度可扩展的服务,请广泛阅读其使用情况和性能影响

使用
BIGINT
而不是
INT
。它会持续你几个世纪。没关系!只需使用更大的id字段。不要试图重复使用间隙。这是个坏主意。@HoboSapiens,但这也会损害性能。。。我不知道,也许我太偏执了?这不会影响你的表现。此外,如果您打算在2年内耗尽常规的4字节整数,那么除了使用bigint之外,您别无选择。另一种选择是使用guid和各种愚蠢的组合键,这肯定会损害您的性能。这听起来可能是一个愚蠢的问题,但当用户更改评级时,您为什么要创建不同的条目?它是历史性的吗?为什么不更新用户对某个项目的评分呢?@peter,正如我所说的那样。假设列c1和c2是唯一的。让这一排排排成一排。在你通过的插入站中没有。第一行或第二行应该更新。MySQL只更新它得到的第一行,在主服务器和从服务器中可能不一样。互联网上有很多博客和问答,请阅读更多关于sameThank you的文章,您关于复合主键的建议看起来是一个很好的解决方案。我尝试使用PostgreSQL 9.5来避免这些差距,但同样的现象也发生在PostgreSQL上。