Как избежать тупика в этом сценарии?

У меня есть таблица innoDB, в которую несколько подключений могут вставлять данные, а одно событие MySql, которое запускается каждые 10 секунд, удаляет некоторые ранее вставленные записи.

Но я получаю тупиковые ситуации. Как их избежать?

Этот код отвечает за построчную вставку записей в таблицу.

sql = "INSERT INTO ex_result (Result_ID, ParentResult_ID, StepNumber, Name, Type, DB_ID, Session_ID, Status, TotalTime, FunctionCall, FunctionResult, ToolTime, PluginName, Screenshot_FID, IsNegative, ContinueOnError, WantSnapshot, Message, ResultCode, Output, InputArguments) VALUES (@Result_ID, @ParentResult_ID, @StepNumber, @Name, @Type, @DB_ID, @Session_ID, @Status, @TotalTime, @FunctionCall, @FunctionResult, @ToolTime, @PluginName, @Screenshot_FID, @IsNegative, @ContinueOnError, @WantSnapshot, @Message, @ResultCode, @Output, @InputArguments)"

Код события:

DELIMITER //

CREATE EVENT EVENT_RESULT_CLEANER
ON SCHEDULE
EVERY 10 second
COMMENT 'Clean all delted results'
DO
BEGIN

DROP TEMPORARY TABLE IF EXISTS `Temp_Result_Purge`;

CREATE TEMPORARY TABLE `Temp_Result_Purge` (
`Result_ID` VARCHAR(63) NOT NULL,
PRIMARY KEY (`Result_ID`))
ENGINE = MEMORY;

INSERT INTO Temp_Result_Purge(Result_ID)
(
    SELECT t1.result_id
    FROM ex_result AS t1
    INNER JOIN ex_session as t2
    ON t1.session_id=t2.session_id
    WHERE NOT EXISTS(SELECT t3.result_id FROM ex_result as t3 WHERE t3.parentresult_id=t1.result_id)
    AND t2.DeletedOn IS NOT NULL
    LIMIT 2000
);

DELETE t1 FROM `ex_result` AS t1 INNER JOIN
`Temp_Result_Purge` AS t2 ON t1.Result_ID=t2.Result_ID;

DROP TEMPORARY TABLE `Temp_Result_Purge`;

END//

DELIMITER ;

person Anshuman Chatterjee    schedule 13.02.2015    source источник
comment
Я не понимаю, почему вы создаете временную таблицу Temp_Result_Purge, а не удаляете напрямую t1 FROM ex_result AS t1 INNER JOIN ex_session как t2 ON t1.session_id = t2.session_id WHERE NOT EXISTS (SELECT t3.result_id FROM ex_result as t3 WHERE t3 .parentresult_id = t1.result_id) И t2.DeletedOn НЕ ЯВЛЯЕТСЯ NULL   -  person Mailkov    schedule 16.02.2015
comment
Вы уверены, что есть тупик? Очень странно, как одиночный INSERT может быть причиной тупика. Есть ли способ запустить процедуру удаления несколько раз одновременно? Это может быть причиной тупика.   -  person i486    schedule 16.02.2015
comment
Пожалуйста, предоставьте SHOW CREATE TABLE ex_result.   -  person Rick James    schedule 17.02.2015
comment
Сразу после выхода из тупика выполните SHOW ENGINE INNODB STATUS и предоставьте нам два оператора, которые оказались в тупике.   -  person Rick James    schedule 17.02.2015
comment
@RickJames: CreateTable и InnoDBStatus для того же были добавлены в вопрос ссылка   -  person Anshuman Chatterjee    schedule 17.02.2015


Ответы (3)


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

«Не ставьте это в очередь, просто сделайте это». - MySQL не может служить хорошим механизмом организации очередей.

Добавьте BEGIN ... COMMIT (как уже упоминалось). И BEGIN ... COMMIT также должен быть связан с другим кодом.

Добавьте код для проверки взаимоблокировок. Затем воспроизведите BEGIN ... COMMIT. Вы не можете избежать всех взаимоблокировок, поэтому планируйте их.

Снизьте ПРЕДЕЛ, скажем, до 10. Затем включите очиститель в непрерывном цикле, а не просыпайтесь каждые 10 секунд. (Если вы собираетесь сказать «это только усугубляет ситуацию», читайте дальше; я дам вам вариант, который может сработать лучше.)

Используйте LEFT JOIN ... WHERE ... IS NULL вместо NOT EXISTS ( SELECT ... )

Не воссоздайте таблицу снова и снова; просто TRUNCATE TABLE. Или, что еще лучше, просто УДАЛИТЬ напрямую, не просматривая таблицу tmp. Однако это приводит к другому вопросу ...

Сколько строк должен пройти запрос, чтобы найти LIMIT строк? Имейте в виду, что SELECT мешает INSERT. Если обычно приходится сканировать миллион строк, чтобы найти 2000 для удаления, то нам нужно найти способ упростить поиск строк. Для этого нам нужна дополнительная информация о вашем приложении и размерах таблиц. Или задумайтесь об этом ...

Один из способов вежливо просканировать миллион строк, чтобы найти всего несколько, - это пройти по таблице 1000 строк за раз, обычно с использованием ПЕРВИЧНОГО КЛЮЧА. Примечание. Это 1000 строк таблицы, а не 1000 строк, которые можно удалить. После каждой 1000 УДАЛИТЕ те, которые вы нашли (если есть), затем переходите к следующей 1000. Когда вы дойдете до конца таблицы, начните заново. Подробная информация о том, как выполнить это разбиение на части, находится здесь: http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks

person Rick James    schedule 17.02.2015
comment
Разбиение на части должно составлять от 100 до 1000 строк за раз. Ваша партия из 5000 будет слишком длинной, особенно после добавления BEGIN ... COMMIT. Тем не менее, разбиение на части заставит работу работать в 10 раз быстрее, что значительно снизит частоту тупиковых ситуаций. - person Rick James; 23.02.2015

По крайней мере, вам нужно начать транзакцию в вашем коде события.

Это не выполняется в начале ... конце, см. http://dev.mysql.com/doc/refman/5.6/en/commit.html

Во всех хранимых программах (хранимых процедурах и функциях, триггерах и событиях) синтаксический анализатор рассматривает BEGIN [WORK] как начало блока BEGIN ... END. Вместо этого начните транзакцию в этом контексте с START TRANSACTION.

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

person Meier    schedule 16.02.2015
comment
Я попробую реализовать Транзакцию в Событии и буду наблюдать. Это должно помочь. - person Anshuman Chatterjee; 16.02.2015

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

Просто установите статус удаленного объекта как удаленный. В случае, если количество записей действительно ОГРОМНОЕ и вы вообще не хотите хранить данные истории, вы можете выполнять плановую очистку базы данных каждую ночь или каждую неделю.

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

person Artjoman    schedule 16.02.2015
comment
Мы уже реализовали мягкое удаление в таблице ex_session (см. T2.DeletedOn IS NULL). Очистка выполняется событием, но INSERT может столкнуться с событием очистки в любое время. - person Anshuman Chatterjee; 16.02.2015