【发布时间】:2011-08-21 04:56:53
【问题描述】:
假设我在我们的服务器上收到了 1000 个更新单个 MySQL 表的请求。在这种情况下不可避免地会出现死锁问题。我们已按照建议的死锁重试发布事务,但它们仍然会发生。
我们正在考虑在下面提出一个替代解决方案。
- 创建表 A、B、C。
- 向服务器发送将表 D 更新到 A 或 B 或 C 的写入请求。
- 分别在表 A、B 和 C 上创建一个 INSERT 触发器,依次将数据写入表 D 中,而不是直接将表 D 暴露给到达服务器的 1000 个请求。
所以我们的问题是,当这种情况发生并且多行写入表 A、B 和 C 时,表 A、B 和 C 上的底层触发器可能会同时触发以更新表 D。
MySQL InnoDB 引擎会自动将这些触发器排队,还是我们必须在代码中处理这些?
非常感谢任何帮助。
现在由所有这些请求直接更新的表 D 以及发生死锁的位置如下所示。
v_user_email varchar(60) NO PRI
v_device_IMEI varchar(40) NO PRI
i_adid int(11) NO PRI
i_impressions int(4) YES 0
dt_pulllogdttm datetime NO
c_created_by char(15) NO
dt_created_on datetime NO
c_modified_by char(15) YES
dt_modified_on datetime YES
在此表中插入/更新行的 PHP 如下所示。您会看到,如果由于死锁而失败,我们会尝试发布 3 次事务,但即使在那时也有事务失败,并且日志显示由于死锁。
$updateQuery = "UPDATE tb_ad_pull_log SET i_impressions = (i_impressions + 1), dt_pulllogdttm = SYSDATE(), c_modified_by = '$createdBy', dt_modified_on = SYSDATE() WHERE v_user_email = '$email' AND i_adid = $adId";
if(ExecuteDeadLockQuery($updateQuery, "UPDATE", __LINE__) == 0) // If there is no record for this ad for the user, insert a new record
{
$insertQuery = "INSERT INTO tb_ad_pull_log VALUES('$email', '$device_IMEI', $adId, 1, SYSDATE(), '$createdBy', SYSDATE(), NULL, NULL)";
ExecuteDeadLockQuery($insertQuery, "INSERT", __LINE__);
}
ExecuteDeadLockQuery 函数长这样 -
function ExecuteDeadLockQuery($query, $activity, $lineNumber)
{
global $errorLoggingPath;
$maxAttempts = 3;
$currentTry = 1;
$noOfAffectedRows = -1;
while($currentTry <= $maxAttempts)
{
$currentTry++;
mysql_query($query);
if( mysql_errno() <> 0 ) // If error occured
{
continue;
}
else
{
$noOfAffectedRows = mysql_affected_rows();
break;
}
}
if($noOfAffectedRows == -1) // Query never executed successfully
{
LogError($activity . " failed in tb_ad_pull_log: " . mysql_error(), __FILE__, $lineNumber , $errorLoggingPath);
}
return $noOfAffectedRows;
}
有没有更简洁的方法来避免这种死锁?以下是我们拥有的一些日志。
ERROR: 08-21-2011 14:09:57 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:09:57 INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 86
ERROR: 08-21-2011 14:09:57 INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 86
ERROR: 08-21-2011 14:09:57 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:09:57 INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 86
ERROR: 08-21-2011 14:09:57 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:09:59 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:09:59 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:10:01 UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 83
ERROR: 08-21-2011 14:10:01 INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction LINE 86
第 83 行是 PHP 中的 UPDATE 语句,第 86 行是 INSERT。请记住,这些数据可以以每秒 5-8 个事务的速率写入此表。
其他信息
每次 INSERT 和 UPDATE 到 TABLE D 时,都会执行一个触发器来更新 TABLE X 和 TABLE Y。这是表 D 保持锁定并因此传入请求陷入死锁的原因吗?
终于找到了问题,但我不知道如何解决它。 TABLE D 上的 AFTER INSERT 和 AFTER UPDATE 触发器在触发时会锁定表,因此传入请求会发生死锁。为什么我如此确定这一点是因为一旦我删除了这些触发器,日志就会停止记录否则记录的死锁消息
触发器代码片段。
CREATE DEFINER=CURRENT_USER TRIGGER tuadmin.t_update_CPM_updateBalance
AFTER UPDATE
ON tb_ad_pull_log
FOR EACH ROW
BEGIN
DECLARE `cpm_value` decimal(10,4);
DECLARE `clientid` int(4);
/* Execute the below block if the requested ad is not the default ad */
IF NEW.i_adid <> 1 THEN
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
//do updates to TABLE X and Y logic
END
这就是我不明白为什么这些触发器会锁定表 D 而不让任何插入/更新同时发生。
如果我们放弃触发器,只从 PHP 调用一个 SP 来完成这项工作,这会避免所有问题吗?
【问题讨论】:
-
您的交易是什么样的?为了产生死锁,您应该有多个事务,它们已经更新了一行并尝试更新第二行,但该行已被另一个事务更新。 Simle 插入以及仅对 1 行的更新不会导致死锁。也许您需要按/限制更新查询排序?
-
@Darhazer 更新了包含交易详情的问题。非常感谢您的帮助。
-
你有关于 v_user_email / i_adid 的索引吗?如果不是,那会导致死锁,导致更新查询必须扫描所有记录
-
@Darhazer v_user_email/v_device_IMEI/i_adid 是表的主键
-
您是否对同一封电子邮件有多个查询?您可能知道,BTree 索引中字段的顺序很重要,因此该索引可用于 i_adid 查找,因为未提供 IMEI,并且查询必须锁定给定用户的所有记录。我正在研究这一点,因为仅使用 1 个表时发生死锁是罕见的情况,应该可以通过优化查询来避免。
标签: mysql triggers innodb deadlock