处理 select + 插入以避免重复错误的正确方法
correct way to handle select + insert to avoid duplicates errors
你好,我有一个简单的 MySQL InnoDB table,只有两个字段:
- id - 自增主索引
- 名称 - 唯一索引
我正在从各种来源并行导入一些数据,我需要确保数据在插入时不会重复,所以我正在执行以下操作:
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
.
看起来你的逻辑是为了做两件事:
- 确保
name
值在 table 中,如果还没有,则将其放在那里。
- 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.
你好,我有一个简单的 MySQL InnoDB table,只有两个字段:
- id - 自增主索引
- 名称 - 唯一索引
我正在从各种来源并行导入一些数据,我需要确保数据在插入时不会重复,所以我正在执行以下操作:
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
.
看起来你的逻辑是为了做两件事:
- 确保
name
值在 table 中,如果还没有,则将其放在那里。 - 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.