«INSERT IGNORE» против «INSERT… ON DUPLICATE KEY UPDATE»

833

При выполнении INSERTоператора с несколькими строками я хочу пропустить дублирующиеся записи, которые в противном случае могли бы вызвать сбой. После некоторых исследований моими вариантами, по-видимому, являются:

  • ON DUPLICATE KEY UPDATE что подразумевает ненужное обновление за определенную плату, или
  • INSERT IGNORE что подразумевает приглашение для других видов неспособности проскользнуть без предупреждения.

Прав ли я в этих предположениях? Какой лучший способ просто пропустить строки, которые могут вызвать дублирование, и просто перейти к другим строкам?

Томас Дж Генри
источник

Ответы:

991

Я бы порекомендовал использовать INSERT...ON DUPLICATE KEY UPDATE.

Если вы используете INSERT IGNORE, то строка фактически не будет вставлена, если она приведет к дублированию ключа. Но утверждение не приведет к ошибке. Вместо этого он генерирует предупреждение. Эти случаи включают в себя:

  • Вставка дубликата ключа в столбцы с PRIMARY KEYили UNIQUEограничениями.
  • Вставка NULL в столбец с NOT NULLограничением.
  • Вставка строки в многораздельную таблицу, но вставляемые значения не отображаются на раздел.

Если вы используете REPLACE, MySQL фактически DELETEследует за INSERTвнутренним, что имеет некоторые неожиданные побочные эффекты:

  • Новый идентификатор автоинкремента назначен.
  • Зависимые строки с внешними ключами могут быть удалены (если вы используете каскадные внешние ключи), либо можете запретить REPLACE.
  • Триггеры, которые запускаются, DELETEвыполняются без необходимости.
  • Побочные эффекты распространяются и на реплики.

исправление: оба REPLACEи INSERT...ON DUPLICATE KEY UPDATEявляются нестандартными, проприетарными изобретениями, специфичными для MySQL. ANSI SQL 2003 определяет MERGEоператор, который может удовлетворить ту же потребность (и более), но MySQL не поддерживает MERGEоператор.


Пользователь попытался отредактировать это сообщение (редактирование было отклонено). При редактировании была предпринята попытка добавить утверждение, которое INSERT...ON DUPLICATE KEY UPDATEприводит к назначению нового идентификатора автоинкремента. Это правда, что новый идентификатор генерируется , но он не используется в измененной строке.

См. Демонстрацию ниже, протестированную с Percona Server 5.5.28. Переменная конфигурации innodb_autoinc_lock_mode=1(по умолчанию):

mysql> create table foo (id serial primary key, u int, unique key (u));
mysql> insert into foo (u) values (10);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   10 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1

mysql> insert into foo (u) values (10) on duplicate key update u = 20;
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1

Выше показано, что оператор IODKU обнаруживает дубликат и вызывает обновление, чтобы изменить значение u. Обратите внимание, что AUTO_INCREMENT=3указывает, что идентификатор был создан, но не используется в строке.

Принимая во внимание, REPLACEчто удаляет исходную строку и вставляет новую строку, генерируя и сохраняя новый идентификатор автоинкремента:

mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+
Билл Карвин
источник
3
Интересно, планирует ли команда разработчиков mysql когда-либо использовать MERGE из ANSI SQL 2003?
Лонни Бест
1
@LonnieBest: Запрос функции для реализации MERGE был сделан в 2005 году, но, насколько я знаю, нет никакого прогресса или плана. bugs.mysql.com/bug.php?id=9018
Билл Карвин
2
О, я могу добавить, что он генерирует предупреждения (не ошибки) для неверного несоответствия типов, но он не генерирует предупреждение для дублированного составного первичного ключа.
Фабрисио Мате
11
Я только что посмотрел на стол, который был заполнен множеством INSERT ... ON DUPLICATE KEY UPDATE ...утверждений. Большая часть данных является дубликатом, и это привело к тому, что один случай ИИ ПК увеличился с 17 029 941 до 46 461 740 между двумя строками. Это поколение нового ИИ каждый раз означает, что ваш диапазон может быть очень быстро заполнен, и вам нужно вычистить. Этому столу всего две недели!
Engineer81
4
@AntTheKnee, ааа, проблемы работы во времена больших данных.
Билл Карвин
174

В случае, если вы хотите увидеть, что все это значит, вот пошагово все:

