如何防止应用程序中发生死锁?

Roh*_*wad 6 mysql innodb deadlock

我正在 PHP 框架(Codeigniter 2.1.0)中开发 LMS 应用程序。我正在使用 MySQL 数据库。数据库中的所有表都有innodb引擎。我还在每个表上创建了索引。现在我正在本地使用 Jmeter 2.9 版同时对 200 个用户进行负载测试。在负载测试期间,在特定页面操作中,我遇到了Deadlock Found错误。我将原始查询更改为新查询,但再次发生相同的错误。

我已经编写了 save_interactions 函数,它需要四个参数交互数组、module_id、course_id、user_id & 被 AJAX 脚本调用了很多次。如果特定的interaction_id 不存在于该表中,则以下脚本将插入记录,否则更新查询将被触发。

public function save_interactions($interaction_array,$modid,$cid,$uid)
{
    foreach($interaction_array as $key=>$interact_value)
    {
        $select_query = $this->db->query("SELECT COUNT(*) AS total FROM `scorm_interactions` WHERE `mod_id`='".$modid."' AND `course_id`='".$cid."' AND `user_id`='".$uid."' AND `interaction_id`='".$interact_value[0]."'");
        $fetchRow = $select_query->row_array();

        if($fetchRow['total']==1)
        {
            $update_data = array(
                        "interaction_type"=>$interact_value[1],
                        "time"=>$interact_value[2],
                        "weighting"=>$interact_value[3],
                        "correct_response"=>$interact_value[4],
                        "learner_response"=>$interact_value[5],
                        "result"=>$interact_value[6],
                        "latency"=>$interact_value[7],
                        "objectives"=>$interact_value[8],
                        "description"=>$interact_value[9]
            );
            $this->db->where('mod_id', $modid);
            $this->db->where('course_id', $cid);
            $this->db->where('user_id', $uid);
            $this->db->where('interaction_id', $interact_value[0]);
            $this->db->update('scorm_interactions', $update_data);
        }else
        {
            $insert_data = array(
                        "user_id"=>$uid,
                        "course_id"=>$cid,
                        "mod_id"=>$modid,
                        "interaction_id"=>$interact_value[0],
                        "interaction_type"=>$interact_value[1],
                        "time"=>$interact_value[2],
                        "weighting"=>$interact_value[3],
                        "correct_response"=>$interact_value[4],
                        "learner_response"=>$interact_value[5],
                        "result"=>$interact_value[6],
                        "latency"=>$interact_value[7],
                        "objectives"=>$interact_value[8],
                        "description"=>$interact_value[9]
            );
            $this->db->insert('scorm_interactions', $insert_data);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到了这种类型的错误:

尝试获取锁时发现死锁;尝试重新启动事务

UPDATE `scorm_interactions` SET
    `interaction_type` = 'choice',
    `time` = '10:45:31',
    `weighting` = '1',
    `correct_response` = 'Knees*',
    `learner_response` = 'Knees*',
    `result` = 'correct',
    `latency` = '0000:00:02.11',
    `objectives` = 'Question2_1',
    `description` = ''
WHERE
    `mod_id` =  '4' AND
    `course_id` =  '5' AND
    `user_id` =  '185' AND
    `interaction_id` =  'Question2_1'
;

Filename: application/models/user/scorm1_2_model.php Line Number: 234
Run Code Online (Sandbox Code Playgroud)

谁能建议我如何避免死锁

Rol*_*DBA 5

免责声明:不是 Codeigniter 人员,只是 MySQL DBA

查看 PHP 中的控制流。它对我说:

  • 检查以下mod_id, course_id, user_id,interaction_id
  • 如果没有找到,INSERT新行
  • 如果找到,UPDATE所有其他列

为 INSERT 和 UPDATE 场景提供了三 (3) 种机制

机制#1:替换成

REPLACE INTO `scorm_interactions` SET
    `mod_id` =  '4',
    `course_id` =  '5',
    `user_id` =  '185',
    `interaction_id` =  'Question2_1',
    `interaction_type` = 'choice',
    `time` = '10:45:31',
    `weighting` = '1',
    `correct_response` = 'Knees*',
    `learner_response` = 'Knees*',
    `result` = 'correct',
    `latency` = '0000:00:02.11',
    `objectives` = 'Question2_1',
    `description` = ''
;
Run Code Online (Sandbox Code Playgroud)

或者

REPLACE INTO `scorm_interactions`
(`mod_id`,`course_id`,`user_id`,`interaction_id`,
`interaction_type`,`time`,`weighting`,`correct_response`,
`learner_response`,`result`,`latency`,`objectives`,`description`) VALUES
('4','5','185','Question2_1','choice','10:45:31','1','Knees*',
'Knees*','correct','0000:00:02.11','Question2_1','');
Run Code Online (Sandbox Code Playgroud)

这在mod_id, course_id, user_id,上机械地执行 DELETE 和 INSERTinteraction_id

机制#2:在重复密钥上插入...

INSERT INTO `scorm_interactions`
(`mod_id`,`course_id`,`user_id`,`interaction_id`,
`interaction_type`,`time`,`weighting`,`correct_response`,
`learner_response`,`result`,`latency`,`objectives`,`description`) VALUES
('4','5','185','Question2_1',
'choice','10:45:31','1','Knees*',
'Knees*','correct','0000:00:02.11','Question2_1','')
ON DUPLICATE KEY UPDATE
    `interaction_type` = 'choice',
    `time` = '10:45:31',
    `weighting` = '1',
    `correct_response` = 'Knees*',
    `learner_response` = 'Knees*',
    `result` = 'correct',
    `latency` = '0000:00:02.11',
    `objectives` = 'Question2_1',
    `description` = ''
;
Run Code Online (Sandbox Code Playgroud)

机制#3:选择......更新

您是否知道可以对要精确更新的行执行排他锁?

根据MySQL 文档,您可以SELECT在该行上运行查询并在此过程中发出行锁。例如:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
Run Code Online (Sandbox Code Playgroud)

这将锁定整个表并允许更新 counter_field 列。

在您的情况下,只需背靠背运行两个查询

SELECT * FROM `scorm_interactions` 
WHERE `mod_id` =  '4',
AND `course_id` =  '5',
AND `user_id` =  '185',
AND `interaction_id` =  'Question2_1'
FOR UPDATE ;
UPDATE `scorm_interactions` SET
    `interaction_type` = 'choice',
    `time` = '10:45:31',
    `weighting` = '1',
    `correct_response` = 'Knees*',
    `learner_response` = 'Knees*',
    `result` = 'correct',
    `latency` = '0000:00:02.11',
    `objectives` = 'Question2_1',
    `description` = ''
WHERE `mod_id` =  '4'
AND `course_id` =  '5',
AND `user_id` =  '185',
AND `interaction_id` =  'Question2_1'
;
Run Code Online (Sandbox Code Playgroud)

结语

作为安全缓冲,如果流量是写密集的,请尝试增加innodb_lock_wait_timeout。请参阅我的 StackOverflow 帖子:如何调试超出锁定等待超时?

试一试 !!!