web-dev-qa-db-ja.com

アプリケーションでのデッドロックの発生を防ぐにはどうすればよいですか?

PHP framework(Codeigniter 2.1.0)でLMSアプリケーションを開発しています。MySQLデータベースを使用しています。データベース内のすべてのテーブルにはinnodbエンジンがあり、各テーブルにインデックスも作成しています。今は私は、Jmeterバージョン2.9を使用してローカルで200人のユーザーに対して同時にローカルで負荷テストを行っています。負荷テスト中に、特定のページアクションでDeadlock Foundエラーが発生しました。元のクエリを新しいクエリに変更しましたが、同じですエラーが発生しています。

4つのパラメーターの相互作用配列を取るmodule_id、course_id、user_idのsave_interactions関数を作成し、AJAXスクリプトによって何度も呼び出されています。特定の相互作用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);
        }
    }
}

私はこのタイプのエラーを受け取りました:

ロックを取得しようとしたときにデッドロックが見つかりました。トランザクションを再開してみてください

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

誰かが私に回避方法を提案してくれませんかDeadlock

6
Rohit Gaikwad

免責事項:共謀者ではなく、MySQL DBAのみ

PHPでの制御の流れを見てください。それは私に言います:

  • 次のmod_idcourse_iduser_idinteraction_idを確認してください
  • 見つからない場合、INSERT新しい行
  • 見つかった場合、UPDATE他のすべての列

INSERTおよびUPDATEシナリオには3つのメカニズムが用意されています。

メカニズム#1: REPLACE INTO

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` = ''
;

または

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','');

これは、mod_idcourse_iduser_idinteraction_idに対してDELETEおよびINSERTを機械的に実行します

メカニズム#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` = ''
;

メカニズム#3: 更新用に選択...

正確に更新する予定の行に対して排他ロックを実行できることをご存知ですか?

MySQL Documentation によると、その行でSELECTクエリを実行し、途中で行ロックを発行できます。例えば:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

これにより、テーブル全体がロックされ、counter_field列の更新が可能になります。

あなたの場合、2つのクエリを連続して実行するだけです

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'
;

エピローグ

安全策として、トラフィックが書き込みが多い場合は innodb_lock_wait_timeout を増やしてみてください。 StackOverflowの投稿を参照してください: ロック待機タイムアウトをデバッグする方法を超えましたか?

試してみる !!!

5
RolandoMySQLDBA