CREATE TABLE `users_partners` (
  `uid` int(11) NOT NULL DEFAULT '0',
  `pid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`,`pid`),
  KEY `partner_user` (`pid`,`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Первичный ключ основан на обоих столбцах этой краткой справочной таблицы. Первичный ключ требует уникальных значений.

Давайте начнем:

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...1 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1);
...0 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1) ON DUPLICATE KEY UPDATE uid=uid
...0 row(s) affected

обратите внимание, что выше сэкономлено слишком много дополнительной работы, установив столбец, равный самому себе, обновление не требуется

REPLACE INTO users_partners (uid,pid) VALUES (1,1)
...2 row(s) affected

а теперь несколько тестов строк:

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...3 row(s) affected

в консоли не было сгенерировано никаких других сообщений, и теперь у них есть эти 4 значения в данных таблицы. Я удалил все, кроме (1,1), чтобы я мог тестировать с того же игрового поля

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4) ON DUPLICATE KEY UPDATE uid=uid
...3 row(s) affected

REPLACE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...5 row(s) affected

Так что у вас есть это. Поскольку все это было выполнено на свежем столе почти без данных и не в производстве, время выполнения было микроскопическим и не имело значения. Любой, у кого есть реальные данные, будет рад предоставить их.

Паулюс Максимус
источник
Я запустил оба на дубликате ключа и заменил на. Мои таблицы закончились ~ 120К строк, причем около 30% строк были дубликатами. На дубликате ключа пробежал 102 секунды, а на замене пробежал 105 секунд. В моем случае я придерживаюсь дубликата ключа.
crunkchitis
1
Протестировал вышеупомянутое с MariaDB 10 и получил предупреждение при запуске INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Флорис
Какую версию MySQL вы использовали для всего этого?
Раду Мурзеа,
41

Что-то важное, что нужно добавить: при использовании INSERT IGNORE, когда у вас есть ключевые нарушения, MySQL НЕ выдает предупреждение!

Например, если вы попытаетесь вставить 100 записей за раз, причем одна неисправная, вы попадете в интерактивный режим:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Как видите: предупреждений нет! Это поведение даже неправильно описано в официальной документации Mysql.

Если ваш скрипт должен быть проинформирован, если некоторые записи не были добавлены (из-за нарушения ключа), вы должны вызвать mysql_info () и проанализировать его для значения «Duplicates».

Jens
источник
6
Если вы используете PHP, вам нужно mysqli_affected_rows()будет узнать, INSERTдействительно ли это произошло.
Амаль Мурали
С обеих MySQL 5.5 и MariaDB 10 я делать получаю сообщение об ошибке , Cannot add or update a child row: a foreign key constraint fails и ни одной строки (даже годные) не добавляются.
Флорис
2
@Floris Эта ошибка вызвана ограничением внешнего ключа, а не дублирующим ключом . Я использую MySQL 5.5.28. При использовании INSERT IGNOREдубликаты ключей игнорируются без ошибок или предупреждений.
Токсалот
20

Я обычно использую INSERT IGNORE, и это звучит точно так же, как вы ищете. Пока вы знаете, что строки, которые могут вызвать конфликты индексов, вставляться не будут, и вы планируете свою программу соответствующим образом, это не должно вызывать никаких проблем.

Дэвид З
источник
4
Я обеспокоен тем, что буду игнорировать ошибки, кроме дублирования. Это правильно или INSERT IGNORE игнорирует только игнорирует ошибку дублирования? Спасибо!
Томас Дж Генри
2
Любая ошибка превращается в предупреждение. Смотрите список таких случаев в моем ответе.
Билл Карвин
Это позор; Я хотел бы, чтобы это только игнорировало дублирующиеся сбои.
Лонни Бест
Ключевые нарушения приводят к ошибкам ! Смотрите мой комментарий в ответе @Jens.
Флорис
1
@Pacerier, это зависит от того, проверяет ли ваше приложение предупреждения. Или если он может проверить на наличие предупреждений. Например, большинство пакетов ORM не дают вам такой возможности. Некоторые коннекторы (например, JDBC) также отделяют вас от MySQL API, поэтому у вас нет возможности проверять предупреждения.
Билл Карвин
18

Я знаю, что это старая версия, но я добавлю эту заметку на случай, если кто-то другой (например, я) попадет на эту страницу, пытаясь найти информацию на INSERT..IGNORE.

Как упоминалось выше, если вы используете INSERT..IGNORE, ошибки, возникающие при выполнении оператора INSERT, обрабатываются как предупреждения.

Одна вещь, которая явно не упоминается, заключается в том, что INSERT..IGNORE приведет к тому, что недопустимые значения будут корректироваться до ближайших значений при вставке (тогда как недопустимые значения приводят к прерыванию запроса, если ключевое слово IGNORE не использовалось).

Крис
источник
6
Я не совсем уверен, что вы подразумеваете под "недопустимыми значениями" и исправлены к чему? Не могли бы вы привести пример или дальнейшее объяснение?
Маренц
4
Это означает, что если вы вставите неправильный тип данных в поле при использовании «INSERT IGNORE», данные будут изменены, чтобы соответствовать типу данных поля, и будет вставлено потенциально недопустимое значение, тогда запрос будет продолжен. Только с INSERT возникнет ошибка о неправильном типе данных, и запрос будет прерван. Это может быть нормально, если число вставляется в varchar или текстовое поле, но вставка текстовой строки в поле с числовым типом данных может привести к получению неверных данных.
Codewaggle
2
@Marenz другой пример: если ваша таблица имеет ненулевой столбец и ваш запрос «INSERT IGNORE» не указывает значение для этого столбца, строка будет вставлена ​​с нулевым значением в этот столбец независимо от того, включен ли строгий sql_mode ,
Шеннон
Хороший вопрос о недопустимых значениях! Эта ветка отлично подходит для изучения "INSERT IGNORE", я тоже оставлю свои 5 центов: medium.com/legacy-systems-diary/… Хорошая статья с примерами того, как осторожно вы должны быть при использовании "INSERT IGNORE" заявление.
0x49D1
8

ON DUPLICATE KEY UPDATE на самом деле не в стандарте. Это примерно так же стандартно, как REPLACE. Смотрите SQL MERGE .

По сути, обе команды являются альтернативно-синтаксическими версиями стандартных команд.

Крис К.Л.
источник
1
replace выполняет удаление и вставку, тогда как при обновлении ключа onduplicate обновляется существующая строка. некоторые отличия: автоматическое увеличение идентификатора, положение строки, куча триггеров
ahnbizcad
8

ReplaceВ кажется, как вариант. Или вы можете проверить с

IF NOT EXISTS(QUERY) Then INSERT

Это будет вставить или удалить, а затем вставить. Я склонен идти на IF NOT EXISTSпроверку первым.

IEnumerator
источник
Спасибо за быстрый ответ. Я предполагаю повсеместно, но я предполагаю, что это будет похоже на ON DUPLICATE KEY UPDATE в том, что оно будет выполнять ненужное обновление. Это кажется расточительным, но я не уверен. Любой из них должен работать. Мне интересно, если кто-нибудь знает, что лучше.
Томас Дж Генри
6
NTuplip - это решение все еще открыто для условий гонки от вставок параллельными транзакциями.
Крис К.Л.
REPLACEудаляет все строки в таблице, соответствующие любому PRIMARY или UNIQUEключу, затем INSERTs . Это потенциально намного больше работы, чем IODKU.
Рик Джеймс
4

Потенциальная опасность INSERT IGNORE. Если вы пытаетесь вставить значение VARCHAR дольше, чем столбец был определен с - значение будет усечено и вставлено ДАЖЕ ЕСЛИ строгий режим включен.

лол
источник
3

При использовании insert ignoreимеющим SHOW WARNINGS;заявление в конце вашего набора запроса покажет таблицу со всеми предупреждениями, в том числе , какие идентификаторы были дубликаты.

Рэй Фосс
источник
SHOW WARNINGS;только кажется, влияет на последний запрос. Любые предыдущие утверждения не накапливаются, если у вас есть более одного утверждения.
Каву
2

Если вы хотите вставить в таблицу и при конфликте первичного ключа или уникального индекса, он обновит конфликтующую строку вместо вставки этой строки.

Синтаксис:

insert into table1 set column1 = a, column2 = b on duplicate update column2 = c;

Теперь здесь этот оператор вставки может выглядеть иначе, чем вы видели ранее. Этот оператор вставки пытается вставить строку в table1 со значениями a и b в столбцы column1 и column2 соответственно.

Давайте разберемся в этом утверждении подробно:

Например: здесь column1 определяется как первичный ключ в table1.

Теперь, если в таблице1 нет строки, имеющей значение «a» в column1. Таким образом, этот оператор вставит строку в таблицу1.

Теперь, если в таблице 1 есть строка, имеющая значение «a» в столбце 2. Таким образом, этот оператор обновит значение столбца в строке на «с», где значение столбца на «а».

Поэтому, если вы хотите вставить новую строку, обновите эту строку в случае конфликта первичного ключа или уникального индекса.
Подробнее по этой ссылке

Дилрадж Сингх
источник
0

INSERT...ON DUPLICATE KEY UPDATE предпочтительнее для предотвращения непредвиденных исключений управления.

Это решение работает, когда у вас ** 1 уникальное ограничение **

В моем случае я знаю это col1и col2делаю уникальный составной индекс.

Он отслеживает ошибку, но не создает исключение для дубликата. Что касается производительности, обновление с тем же значением является эффективным, так как MySQL замечает это и не обновляет его

INSERT INTO table
  (col1, col2, col3, col4)
VALUES
  (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
    col1 = VALUES(col1),
    col2 = VALUES(col2)

Идея использовать этот подход возникла в комментариях на phpdelusion.net/pdo .

micaball
источник