处理 select + 插入以避免重复错误的正确方法

correct way to handle select + insert to avoid duplicates errors

你好,我有一个简单的 MySQL InnoDB table,只有两个字段:

我正在从各种来源并行导入一些数据,我需要确保数据在插入时不会重复,所以我正在执行以下操作:

SELECT `id` FROM `table` WHERE `name` = <name>;
 if `id` <= 0
  INSERT INTO `table` SET `name` = "<name>";
  return AUTO_INCREMENT
 else return `id`

这在 99.9999% 的情况下都有效,但它可能会发生(并且发生在我身上)两个或多个不同的脚本正在插入相同的数据,因为两个 SELECT 返回 id <=0所以两者都执行了 INSERT,其中之一引发了错误。

我有两个可能的解决方案,但我不确定哪个最有效。

还有一条信息:最初导入不会在 table 中找到元素,但随着插入的元素越来越多,找到的可能性会增加。最终的table,经过粗略的计算,大约有7-1000万条记录:

SELECT `id` FROM `table` WHERE `name` = <name>;
if `id` <= 0
 INSERT IGNORE INTO `table` SET `name` = "<name>";
 get AUTO_INCREMENT
  if AUTO_INCREMENT <=0
   SELECT `id` FROM `table` WHERE `name` = <name>;
   return `id`
  else return AUTO_INCREMENT
else return `id`

INSERT IGNORE INTO `table` SET `name` = "<name>";
get AUTO_INCREMENT
 if AUTO_INCREMENT <=0
  SELECT `id` FROM `table` WHERE `name` = <name>;
  return `id`
 else return AUTO_INCREMENT

您遇到了竞争条件。当您的代码检测到需要新的插入时,您的两个客户就会竞相成为第一个插入该值的人。这是赢家通吃。您需要编写代码来避免这种竞争情况。幸运的是,SQL 是专门设计的,因此可以做到这一点。

您在这里有几个选择,都针对 MySQL 的 SQL 方言。

一种是使用内置函数LAST_INSERT_ID()。它服务于我相信您所说的 get AUTO_INCREMENT 的目的。

另一种是使用INSERT ... ON DUPLICATE KEY UPDATE.

看起来你的逻辑是为了做两件事:

  1. 确保 name 值在 table 中,如果还没有,则将其放在那里。
  2. return 与名称值关联的 id 值。

你可以这样做。

INSERT IGNORE INTO `table` (name) VALUES (<name>);
SELECT id FROM `table` WHERE name = <name>;

请注意,INSERT IGNORE 操作不会被访问数据库的不同程序之间的竞争条件捕获,因为它是单个 SQL 语句。

您可以使用 LAST_INSERT_ID() 优化它。

INSERT IGNORE INTO `table` (name) VALUES (<name>);
if (LAST_INSERT_ID()=0) then do the select.