MySQL INSERT IGNORE INTO и внешние ключи - PullRequest
34 голосов
/ 27 июля 2011

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

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

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

Спасибо

Ответы [ 6 ]

33 голосов
/ 19 августа 2011

[НОВЫЙ ОТВЕТ]

Спасибо @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, <b>4 rows affected, 2 warnings</b> (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);

с. ПРОБЛЕМА: Проблема возникает при попытке вставить следующие дочерние строки:

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 отменяет запрошенную операцию, поскольку сгенерированная ошибка не превращается в предупреждение (как и предполагалось). Теперь, когда проблема очевидна, давайте посмотрим, как мы можем выполнить последнюю вставку в оператор без каких-либо ошибок.

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

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.

6 голосов
/ 19 августа 2011

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

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

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

3 голосов
/ 27 июля 2011

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

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

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

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

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

1 голос
/ 24 октября 2017

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

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

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

0 голосов
/ 29 июня 2012

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

В этом случае, и почти каждый раз, имеет смыслиспользуйте REPLACE вместо INSERT IGNORE.Другой вариант - ON DUPLICATE KEY UPDATE.

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

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;

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

0 голосов
/ 16 августа 2011

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

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")

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

...