在这种情况下如何避免死锁?

How to avoid Deadlock in this scenario?

我有一个 innoDB table,其中多个连接可能会插入数据,并且每 10 秒运行一次的单个 MySql 事件会删除一些先前插入的记录。

但是我遇到了死锁。我怎样才能避免它们?

此代码负责将记录逐行插入table。

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 ;

我认为最好的解决方案是使用 soft deletes

只需将已删除对象的状态设置为已删除即可。如果记录量真的很大并且您根本不想存储历史数据,您可以每晚或每周进行一次计划的数据库清除

缺点之一是您将不得不通过添加一个新条件来重写数据检索逻辑

至少,您需要在您的事件代码中启动一个交易。

这不是在开始...结束时隐式完成的,请参阅http://dev.mysql.com/doc/refman/5.6/en/commit.html

Within all stored programs (stored procedures and functions, triggers, and events), the parser treats BEGIN [WORK] as the beginning of a BEGIN ... END block. Begin a transaction in this context with START TRANSACTION instead.

此外,如果您 运行 每 10 秒执行一次,那么请考虑更改您的架构。关系数据库不太适合生命周期短的数据。另一种可能是消息队列。

首先我有一些讨厌的话要说,然后我会得到一个可能的解决方案。

"Don't queue it, just do it." -- MySQL 不是一个好的排队引擎。

添加 BEGIN...COMMIT(如前所述)。 BEGIN...COMMIT 也需要围绕其他代码。

添加代码来测试死锁。然后重播 BEGIN...COMMIT。您无法避免所有个死锁,因此请为它们做好计划。

将 LIMIT 降低到只有 10 个。然后将净化器置于连续循环中,而不是每 10 秒唤醒一次。 (如果您要说 "that makes things worse",请继续阅读;我会给您一个可能效果更好的变体。)

使用 LEFT JOIN ... WHERE ... IS NULL 而不是 NOT EXISTS ( SELECT ... )

不要一遍又一遍地重新创建 table;只是 TRUNCATE TABLE。或者,更好的是,直接删除而不通过 tmp table。然而,这导致了另一个问题......

查询必须经过多少行才能找到 LIMIT 行?请记住 SELECT 会干扰 INSERT。如果它通常必须扫描一百万行才能找到 2000 行进行删除,那么我们需要想办法让这些行更容易找到。为此,我们需要有关您的应用和 table 大小的更多详细信息。或者思考这个...

礼貌地扫描一百万行以找到其中几行的一种技术是一次遍历 table 1000 行,通常使用 PRIMARY KEY。注意:那是 table 的 1000 行,而不是符合删除条件的 1000 行。在每 1000 次之后,删除您找到的那些(如果有的话),然后继续进行下 1000 次。当您到达 table 的末尾时,重新开始。有关如何执行此分块的详细信息,请参见此处:http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks