Удалить и вставить броски Дубликат записи для ключа ПЕРВИЧНЫЙ - PullRequest
5 голосов
/ 06 апреля 2020

У меня есть составной PK, где мне нужно обновить одно из значений PK. Но из-за некоторых внутренних проблем я не могу запустить запросы на обновление столбца PK.

Поэтому я запускаю DELETE и INSERT запрос.

Это DELETE и INSERT выполняется внутри TRANSACTION (READ COMMITTED).

Но иногда, когда два запроса обновляются в одной строке. Вкл. Дублирующая запись для ключа PRIMARY выдается ошибка. Это происходит случайно, я много пытался воспроизвести эту проблему локально, но не смог найти причину root.

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

  1. Обновить запрос до PK
  2. Заменить запрос
  3. Вставить в дубликаты или Вставить игнорировать запросы
  4. Добавление текущего идентификатора для этой таблицы в настоящее время будет огромной миграцией.

Пожалуйста, помогите мне с этой проблемой.

Обновление:

Пример структуры таблицы:

       Table: temp
Create Table: CREATE TABLE `temp` (
  `id1` int(11) NOT NULL,
  `id2` int(11) NOT NULL,
  `id3` int(11) NOT NULL,
  `value` int(11) DEFAULT NULL,
  PRIMARY KEY (`id1`,`id2`,`id3`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Пример данных:

+-----+-----+-----+-------+
| id1 | id2 | id3 | value |
+-----+-----+-----+-------+
|   1 | 100 | 111 |   123 |
|   2 | 200 | 222 |   456 |
+-----+-----+-----+-------+

Пример запросов:

Попытки транзакции T1 to update ID1=3 value where id2=100

Delete from temp where id2=100
Insert into temp values(3,100,111,123);

Попытки транзакции T2 to update ID1=3 value where id2=100

Delete from temp where id2=100
Insert into temp values(3,100,111,123);

количество удалений и вставок всегда будет одинаковым

Ответы [ 3 ]

2 голосов
/ 11 апреля 2020

Прежде всего, получение ошибки дублированного ключа (или, например, взаимных блокировок) время от времени не является принципиально проблематичным c. Серверы баз данных по своей сути параллельны, и если вы не хотите полностью блокировать базу данных, ожидается, что два процесса могут попытаться сделать что-то, что конфликтует друг с другом (например, попытка вставить одну и ту же строку). Он пойман, и ваша задача теперь реагировать на это должным образом.

В вашей ситуации вы должны получить сообщение об ошибке, когда у вас нет строк для удаления (например, идентификатор еще не существует), что особенно вероятно, если вы используете свой логин c для удаления-вставки в целом даже для совершенно новых записей.

Уровень изоляции read committed не сохраняет блокировку для несуществующих строк (или блокировку пробела):

Для Операторы UPDATE или DELETE, InnoDB удерживает блокировки только для строк, которые он обновляет или удаляет. Блокировки записей для несовпадающих строк снимаются после того, как MySQL оценил условие WHERE. Это значительно снижает вероятность тупиков, но они все же могут возникнуть.

Таким образом, без существующей строки для удаления может иногда происходить следующий поток для двух транзакций A и B, которые хотят вставить один и тот же идентификатор:

A: delete id, no rows -> no lock
B: delete id, no rows -> no lock (and no wait)
A: insert id -> locks row(s)
B: insert id -> wait
A: commit
B: lock released -> insert id -> duplicate key error

Ваши данные могут отличаться , вы можете использовать некоторую форму, которая скрывает эти детали, или, например, у вас может быть дополнительная проверка, чтобы увидеть, существует ли уже этот идентификатор (но затем вы должны включить эту проверку / выбрать с for update в вашей транзакции, чтобы сделать эту проверку согласованной), и затем используйте другую процедуру, которая просто вставляет - но в этом случае вы просто столкнетесь с подобной проблемой: вы просто получите ошибку дублирующего ключа в этой другой процедуре (где две «простые» вставки конфликтуют друг с другом).

В конечном счете, вам просто нужно разобраться со случаем, когда две сессии пытаются сделать одно и то же одновременно. Поскольку вы исключили все решения, которые обрабатывают эту ситуацию внутри базы данных (например, replace или on duplicate key update), вам придется отлавливать и обрабатывать его вручную в своем приложении (например, откат и повтор транзакции).

Кстати, вы можете проверить это, изменив уровень изоляции на repeatable read (но поскольку это может быть значительным изменением, вы должны делать это только в среде разработки). Это должно привести к тупику, а не к ошибке дублированного ключа в этой ситуации. Хотя это не совсем решит вашу проблему, это может убедить вас, что это действительно проблема.

1 голос
/ 15 апреля 2020

@ vinieth

Поскольку id1, id2 и id3 являются первичными ключами, дублировать значение будет невозможно. верно?

Если ваша задача состоит только в обновлении id1, то я бы предложил вам использовать оператор UPDATE вместо операторов DELETE и INSERT. Потому что, выполняя удаление и вставку, вы потребляете ресурс, и это тоже плохая практика.

Вы можете просто написать оператор как

UPDATE `temp`
SET
   id1=3
WHERE
   id2=100;

вместо

Delete from temp where id2=100;
Insert into temp values(3,100,111,123);

Вы можете увидеть разницу между обоими операторами.

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

Механизмы БД более интеллектуальны , Когда вы стреляете оба заявления одновременно. БД подготовит список выписки перед исполнением. Итак, во время подготовки заявления оно остается в памяти стека. И после подготовки запроса они вычисляют время выполнения и упорядочивают их в приоритетном порядке, чтобы ускорить выполнение. В этом случае Ваша операция вставки получила первый приоритет. Таким образом, DB попытается выполнить операцию вставки, найдет значение и выдаст ошибку «Duplicate entry for PRIMARY KEY».

Решение: Если вы не хотите использовать оператор UPDATE вместо DELETE and INSERT. Удалите первичный ключ из id1 и проверьте результат. Но я все же рекомендую вам go с оператором UPDATE.

РЕДАКТИРОВАТЬ:

Существует также другой сценарий, который ответственен за эту ошибку. При попытке запустить оба запроса одновременно. Они оба бегают параллельно. И поскольку DELETE и INSERT оба выполняются одновременно, операция INSERT обнаружила значение, которое еще предстоит удалить. И двигатель возвращает эту ошибку.

0 голосов
/ 09 апреля 2020

Предполагая, что пары delete-insert всегда спарены, используйте INSERT .. ON DUPLICATE KEY UPDATE .. вместо отдельных команд SQL. Это объединяет шаги в одну операцию atomi c. Следовательно, он избегает предполагаемых проблемных транзакций.

Состав PRIMARY KEY вместе со спецификацией для одного столбца для DELETE вызывает недоумение. Это означает, что Delete from temp where id2=100 может удалить несколько строк. Итак, после удаления следует несколько INSERTs? Это может быть достигнуто IODKU, и быть таким же эффективным, возможно, более эффективным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...