【问题标题】: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 作为密钥。

标签: mysql sql innodb auto-increment


【解决方案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】:

我认为您不能阻止计数器增加,原因在您所链接的问题的答案中给出。

您有三个选择:

  1. 使用跳过的标识符;你真的希望用完 64 位吗?

  2. 在尝试 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 ... 表单。

  3. 每次插入后重置计数器。如果在存储过程中执行 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。
      猜你喜欢
      • 1970-01-01
      • 2015-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 2013-01-09
      • 1970-01-01
      相关资源
      最近更新 更多