如何在MySQL中"插入,如果不存在"?

war*_*ren 781 php mysql sql performance primary-key

我开始使用谷歌搜索,发现这篇文章讨论了互斥表.

我有一张约有1400万条记录的表格.如果我想以相同的格式添加更多数据,有没有办法确保我想要插入的记录不存在而不使用一对查询(即,一个查询要检查,一个要插入是结果集是空)?

unique对某个字段的约束是否保证insert如果它已经存在则会失败?

似乎只有一个约束,当我通过php发出插入时,脚本呱呱叫.

kni*_*ttl 756

使用 INSERT IGNORE INTO table

http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html

还有INSERT … ON DUPLICATE KEY UPDATE语法,你可以在dev.mysql.com上找到解释


根据谷歌的网络摄像头发布来自bogdan.org.ua :

2007年10月18日

首先:从最新的MySQL开始,标题中提供的语法是不可能的.但是有几种非常简单的方法可以实现使用现有功能所期望的功能.

有3种可能的解决方案:使用INSERT IGNORE,REPLACE或INSERT ... ON DUPLICATE KEY UPDATE.

想象一下,我们有一张桌子:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Run Code Online (Sandbox Code Playgroud)

现在假设我们有一个自动管道从Ensembl导入转录元数据,并且由于各种原因导致管道在任何执行步骤都可能被破坏.因此,我们需要确保两件事:1)重复执行管道不会破坏我们的数据库,2)重复执行不会因"重复主键"错误而死亡.

方法1:使用REPLACE

这很简单:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;
Run Code Online (Sandbox Code Playgroud)

如果记录存在,它将被覆盖; 如果它还不存在,它将被创建.但是,对于我们的情况,使用此方法效率不高:我们不需要覆盖现有记录,只需跳过它们就可以了.

方法2:使用INSERT IGNORE也非常简单:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;
Run Code Online (Sandbox Code Playgroud)

这里,如果'ensembl_transcript_id'已经存在于数据库中,它将被静默跳过(忽略).(更准确地说,这是MySQL参考手册的引用:"如果使用IGNORE关键字,则执行INSERT语句时发生的错误将被视为警告.例如,没有IGNORE,会复制现有UNIQUE索引的行表中的PRIMARY KEY值会导致重复键错误,并且语句将被中止.".)如果该记录尚不存在,则会创建该记录.

第二种方法有几个潜在的弱点,包括在发生任何其他问题时不中止查询(参见手册).因此,如果先前没有IGNORE关键字进行测试,则应该使用它.

还有一个选项:使用INSERT ... ON DUPLICATE KEY UPDATE语法,并且在UPDATE部分只是不做任何无意义(空)操作,比如计算0 + 0(Geoffray建议为MySQL优化做id = id赋值引擎忽略此操作).此方法的优点是它只忽略重复的键事件,并仍然中止其他错误.

