避免插入失败以避免虚假的自动增量

Posted

技术标签:

【中文标题】避免插入失败以避免虚假的自动增量【英文标题】:Avoiding failed inserts to avoid spurious autoincrement 【发布时间】:2012-05-22 22:07:39 【问题描述】:

我的问题与Why does mysql autoincrement increase on failed inserts? 相同,但与其增加我的id 字段,我宁愿只重写给我带来麻烦的INSERT 查询。假设我有一个包含两个字段的数据库,idusername,其中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 中出现虚假的“未使用参数”警告

如何避免 BigQuery 中的 Power BI 增量刷新重复查询?

有啥方法可以避免 C++ 中的“调试断言失败”窗口?