Несколько транзакций, выбирающих одинаковые строки внешнего ключа, приводят к тайм-ауту ожидания блокировки - PullRequest
0 голосов
/ 06 апреля 2020

У меня есть некоторые функции импорта тяжелых данных в приложении, написанном на sylius (symfony ecommerce).

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

enter image description here

Когда действие для вставки в первую таблицу завершено, событие запускается и запускаются действия во второй строке .

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

Сообщение возвращается в очередь, но процесс кажется, останавливается снова и снова.

Могу ли я что-то сделать в этой ситуации с параллельными транзакциями?

Запросы имеют такую ​​форму, более или менее с небольшими изменениями в зависимости от варианта использования:

INSERT INTO product_variant(productid, other_attribute)
VALUES ((SELECT id FROM product WHERE ...), 'value')

1 Ответ

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

Ваш процесс вставки выглядит примерно так:

 INSERT INTO products (this, that, something, else)
               VALUES (?,?,?,?);
 SELECT id FROM products WHERE this=? AND that=?;
 INSERT INTO product_variants ( product_id, col,...) 
                      VALUES   (<<that id you just SELECTED>>, ...);

Другими словами, у вас есть последовательность INSERT, а затем SELECT. Это промышленный рецепт для взаимоблокировок в загруженной СУБД.

Старайтесь избегать SELECT, используя MySQL s LAST_INSERT_ID() function . Он возвращает вам значение идентификатора автоинкремента, обычно первичного ключа, после любой вставки. Сделайте что-то вроде этого:

 INSERT INTO products (this, that, something, else)
               VALUES (?,?,?,?);
 SET @product_id := LAST_INSERT_ID();
 INSERT INTO product_variants ( product_id, col,...) 
                      VALUES   (@product_id, ...);
 SET @product_variant_id := LAST_INSERT_ID();

Тогда вам не нужно будет чередовать INSERT с SELECT только для того, чтобы получить значение id. Операции SET в моем примере позволяют вам повторно использовать значение id для операции INSERT; Следующий INSERT перезаписывает значение LAST_INSERT_ID, поэтому вам нужно сохранить это значение, если оно используется в качестве внешнего ключа в последующих INSERT.

Также попробуйте заключить последовательности INSERT в BEGIN TRANSACTION; и COMMIT;.

Также попробуйте уменьшить количество процессов, потребляющих данные из вашей очереди. Как это ни парадоксально, слишком много одновременных операций в СУБД может снизить производительность. И параллелизм определенно увеличивает вероятность взаимоблокировок.

Редактировать Конечно, все ваши связанные вставки должны быть сделаны в одном соединении с СУБД, чтобы LAST_INSERT_ID был полезен.

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

BEGIN TRANSACTION;
SELECT id INTO @fk
  FROM master_table
 WHERE whatever
   AND whatever
   FOR UPDATE;
 INSERT INTO detail_table (master_id, col, col, col)
                   VALUES (@fk, ?, ?);
 COMMIT;

Этот SELECT ... FOR UPDATE устанавливает блокировку транзакции в строке главной таблицы, с которой вы работаете , Если вы будете тщательно следовать этому шаблону, это приведет к тому, что каждая операция получит необходимые блокировки в том же порядке. Какой промышленный рецепт для предотвращения тупиков. Если вам нужно получить значения FK из нескольких таблиц для выполнения операции, всегда упоминайте таблицы в одном и том же порядке в инструкциях SELECT ... FOR UPDATE

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

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