"ВСТАВКА IGNORE" проти "ВСТАВКА ... НА ДУПЛІКАТИ КЛЮЧОВОГО ОНОВЛЕННЯ"


833

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

  • ON DUPLICATE KEY UPDATE що означає непотрібне оновлення за певну ціну, або
  • INSERT IGNORE що має на увазі запрошення для інших видів невдач, щоб пропустити їх без повідомлення.

Я прав у цих припущеннях? Який найкращий спосіб просто пропустити рядки, які можуть спричинити дублікати, і просто продовжити до інших рядків?

Відповіді:


990

Я б рекомендував використовувати 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?
Lonnie Best

1
@LonnieBest: Запит на функцію впровадження MERGE був зроблений у 2005 році, але наскільки я не знаю прогресу чи плану. bugs.mysql.com/bug.php?id=9018
Білл Карвін

2
О, я можу додати, що він генерує попередження (а не помилки) для невідповідності недійсного типу, але не генерує попередження для дублюється складеного первинного ключа.
Фабріціо Матте

11
Я щойно дивився на таблицю, яка була заповнена безліччю INSERT ... ON DUPLICATE KEY UPDATE ...тверджень. Багато даних є дублюючими, і це призвело до того, що один екземпляр AI PK збільшився з 17,029,941 до 46,271,740 між двома рядами. Таке покоління нового AI кожного разу означає, що ваш асортимент може дуже швидко заповнитися і вам потрібно прибрати. Цьому столу всього два тижні!
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 секунд. У моєму випадку я дотримуюся дублюючого ключа.
хрускіт

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 () та проаналізувати його на значення "Дублікати".


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
Це перетворює будь-яку помилку на попередження. Дивіться список таких випадків у моїй відповіді.
Білл Карвін

Це ганьба; Мені б хотілося, щоб вона ігнорувала лише дублюючі невдачі.
Lonnie Best

Основні порушення викликають помилки ! Дивіться мій коментар у відповіді @Jens.
Флоріс

1
@Pacerier, це залежить від того, чи перевіряє ваша програма на попередження. Або якщо він може перевірити наявність попереджень. Наприклад, більшість пакетів ORM не дають вам можливості. Деякі роз'єми (наприклад, JDBC) також відокремлюють вас від API MySQL, щоб у вас не було можливості перевірити попередження.
Білл Карвін

18

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

Як було сказано вище, якщо ви використовуєте INSERT..IGNORE, помилки, які виникають під час виконання оператора INSERT, замість цього трактуються як попередження.

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


6
Я не дуже впевнений, що ви маєте на увазі під "недійсними значеннями" і виправлені до чого? Чи можете ви надати приклад чи додаткове пояснення?
Marenz

4
Це означає, що якщо ви вставите неправильний тип даних у поле під час використання "INSERT IGNORE", дані будуть змінені, щоб відповідати типу даних поля та буде вставлено потенційно недійсне значення, тоді запит буде продовжувати працювати. Лише для "INSERT" виникне помилка щодо неправильного типу даних, а запит буде скасовано. Це може бути гаразд, коли число вставляється у варшар або текстове поле, але вставлення текстового рядка в поле з числовим типом даних призведе до поганих даних.
codewaggle

2
@Marenz ще один приклад: якщо у вашій таблиці є стовпець, що не застосовується до нуля, а запит "INSERT IGNORE" не вказує значення для цього стовпця, рядок буде вставлено з нульовим значенням у цей стовпець, незалежно від того, включений строгий sql_mode .
Шеннон

Хороший пункт про недійсні значення! Цей потік чудово підходить для вивчення "INSERT IGNORE", я також залишаю свої 5 копійок: medium.com/legacy-systems-diary/… приємна стаття з прикладами того, як обережно слід бути під час використання "INSERT IGNORE" заява.
0x49D1

8

ON KEY UPDATE DUPLICATE НЕ дійсно в стандарті. Це приблизно так само стандартно, як і ЗАМІНА. Див. SQL MERGE .

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


1
замість видаляє та вставляє, тоді як оновлення копії ключа оновлює існуючий рядок. деякі відмінності: автоматичний приріст ідентифікатора, позиція рядка, купа тригерів
ahnbizcad

8

ReplaceInto здається варіантом. Або ви можете перевірити

IF NOT EXISTS(QUERY) Then INSERT

Це буде вставити або видалити, потім вставити. Я схильний IF NOT EXISTSспершу йти на перевірку.


Дякуємо за швидку відповідь. Я припускаю, що в усьому світі, але я припускаю, що це буде аналогічно ON DUPLICATE KEY UPDATE, оскільки воно буде виконувати непотрібне оновлення. Це виглядає марно, але я не впевнений. Будь-яке з них має спрацювати. Мені цікаво, чи хтось знає, що найкраще.
Томас Г Генрі

6
NTuplip - це рішення все ще відкрите для перегонів за умовами одночасних транзакцій.
Кріс КЛ

REPLACEвидаляє всі рядки в таблиці з відповідними будь-яким PRIMARY або UNIQUEклавіші, а потім INSERTs . Це потенційно набагато більше роботи, ніж ІОДКУ.
Рік Джеймс

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 у стовпчик колонки1 та колонки2 відповідно.

Давайте глибше розберемося з цим твердженням:

Наприклад: тут стовпчик1 визначається як основний ключ у таблиці1.

Тепер, якщо в table1 немає рядка зі значенням "a" у колонці1. Отже цей вислів буде вставляти рядок у таблицю1.

Тепер, якщо в table1 є рядок зі значенням "a" у колонці2. Таким чином, це твердження оновить значення рядка колонки2 на "с", де значенням стовпець1 є "а".

Тож якщо ви хочете вставити новий рядок, інакше оновіть цей рядок на конфлікт первинного ключа або унікального індексу.
Детальніше читайте за цим посиланням


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)

Ідея використовувати такий підхід виникла з коментарів на phpdelusions.net/pdo .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.