如果不能忽略由 MySQL `INSERT...ON DUPLICATE KEY UPDATE` 造成的 `auto_increment` 间隙怎么办?
What if `auto_increment` gaps caused by MySQL `INSERT...ON DUPLICATE KEY UPDATE` cannot be ignored?
在 MySQL 中对 InnoDB 执行 INSERT...ON DUPLICATE KEY UPDATE
时,我们经常被告知要忽略 auto_increment
列中的潜在间隙。如果这种差距很可能存在且不容忽视怎么办?
举个例子,假设有一个 table rating
存储用户对项目的评分。 table 方案类似于
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...ON DUPLICATE KEY UPDATE
,就会产生一个间隙,否则我们将不得不查询两次(先执行 SELECT
),这会损害性能或检查 affected rows
,这不会容纳多个记录 INSERT
.
对于某些系统,其中 100K 用户每人对 10 个项目进行评分并且每天更改一半的评分,auto_increment
id 将在两年内用完。那在实践中应该如何预防呢?
完整答案。
差距没关系!只需使用更大的 id 字段,例如 BIGINT。不要试图重用间隙。这是一个坏主意。在这种情况下不要考虑性能或优化。太浪费时间了。
另一种解决方案是将组合键设为主键。在您的情况下,您可以删除 id 字段,并使用 pair (user_id, item_id) 作为主键。
在 "rating" 的情况下,最频繁的查询是 "delete by user_id" 和插入。所以你并不是真的需要这个 "id" 主键来实现功能。但是您总是需要在 table.
中显示任何主键
此方法的唯一缺点是,现在当您只想从 table 中删除一行时,您需要使用如下查询:
DELETE FROM rating WHERE user_id = 123 AND item_id=1234
而不是旧的
DELETE FROM rating WHERE id = 123
但在这种情况下,更改应用程序中的一行代码并不难。此外,在大多数情况下,人们并不需要这样的功能。
我们在大型 table 中工作,我们有 tables 在一些 table 中有数亿条记录。我们反复使用 INSERT IGNORE
或 INSERT.. ON DUPLICATE KEY
。将列设置为 unsigned bigint
将避免 id 问题。
但我建议您也考虑一下长期解决方案。有一些已知的事实。
SELECT
和 INSERT
/UPDATE
通常比 INSERT..ON DUPLICATE KEY
快,同样取决于您的数据大小和其他因素
- 如果您有两个唯一键(或一个主键和一个唯一键),您的查询可能并不总是 predictable。如果您使用基于语句的复制,它会给出复制错误。
- ID 并不是大 table 的唯一问题。如果您 table 的记录超过 300M,性能会急剧下降。你需要尽快考虑 partitioning/clustering/sharding 你的 database/tables
我个人建议不要使用 INSERT.. ON DUPLICATE KEY
。如果您计划高度可扩展的服务,请广泛阅读其使用和性能影响
在 MySQL 中对 InnoDB 执行 INSERT...ON DUPLICATE KEY UPDATE
时,我们经常被告知要忽略 auto_increment
列中的潜在间隙。如果这种差距很可能存在且不容忽视怎么办?
举个例子,假设有一个 table rating
存储用户对项目的评分。 table 方案类似于
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...ON DUPLICATE KEY UPDATE
,就会产生一个间隙,否则我们将不得不查询两次(先执行 SELECT
),这会损害性能或检查 affected rows
,这不会容纳多个记录 INSERT
.
对于某些系统,其中 100K 用户每人对 10 个项目进行评分并且每天更改一半的评分,auto_increment
id 将在两年内用完。那在实践中应该如何预防呢?
完整答案。
差距没关系!只需使用更大的 id 字段,例如 BIGINT。不要试图重用间隙。这是一个坏主意。在这种情况下不要考虑性能或优化。太浪费时间了。
另一种解决方案是将组合键设为主键。在您的情况下,您可以删除 id 字段,并使用 pair (user_id, item_id) 作为主键。
在 "rating" 的情况下,最频繁的查询是 "delete by user_id" 和插入。所以你并不是真的需要这个 "id" 主键来实现功能。但是您总是需要在 table.
中显示任何主键此方法的唯一缺点是,现在当您只想从 table 中删除一行时,您需要使用如下查询:
DELETE FROM rating WHERE user_id = 123 AND item_id=1234
而不是旧的
DELETE FROM rating WHERE id = 123
但在这种情况下,更改应用程序中的一行代码并不难。此外,在大多数情况下,人们并不需要这样的功能。
我们在大型 table 中工作,我们有 tables 在一些 table 中有数亿条记录。我们反复使用 INSERT IGNORE
或 INSERT.. ON DUPLICATE KEY
。将列设置为 unsigned bigint
将避免 id 问题。
但我建议您也考虑一下长期解决方案。有一些已知的事实。
SELECT
和INSERT
/UPDATE
通常比INSERT..ON DUPLICATE KEY
快,同样取决于您的数据大小和其他因素- 如果您有两个唯一键(或一个主键和一个唯一键),您的查询可能并不总是 predictable。如果您使用基于语句的复制,它会给出复制错误。
- ID 并不是大 table 的唯一问题。如果您 table 的记录超过 300M,性能会急剧下降。你需要尽快考虑 partitioning/clustering/sharding 你的 database/tables
我个人建议不要使用 INSERT.. ON DUPLICATE KEY
。如果您计划高度可扩展的服务,请广泛阅读其使用和性能影响