用于发送电子邮件的脚本死锁

Deadlock in script for sending emails

我有一个在后台发送电子邮件的脚本。脚本 运行s 并行地同时发送多封电子邮件。它基本上是这样工作的,混合了 MySQL 和 PHP:

/* TransmissionId is a PRIMARY KEY */
/* StatusId is a FOREIGN KEY */
/* Token is UNIQUE */

/* Pick a queued (StatusId=1) transmission and set it to pending (StatusId=2) */
/* This is a trick to both update a row and store its id for later retrieval in one query */
SET @Ids = 0;
UPDATE transmission
SET StatusId=IF(@Ids := TransmissionId,2,2), LatestStatusChangeDate=NOW()
WHERE StatusId = 1
ORDER BY TransmissionId ASC
LIMIT 1;

/* Fetch the id of the picked transmission */
$Id = SELECT @Ids;

try {
    /* Fetch the email and try to send it */
    $Email = FetchEmail($Id);
    $Email->Send();

    /* Set the status to sent (StatusId=3) */
    $StatusId = 3;
} catch(Exception $E) {
    /* The email could not be sent, set the status to failed (StatusId=4) */
    $StatusId = 4;
} finally {
    /* Save the new transmission status */
    UPDATE transmission
    SET StatusId=$StatusId, LatestStatusChangeDate=NOW(), Token='foobar'
    WHERE TransmissionId = $Id;
}

问题是我有时会遇到死锁:SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction。这发生在执行最后一个查询时。我在执行第一个查询时没有看到它发生。谁能理解在这种情况下如何发生死锁?会不会是第一个查询和最后一个查询锁StatusIdTransmissionId的顺序相反?但是我认为第一个查询不需要加锁TransmissionId,我认为最后一个查询也不需要加锁StatusId。我怎样才能找到这个问题,我该如何解决它?

编辑

还有另一个查询也可能起作用。每当有人打开电子邮件时,此查询为 运行:

/* Selector is UNIQUE */
UPDATE transmission SET
OpenCount=OpenCount+1
WHERE Selector = 'barfoo'

InnoDB 使用自动行级锁定。即使在仅插入或删除一行的事务中,您也可能会遇到死锁。那是因为这些操作并不是真正的“原子”;他们自动在插入或删除的行的(可能是几个)索引记录上设置锁。 dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.ht‌ ml