最后通知:这篇文章的灵感来自Xaprb.我还建议查阅他关于编写灵活SQL查询的其他帖子.

  • 是的,请记住[REPLACE INTO删除_then_ INSERT,而不是更新](http://code.openark.org/blog/mysql/replace-into-think-twice) (29认同)
  • 只是告诉大家.使用`INSERT ... ON DUPLICATE KEY UPDATE`方法会增加任何插入失败的AUTO_INCREMENT列.可能是因为它并没有真正失败,而是UPDATE. (13认同)
  • `INSERT ... ON DUPLICATE KEY UPDATE`更好,因为它不删除行,保留任何`auto_increment`列和其他数据. (9认同)
  • 我可以将它与"延迟"结合起来加快脚本速度吗? (3认同)
  • 是的,插入延迟可能会为你加快速度.试试看 (3认同)
  • @user1147688`INSERT IGNORE` 也会发生同样的情况,这只适用于 InnoDB 引擎 (2认同)
  • 在InnoDB引擎上使用INSERT ON DUPLICATE KEY获得奇怪的增量行为.即使在失败的插入上,自动增量列也会递增.因此,如果你有Keyword1,Keyword2已经存储,然后尝试添加一个新的Keyword3,Keyword3的ID实际上会增加3,因为之前的重复发现/失败. (2认同)

小智 186

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 
Run Code Online (Sandbox Code Playgroud)

或者,外部WHERE NOT EXISTS语句可以引用LIMIT 1以便处理表最初为空的情况:

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1
Run Code Online (Sandbox Code Playgroud)

  • 如果表上没有唯一键,则此变量是合适的(`INSERT IGNORE`和`INSERT ON DUPLICATE KEY`需要唯一键约束) (28认同)
  • 如果`value1`的东西和value2的东西是相同的呢?这会抛出一个"重复的列名" (5认同)
  • 你能提供一些关于如何使用它的更多信息吗? (4认同)
  • 如果您在第2行使用"from dual"而不是"from table",那么您不需要"limit 1"子句. (2认同)
  • 我也更喜欢在子查询中使用“SELECT 1”而不是“SELECT *”。更有可能的是,这可以通过索引来满足。 (2认同)
  • 您可以使用(至少在mysql中)代替 DUAL INSERT INTO `table` (value1, value2) SELECT 'stuff for value1', 'stuff for value2' FROM (select 1) x WHERE NOT EXISTS (SELECT * FROM `table` WHERE value1='value1 的内容' AND value2='value2 的内容'); (2认同)

Zed*_*Zed 55

重复密钥更新,或插入忽略可以成为MySQL的可行解决方案.


基于mysql.com的重复密钥更新更新示例

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;
Run Code Online (Sandbox Code Playgroud)

基于mysql.com 的insert ignore示例

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Run Code Online (Sandbox Code Playgroud)

要么:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Run Code Online (Sandbox Code Playgroud)

要么:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
Run Code Online (Sandbox Code Playgroud)


KLE*_*KLE 24

如果可以接受异常,任何简单约束都应该完成.例子 :

  • 主键如果不是代理
  • 列上的唯一约束
  • 多列唯一约束

对不起,这看起来很简单.我知道您与我们分享的链接看起来很糟糕.;-(

但我永远不会给出这个答案,因为它似乎满足了你的需要.(如果没有,它可能会触发您更新您的要求,这也是"好事"(TM)).

编辑:如果插入会破坏数据库唯一约束,则会在数据库级别抛出异常,由驱动程序中继.它肯定会因为失败而停止你的脚本.PHP必须能够解决这个问题......

  • 我相信它确实如此.唯一约束将导致错误插入失败.注意:您必须在代码中处理此故障,但这是非常标准的. (2认同)
  • `INSERT IGNORE`基本上将所有错误都更改为警告,以便您的脚本不会被中断.然后,您可以使用命令"SHOW WARNINGS"查看任何警告.另一个**重要说明**:UNIQUE约束不适用于NULL值,即.row1(1,NULL)和row2(1,NULL)都将被插入(除非主键等其他约束被破坏).不幸的. (2认同)

小智 19

这是一个PHP函数,只有在表中不存在所有指定的列值时才会插入行.

  • 如果其中一列不同,则会添加该行.

  • 如果表为空,则将添加该行.

  • 如果存在所有指定列具有指定值的行,则不会添加该行.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }
    
    Run Code Online (Sandbox Code Playgroud)

用法示例:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>
Run Code Online (Sandbox Code Playgroud)

  • 如果你有大量的插入,相当昂贵. (4认同)
  • **警告:** `mysql_*` 扩展自 PHP 5.5.0 起已弃用,并已从 PHP 7.0.0 起删除。相反,[mysqli](https://www.php.net/manual/en/book.mysqli.php) 或 [PDO_MySQL](https://www.php.net/manual/en/book.pdo .php)应使用扩展名。另请参阅 [MySQL API 概述](https://www.php.net/manual/en/mysqlinfo.api.choosing.php),以获取选择 MySQL API 时的进一步帮助。 (2认同)

小智 18

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;
Run Code Online (Sandbox Code Playgroud)

如果记录存在,它将被覆盖; 如果它还不存在,它将被创建.

  • `REPLACE`可能会删除该行,然后插入而不是更新.副作用是约束可能会删除其他对象并触发删除触发器. (7认同)
  • 来自 MySQL 手册:“只有当表具有 PRIMARY KEY 或 UNIQUE 索引时,REPLACE 才有意义。否则,它就相当于 INSERT,因为没有索引可用于确定新行是否与另一行重复。” (2认同)

小智 17

请尝试以下方法:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END
Run Code Online (Sandbox Code Playgroud)

  • 如果要匹配的字段不是键,这是完美的解决方案..! (5认同)
  • _Try This_答案在StackOverflow上是低价值的,因为它们对教育OP和成千上万的未来研究人员几乎没有作用。请编辑此答案,以包括该解决方案的工作原理以及为什么是个好主意。 (4认同)

wor*_*art 5

有几个答案可以解决如何解决这个问题,如果你有一个UNIQUE可以用ON DUPLICATE KEY或检查的索引INSERT IGNORE.情况并非总是如此,并且由于UNIQUE长度约束(1000字节),您可能无法更改它.例如,我不得不使用WordPress(wp_postmeta)中的元数据.

我终于解决了两个问题:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);
Run Code Online (Sandbox Code Playgroud)

查询1是常规UPDATE查询,当有问题的数据集不存在时,该查询无效.查询2 INSERT取决于a NOT EXISTS,即INSERT仅在数据集不存在时执行.


Gil*_*ong 5

值得注意的是,无论语句是否成功,INSERT IGNORE 仍然会增加主键,就像普通的 INSERT 一样。

这会导致主键出现间隙,从而可能使程序员精神不稳定。或者,如果您的应用程序设计不佳并且依赖于完美的增量主键,那么它可能会变得令人头疼。

查看innodb_autoinc_lock_mode = 0(服务器设置,并且会带来轻微的性能影响),或者首先使用 SELECT 以确保您的查询不会失败(这也会带来性能影响和额外的代码)。