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

时间:2015-02-13 05:20:23

标签: mysql database database-deadlocks

我有一个innoDB表,其中多个连接可能会插入数据,而每10秒运行一个MySql事件会删除一些以前插入的记录。

但我得到了死锁。我该如何避免它们?

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

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 ;

3 个答案:

答案 0 :(得分:1)

至少,您需要在事件代码中启动事务。

这不是由开始...结束,请参阅http://dev.mysql.com/doc/refman/5.6/en/commit.html

  

在所有存储的程序中(存储过程和函数,   触发器和事件),解析器将BEGIN [WORK]视为   BEGIN ... END块的开头。在此开始交易   而不是START TRANSACTION的上下文。

此外,如果您每隔10秒运行一次,请考虑更改您的体系结构。对于寿命较短的数据,关系数据库不太好。另一种选择可能是消息队列。

答案 1 :(得分:1)

首先,我有一些令人讨厌的事情要说,然后我会找到一个可能的解决方案。

“不要排队,只是这样做。” - MySQL没有成为一个好的排队引擎。

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

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

仅将LIMIT降低到10,然后将清除器放入连续循环中,而不是每10秒钟唤醒一次。 (如果您要说“会让事情变得更糟”,那么请继续阅读;我会给您一个可能更好的变体。)

使用LEFT JOIN ... WHERE ... IS NULL代替NOT EXISTS ( SELECT ... )

不要一遍又一遍地重新制作表格;只是TRUNCATE TABLE。或者,更好的是,只需直接删除而无需通过tmp表。然而,这导致另一个问题......

查询必须经过多少行才能找到LIMIT行?请记住,SELECT正在干扰INSERT。如果它通常需要扫描一百万行才能找到要删除的2000,那么我们需要找到一种方法来使行更容易找到。为此,我们需要有关您的应用和表格大小的更多详细信息。或者思考一下......

礼貌地扫描一百万行以找到一些行的一种技术是一次遍历表1000行,通常使用PRIMARY KEY。注意:这是表的1000行,而不是1000行有资格删除。在每1000个之后,删除你找到的那些(如果有的话),然后继续下一个1000.当你到达表的末尾时,重新开始。有关如何执行此分块的详细信息,请访问:http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks

答案 2 :(得分:0)

在我看来,最好的解决方案是使用soft deletes

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

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