避免插入失败以避免虚假的自动增量
Posted
技术标签:
【中文标题】避免插入失败以避免虚假的自动增量【英文标题】:Avoiding failed inserts to avoid spurious autoincrement 【发布时间】:2012-05-22 22:07:39 【问题描述】:我的问题与Why does mysql autoincrement increase on failed inserts? 相同,但与其增加我的id
字段,我宁愿只重写给我带来麻烦的INSERT
查询。假设我有一个包含两个字段的数据库,id
和username
,其中id
是主键,username
是唯一键。我本质上是在寻找可以做到INSERT...IF NOT EXISTS
的语法。现在,我愿意
INSERT INTO `table`(`username`)
VALUES ('foo')
ON DUPLICATE KEY UPDATE `id`=`id`
我只有一个线程写入数据库,所以我不需要任何并发保护。有什么建议吗?
【问题讨论】:
为什么自动增量字段更新会出现这样的问题? 因为我想避免使用HUGEINT
作为密钥。
【参考方案1】:
你应该使用这个:
INSERT INTO tableX (username)
SELECT 'foo' AS username
FROM dual
WHERE NOT EXISTS
( SELECT *
FROM tableX
WHERE username = 'foo'
) ;
如果您想包含更多列的值:
INSERT INTO tableX (username, dateColumn)
SELECT 'foo' --- the aliases are not needed
, NOW() --- actually
FROM dual
WHERE NOT EXISTS
( SELECT *
FROM tableX
WHERE username = 'foo'
) ;
【讨论】:
FROM dual
是什么? 编辑忽略我。在手册中找到它。很好的答案 +1
dual
是一个只有一行的虚拟表(最初在 Oracle DBMS 中,它有 2 行,因此得名)。可用于构造 1 行表。
当手册说“你不能插入一个表并在子查询中从同一个表中选择”时,为什么这会起作用?是因为只有在没有选择任何内容的情况下才会插入?还是EXISTS
避免实际选择任何东西?
@eggyal:它在哪里说的? (实际上我在这里选择“双重”,而不是从同一张表中选择,但这是另一个问题)。
第三个子弹 - dev.mysql.com/doc/en/insert-select.html; EXISTS
子查询不会锁定表并阻止写入吗?【参考方案2】:
我认为您不能阻止计数器增加,原因在您所链接的问题的答案中给出。
您有三个选择:
使用跳过的标识符;你真的希望用完 64 位吗?
在尝试 INSERT
之前检查现有记录是否存在:
DELIMITER ;;
IF NOT EXISTS (SELECT * FROM `table` WHERE username = 'foo') THEN
INSERT INTO `table` (username) VALUES ('foo');
END IF;;
DELIMITER ;
或者,更好的是,使用@ypercube 建议的FROM dual WHERE NOT EXISTS ...
表单。
每次插入后重置计数器。如果在存储过程中执行 INSERT
操作,您可以使用重复键错误处理程序来执行此操作:
DELIMITER ;;
CREATE PROCEDURE TEST(IN uname CHAR(16) CHARSET latin1) BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' BEGIN
-- WARNING: THIS IS NOT SAFE FOR CONCURRENT CONNECTIONS
SET @qry = CONCAT(
'ALTER TABLE `table` AUTO_INCREMENT = ',
(SELECT MAX(id) FROM `table`)
);
PREPARE stmt FROM @qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @qry = NULL;
END;
INSERT INTO `table` (username) VALUES (uname);
END;;
DELIMITER ;
考虑到@ypercube 在下面的 cmets 中提出的关于此策略的(有效)担忧,您可以改用:
SELECT AUTO_INCREMENT - 1
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema = 'db_name' AND table_name = 'table';
【讨论】:
拒绝 4,永远不要使用选项 4。这不安全。 我尝试在存储过程中添加您的“ALTER TABLE”,但 MySQL 对我大喊大叫。这里有什么问题?CREATE DEFINER=`me`@`localhost` PROCEDURE `TEST`(IN `uname` CHAR(16) CHARSET latin1) NOT DETERMINISTIC MODIFIES SQL DATA SQL SECURITY DEFINER INSERT INTO `test` ( `username` ) VALUES ( uname ) ON DUPLICATE KEY (ALTER TABLE `table` AUTO_INCREMENT = (SELECT MAX(id) FROM `table`));
@ypercube:为什么不安全,因为 OP 声明他“只有一个线程写入数据库,所以我不需要任何形式的并发保护 “?
@AndyShulman:你只能ON DUPLICATE UPDATE ...
;尝试以这种方式ALTER TABLE
是语法错误。如图所示,您必须使用单独的语句。
我想我误解了您关于“重复键错误处理程序”的评论。你能详细说明一下吗?【参考方案3】:
您可以在插入失败后简单地重置自动增量字段
ALTER TABLE 表名 AUTO_INCREMENT =1;
这不会将自动增量重置为 1,但会将其重置为当前最大值加一
还请注意,您连接到数据库的用户应该对目标表具有更改权限
【讨论】:
【参考方案4】:您还可以在插入该用户之前检查该用户之前是否已插入,这将需要对您的数据库进行额外调用,但如果该用户已经存在,您可以返回您找到的信息并避免虚假插入。
【讨论】:
【参考方案5】:使用INSERT IGNORE INTO table
。查看下面的帖子
http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html
你可以这样做:
INSERT INTO `table`(`username`)
VALUES ('foo')
ON DUPLICATE KEY UPDATE `username`= 'foo'
或
INSERT IGNORE INTO `table`
SET `username` = 'foo'
【讨论】:
没有。即使没有插入任何行,这仍然会自动递增id
。
对不起,我不这么认为。如果存在重复键,则会跳过它;如果不是,那么将插入一个新行,并且 id 将被递增。你在寻找不同的行为吗?
确实如此。我测试了它。我跑了INSERT IGNORE INTO `test`(`username`) VALUES ('testuser')
5 次,然后检查下一个自增值是多少。
您的意思是,即使它忽略并且不插入,但 id 仍然会自动递增?这很奇怪......虽然没有测试过。建议您也将相同的问题发布到 mysql 论坛。
是的。插入 id
为 1,但成功插入不同的用户名后,下一个 id
为 6。以上是关于避免插入失败以避免虚假的自动增量的主要内容,如果未能解决你的问题,请参考以下文章
ruby 保留用户名列表,以避免与资源路径发生虚假URL冲突
如果一条记录在 oracle 中插入失败,则避免 jdbc 批量插入失败
如何避免将表单发送的数据关联到具有自动增量唯一 ID 的 MySQL 数据库中的可能冲突?
如何避免 TypeScript 中出现虚假的“未使用参数”警告