MySQL INSERT IGNORE в & внешние ключи

почему в MySQL, INSERT IGNORE INTO не меняет ограничение внешнего ключа ошибки в предупреждения?

Я пытаюсь вставить несколько записей в таблицу, и я ожидаю, что MySQL оставит те, которые приводят к ошибке, любой ошибке, и вставит остальные. У кого-нибудь есть предложения?

и SET FOREIGN_KEY_CHECKS = 0; это не мой ответ. Потому что я ожидаю, что строки, которые бросают вызов ограничениям, вообще не будут вставлены.

спасибо

6 ответов


[ОТВЕТ]

спасибо @NeverEndingQueue за это. Кажется, MySQL, наконец, исправил эту проблему. Я не уверен, в какой версии эта проблема была впервые исправлена, но прямо сейчас я тестировал со следующей версией, и проблема больше не существует:

mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 5.7.22                       |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| tls_version             | TLSv1,TLSv1.1                |
| version                 | 5.7.22                       |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | Linux                        |
+-------------------------+------------------------------+

для ясности:

mysql> INSERT IGNORE INTO child
    -> VALUES
    ->     (NULL, 1)
    ->     , (NULL, 2)
    ->     , (NULL, 3)
    ->     , (NULL, 4)
    ->     , (NULL, 5)
    ->     , (NULL, 6);
Query OK, 4 rows affected, 2 warnings (0.03 sec)
Records: 6  Duplicates: 2  Warnings: 2

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

[ОТВЕТ]

мое решение-это работа вокруг проблемы, и фактическое решение всегда будет фиксировать проблему внутри самого MySQL.

следующие шаги решили мою проблему:

a. рассмотрим следующие таблицы и данные:

mysql>
CREATE TABLE parent (id INT AUTO_INCREMENT NOT NULL
                     , PRIMARY KEY (id)
) ENGINE=INNODB;

mysql>
CREATE TABLE child (id INT AUTO_INCREMENT
                    , parent_id INT
                    , INDEX par_ind (parent_id)
                    , PRIMARY KEY (id)
                    , FOREIGN KEY (parent_id) REFERENCES parent(id)
                        ON DELETE CASCADE
                        ON UPDATE CASCADE
) ENGINE=INNODB;

mysql>
INSERT INTO parent
VALUES (NULL), (NULL), (NULL), (NULL), (NULL), (NULL);

mysql>
SELECT * FROM parent;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
+----+

b. теперь нам нужно удалить некоторые строки, чтобы продемонстрировать проблему:

mysql>
DELETE FROM parent WHERE id IN (3, 5);

c. Проблема: проблема возникает при попытке вставить следующие дочерние строки:

mysql>
INSERT IGNORE INTO child
VALUES
    (NULL, 1)
    , (NULL, 2)
    , (NULL, 3)
    , (NULL, 4)
    , (NULL, 5)
    , (NULL, 6);

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint f
ails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERE
NCES `parent` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)

mysql>
SELECT * FROM child;
Empty set (0.00 sec)

хотя IGNORE ключевое слово используется, но MySQL отменяет запрошенную операцию, потому что сгенерированная ошибка не превращается в предупреждение (как это предполагалось). Теперь, когда проблема очевидна, давайте посмотрим, как мы можем выполнить последнюю инструкцию insert into без каких-либо ошибок.

d. Решение: Я собираюсь обернуть insert в оператор некоторыми другие постоянные операторы, которые не зависят ни от вставленных записей, ни от их числа.

mysql>
SET FOREIGN_KEY_CHECKS = 0;

mysql>
INSERT INTO child
VALUES
    (NULL, 1)
    , (NULL, 2)
    , (NULL, 3)
    , (NULL, 4)
    , (NULL, 5)
    , (NULL, 6);

mysql>
DELETE FROM child WHERE parent_id NOT IN (SELECT id FROM parent);

mysql>
SET FOREIGN_KEY_CHECKS = 1;

Я знаю, что это не оптимально, но пока MySQL не исправил проблему, это лучшее, что я знаю. Тем более, что все операторы могут быть выполнены в одном запросе, если вы используете библиотеку mysqli в PHP.


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

INSERT INTO child (parent_id, value2, value3)
SELECT p.id, @new_value2, @new_value3
FROM parent p WHERE p.id = @parent_id

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


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

как для вашего специфического требования:

Я пытаюсь вставить несколько записей в таблицу и я жду MySQL, чтобы исключить те, которые производят ошибку, любую ошибку и вставляют остальной. У кого-нибудь есть предложения?

для этого я рекомендую использовать mysql -f чтобы заставить его продолжать работать, несмотря ни на какие ошибки. Например, если у вас есть такой файл:

insert into child (parent_id, ...) values (bad_parent_id, ...);
insert into child (parent_id, ...) values (good_parent_id, ...);

затем вы можете загрузить этот файл так, что вставит хорошие строки и проигнорирует ошибку из плохих строк:

mysql -f < inserts.sql

эта проблема, кажется, исправлена в MySQL 5.7, см. https://bugs.mysql.com/bug.php?id=78853.

теперь ошибка ограничения внешнего ключа превращается в предупреждение:

эта проблема существует в сборках 5.1,5.5,5.6, но я вижу некоторые улучшения, сделанные в 5.7, где ошибка была преобразована в предупреждение вместо ошибки после WL#6614.


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

в этом случае и почти каждый раз имеет смысл использовать REPLACE вместо INSERT IGNORE. Другой вариант-дублировать ключ ОБНОВЛЕНИЕ.

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

create temporary table tmpTable (col1, col2, ...);
insert into tmpTable values (row1), (row2), ...; -- same insert you already had
alter table tmpTable add key (foreignColumnName); -- only if # of rows is big
insert into table select null /* AI id */, col1, col2 ...
  from tmpTable join parentTable on foreignColumnName = parent.id;

С уважением, Хосе.


Если вы вставляете строку в базу данных, вы можете запустить эту проверку явно с помощью дополнительного запроса. Скажем, у вас есть эти таблицы:

User                               Pet
userId | userName | petId          petId | petName
-------+----------+------          ------+----------
 1     | Harold   | 8               1    | Fido
 2     | Fred     | 3               3    | Spot
                                    8    | Mittens

и вы хотите вставить нового пользователя (Джордж, у которого есть домашнее животное № 1). Вы могли бы сделать что-то вроде

$newUserName = "George"
$petId = "1"
mysql_query("begin")
$result = mysql_query("SELECT petId FROM Pet WHERE petId = $petId LIMIT 1")
if ($result && 1 == mysql_num_rows($result)) {
    mysql_query("INSERT INTO User (userName, petId) VALUES ('$newUserName', $petId)")
}
mysql_query("commit")

пожалуйста, извините чрезмерно упрощенный код и плохие практики в моем примере - это просто означает, как иллюстрация возможной работы.