MySQL InnoDBは、READ COMMITTEDであっても削除時に主キーをロックします


11

序文

私たちのアプリケーションは、DELETEクエリを並行して実行するいくつかのスレッドを実行します。クエリは分離されたデータに影響します。つまりDELETE、別々のスレッドからの同じ行で同時発生する可能性はありません。ただし、ドキュメントごとに、MySQLはDELETEステートメントにいわゆる「次のキー」ロックを使用します。これにより、一致するキーと一部のギャップの両方がロックされます。このことはデッドロックを引き起こし、私たちが見つけた唯一の解決策はREAD COMMITTED分離レベルを使用することです。

問題

巨大なテーブルDELETEJOIN複数ある複雑なステートメントを実行すると問題が発生します。特定のケースでは、警告が2行しかないテーブルがありますが、クエリは2つの個別のINNER JOINedテーブルから特定のエンティティに属するすべての警告を削除する必要があります。クエリは次のとおりです。

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1

day_positionテーブルが十分に大きい場合(私のテストケースでは1448行あります)、READ COMMITTED分離モードでもトランザクションはテーブル全体を ブロックしproc_warningsます。

この問題は常にこのサンプルデータ(http://yadi.sk/d/QDuwBtpW1BxB9)で再現されます(MySQL 5.1(5.1.59でチェック)およびMySQL 5.5(MySQL 5.5.24でチェック))。

編集:リンクされたサンプルデータには、クエリテーブルのスキーマとインデックスも含まれています。

CREATE TABLE  `proc_warnings` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned NOT NULL,
    `warning` varchar(2048) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `proc_warnings__transaction` (`transaction_id`)
);

CREATE TABLE  `day_position` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_day_id` int(10) unsigned DEFAULT NULL,
    `dirty_data` tinyint(4) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `day_position__trans` (`transaction_id`),
    KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
    KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;

CREATE TABLE  `ivehicle_days` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `d` date DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_id` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
    KEY `ivehicle_days__d` (`d`)
);

トランザクションごとのクエリは次のとおりです。

  • トランザクション1

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
  • トランザクション2

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;

それらの1つは常に「ロック待機タイムアウトを超えました...」エラーで失敗します。にinformation_schema.innodb_trxは次の行が含まれます。

| trx_id     | trx_state   | trx_started           | trx_requested_lock_id  | trx_wait_started      | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2'      | '3089'              | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING'   | '2012-12-12 19:58:02' | NULL                   | NULL | '7' | '3087' | NULL |

information_schema.innodb_locks

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

両方のクエリでX、主キー= 53の行に対する排他ロックが必要であることがわかりproc_warningsます。ただし、どちらのクエリもテーブルから行を削除する必要はありません。インデックスがロックされている理由がわかりません。さらに、proc_warningsテーブルが空の場合、またはday_positionテーブルに含まれる行数が少ない場合(つまり、100行)、インデックスはロックされません。

さらなる調査はEXPLAIN、同様のSELECTクエリを実行することでした。これは、クエリオプティマイザーがproc_warningsテーブルのクエリにインデックスを使用しないことを示しています。これが、主キーインデックス全体をブロックする理由を想像できる唯一の理由です。

簡略化されたケース

また、レコードが2つあるテーブルが2つしかなく、子テーブルの親テーブルのref列にインデックスがない場合にも、問題はより簡単なケースで再現できます。

parentテーブルを作成

CREATE TABLE `parent` (
  `id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

childテーブルを作成

CREATE TABLE `child` (
  `id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

テーブルを埋める

INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);

2つの並列トランザクションでテストします。

  • トランザクション1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • トランザクション2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

どちらの場合にも共通するのは、MySQLがインデックスを使用しないことです。それがテーブル全体をロックする理由だと思います。

私たちのソリューション

現時点で確認できる唯一の解決策は、デフォルトのロック待機タイムアウトを50秒から500秒に増やして、スレッドがクリーンアップを完了するようにすることです。その後、指を交差させてください。

助けてくれてありがとう。


質問があります。トランザクションのいずれかでCOMMITを実行しましたか?
RolandoMySQLDBA

もちろん。問題は、他のすべてのトランザクションが、そのうちの1つが変更をコミットするまで待機する必要があることです。単純なテストケースには、問題の再現方法を示すcommitステートメントが含まれていません。待機していないトランザクションでコミットまたはロールバックを実行すると、同時にロックが解除され、待機しているトランザクションが完了します。
vitalidze

MySQLがどちらの場合もインデックスを使用しないと言うとき、それは実際のシナリオには何もないためですか?インデックスがある場合、それらのコードを提供できますか?以下に掲載されているインデックスの提案を試すことはできますか?インデックスがなく、追加を試みることができない場合、MySQLは各スレッドによって処理されるデータセットを制限できません。その場合、NスレッドはサーバーのワークロードをN倍するだけで、1つのスレッドを{WHERE vd.ivehicle_id IN(2、13)AND dp.dirty_data =のようなパラメーターリストで実行する方が効率的です。 1;}。
JMヒックス

わかりました、リンクされたサンプルデータファイルに隠れているインデックスを見つけました。
JM Hicks

さらにいくつか質問:1)day_positionテーブルの実行が遅くなりすぎてタイムアウト制限を500秒に上げなければならない場合、テーブルには通常いくつの行が含まれていますか?2)サンプルデータしかない場合、実行にどのくらいの時間がかかりますか?
JM Hicks

回答:


3

新しい回答(MySQLスタイルの動的SQL):わかりました。これは、他の投稿者が説明した方法で問題に取り組みます-相互に互換性のない排他ロックが取得される順序を逆にすることで、発生するロックの数に関係なく、発生するのはトランザクション実行の最後の最短時間。

これは、ステートメントの読み取り部分をそれ自体の選択ステートメントに分離し、ステートメントの出現順序が原因で最後に実行するよう強制され、proc_warningsテーブルにのみ影響する削除ステートメントを動的に生成することによって実現されます。

デモはsql fiddleで入手できます。

このリンクは、サンプルデータを含むスキーマと、に一致する行の簡単なクエリを示していますivehicle_id=2。削除されていないため、結果は2行になります。

このリンクは同じスキーマとサンプルデータを示していますが、値2をDeleteEntriesストアドプログラムに渡し、SPにのproc_warningsエントリを削除するように指示していますivehicle_id=2。行の単純なクエリでは、すべてが正常に削除されているため、結果は返されません。デモリンクは、コードが削除を意図したとおりに機能することを示すだけです。適切なテスト環境を持つユーザーは、これがブロックされたスレッドの問題を解決するかどうかについてコメントできます。

便宜上、ここにもコードがあります。

CREATE PROCEDURE DeleteEntries (input_vid INT)
BEGIN

    SELECT @idstring:= '';
    SELECT @idnum:= 0;
    SELECT @del_stmt:= '';

    SELECT @idnum:= @idnum+1 idnum_col, @idstring:= CONCAT(@idstring, CASE WHEN CHARACTER_LENGTH(@idstring) > 0 THEN ',' ELSE '' END, CAST(id AS CHAR(10))) idstring_col
    FROM proc_warnings
    WHERE EXISTS (
        SELECT 0
        FROM day_position
        WHERE day_position.transaction_id = proc_warnings.transaction_id
        AND day_position.dirty_data = 1
        AND EXISTS (
            SELECT 0
            FROM ivehicle_days
            WHERE ivehicle_days.id = day_position.ivehicle_day_id
            AND ivehicle_days.ivehicle_id = input_vid
        )
    )
    ORDER BY idnum_col DESC
    LIMIT 1;

    IF (@idnum > 0) THEN
        SELECT @del_stmt:= CONCAT('DELETE FROM proc_warnings WHERE id IN (', @idstring, ');');

        PREPARE del_stmt_hndl FROM @del_stmt;
        EXECUTE del_stmt_hndl;
        DEALLOCATE PREPARE del_stmt_hndl;
    END IF;
END;

これは、トランザクション内からプログラムを呼び出す構文です。

CALL DeleteEntries(2);

元の回答(まだ粗末ではないと思います)2つの問題のように見えます:1)クエリが遅い2)予期しないロック動作

問題#1に関して、遅いクエリは、タンデムクエリステートメントの単純化とインデックスの便利な追加または変更における同じ2つの手法によって解決されることがよくあります。あなた自身はすでにインデックスへの接続を作成しています-それらがないと、オプティマイザは処理する行の限定されたセットを検索できず、追加の行ごとに乗算する各テーブルの各行は、実行する必要がある追加の作業量をスキャンしました。

スキーマとインデックスのポストを見た後に改訂:しかし、適切なインデックス構成があることを確認することで、クエリのパフォーマンスが最も向上すると思います。これを行うには、大きなインデックスをトレードオフし、追加のインデックス構造が追加された同じテーブルで挿入パフォーマンスを著しく遅くして、削除パフォーマンスを向上させ、場合によっては削除パフォーマンスをさらに向上させることができます。

より良いもの:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd` (`dirty_data`, `ivehicle_day_id`)

) ;


CREATE TABLE  `ivehicle_days` (
    ...,
    KEY `ivehicle_days__vid_no_sort_index` (`ivehicle_id`)
);

ここでも修正:実行に時間がかかるので、dirty_dataをインデックスに残しておきます。インデックスの順序でivehicle_day_idの後に配置した場合も、間違いです。

しかし、私がそれを手にした場合、現時点では、それを長くするのに十分な量のデータがなければならないので、最高のインデックスを取得していることを確認するために、すべてのカバーするインデックスに行きます。問題のその部分を除外する他に何もない場合、私のトラブルシューティングの時間は買うことができます。

ベスト/カバーインデックス:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd_trnsid_cvrng` (`dirty_data`, `ivehicle_day_id`, `transaction_id`)
) ;

CREATE TABLE  `ivehicle_days` (
    ...,
    UNIQUE KEY `ivehicle_days__vid_id_cvrng` (ivehicle_id, id)
);

CREATE TABLE  `proc_warnings` (

    .., /*rename primary key*/
    CONSTRAINT pk_proc_warnings PRIMARY KEY (id),
    UNIQUE KEY `proc_warnings__transaction_id_id_cvrng` (`transaction_id`, `id`)
);

最後の2つの変更提案で求められるパフォーマンス最適化の目標は2つあります
。1)連続してアクセスされるテーブルの検索キーが、現在アクセスされているテーブルに対して返されるクラスター化されたキーの結果と同じでない場合、必要だったものを排除します。クラスタ化インデックスの
2 番目のインデックスシークありスキャンオペレーションのセット2)後者でない場合でも、インデックスはインデックスを維持するため、オプティマイザがより効率的な結合アルゴリズムを選択できる可能性があります。ソート順に必要な結合キー。

クエリは可能な限り簡略化されているようです(後で編集する場合に備えて、ここにコピーします)。

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
    ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
    ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;

もちろん、クエリオプティマイザーの進行方法に影響を与える結合順序に関する記述がない場合は、他の人が提供した書き換え提案のいくつかを試すことができます。

DELETE FROM proc_warnings
FORCE INDEX (`proc_warnings__transaction_id_id_cvrng`, `pk_proc_warnings`)
WHERE EXISTS (
    SELECT 0
    FROM day_position
    FORCE INDEX (`day_position__id_rvrsd_trnsid_cvrng`)  
    WHERE day_position.transaction_id = proc_warnings.transaction_id
    AND day_position.dirty_data = 1
    AND EXISTS (
        SELECT 0
        FROM ivehicle_days
        FORCE INDEX (`ivehicle_days__vid_id_cvrng`)  
        WHERE ivehicle_days.id = day_position.ivehicle_day_id
        AND ivehicle_days.ivehicle_id = ?
    )
);

#2に関しては、予期しないロック動作。

両方のクエリで、主キー= 53の行に排他的Xロックが必要なことがわかります。ただし、どちらもproc_warningsテーブルから行を削除する必要はありません。インデックスがロックされている理由がわかりません。

ロックされるデータの行はクラスター化インデックスにあるため、つまり、データの単一行自体がインデックスに存在するため、ロックされるのはインデックスだと思います。

次の理由でロックされます
。1)http://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.htmlによると

... a DELETEは通常、SQLステートメントの処理でスキャンされるすべてのインデックスレコードにレコードロックを設定します。行を除外するステートメントにWHERE条件があるかどうかは関係ありません。InnoDBは正確なWHERE条件を記憶していませんが、スキャンされたインデックス範囲のみを認識しています。

また、上記のとおりです。

...私にとって、READ COMMITTEDの主な機能は、ロックの処理方法です。一致しない行のインデックスロックを解放する必要がありますが、解放しません。

そのために次の参照を提供しました:http :
//dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-committed

どちらもあなたと同じですが、同じ参照に従ってロックが解放される条件があることを除きます。

また、MySQLがWHERE条件を評価した後に、一致しない行のレコードロックが解放されます。

これは、このマニュアルページhttp://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.htmlでも繰り返されます。

READ COMMITTED分離レベルを使用したり、innodb_locks_unsafe_for_binlogを有効にしたりすると、他にも影響があります。MySQLがWHERE条件を評価した後に、一致しない行のレコードロックが解放されます。

したがって、ロックを解除する前にWHERE条件を評価する必要があると言われています。残念ながら、WHERE条件がいつ評価されるかはわかりません。おそらく、オプティマイザによって作成されたプランから別のプランに変更される可能性があります。しかし、それはロックの解放がクエリ実行のパフォーマンスに何らかの形で依存していることを教えてくれます。前述のように、その最適化はステートメントの慎重な記述とインデックスの賢明な使用に依存しています。テーブルの設計を改善することで改善することもできますが、それはおそらく別の質問に任せるのが最善です。

さらに、proc_warningsテーブルが空の場合もインデックスはロックされません。

インデックス内のレコードがない場合、データベースはレコードをロックできません。

さらに、... day_positionテーブルに含まれる行数が少ない場合(つまり、100行)、インデックスはロックされません。

これは、統計情報の変更による異なる実行プラン、非常に小さいデータセットによる非常に高速な実行による非常に速すぎるロックなど、多くのことを意味しますが、これらに限定されません。結合操作。


WHEREクエリが完了すると、条件が評価されます。だよね?いくつかの同時クエリが実行された直後にロックが解除されると思いました。それが自然な振る舞いです。しかし、これは起こりません。このスレッドで提案されているクエリはどちらも、proc_warningsテーブルでのクラスター化インデックスのロックを回避するのに役立ちません。MySQLにバグを報告すると思います。ご協力いただきありがとうございます。
vitalidze

ロック動作を回避することも期待していません。ドキュメントがそれがクエリを処理する方法であるかどうかに関係なく、それが期待されているものであるとドキュメントが言っていると思うので、私はそれがロックすることを期待します。私は、パフォーマンスの問題を取り除くことで、同時クエリがそのような明らかに(500秒以上のタイムアウト)長い時間ブロックされないようにすることを期待しています。
JM Hicks

{WHERE}は、結合処理中に結合計算に含まれる行を制限するために使用できるようですが、結合のセット全体が完了するまで、ロックされた行ごとに{WHERE}句をどのように評価できるかわかりません。同様に計算されます。とはいえ、私たちの分析では、「クエリが完了したときにWHERE条件が評価される」と疑うべきだと私は思っています。それでも、全体的な結果は同じですが、パフォーマンスを解決する必要があります。そうすれば、並行性の見かけの度合いが比例して増加します。
JM Hicks

適切なインデックスは、proc_warningsテーブルで発生するすべてのテーブルスキャンを潜在的に排除できることに注意してください。そのためには、クエリオプティマイザーが適切に機能し、インデックス、クエリ、およびデータが適切に機能する必要があります。パラメータ値は、2つのクエリ間で重複しないターゲットテーブルの行に対して最後に評価する必要があります。インデックスは、これらの行を効率的に検索する手段をクエリオプティマイザーに提供する必要があります。その潜在的な検索効率を認識し、そのような計画を選択するには、オプティマイザーが必要です。
JM Hicks

すべてのパラメーター値、インデックス、オーバーラップしない結果のproc_warningsテーブル、およびオプティマイザープランの選択がうまくいく場合、各スレッドのクエリを実行するために必要な時間の期間ロックが生成される可能性がありますが、そうでない場合、それらのロックオーバーラップし、他のスレッドのロック要求と競合しません。
JM Hicks

3

READ_COMMITTEDがこの状況をどのように引き起こしているのかがわかります。

READ_COMMITTEDでは、次の3つのことが可能です。

  • READ_COMMITTED分離レベルを使用した他のトランザクションによるコミットされた変更の可視性。
  • 繰り返し不可の読み取り:同じ検索を実行するトランザクションで、毎回異なる結果が得られる可能性があります。
  • ファントム:トランザクションでは、事前に表示されなかった場所に行が表示される場合があります。

トランザクションは以下との接触を維持する必要があるため、トランザクション自体の内部パラダイムが作成されます。

  • InnoDBバッファープール(コミットがまだフラッシュされていない間)
  • テーブルの主キー
  • たぶん
    • 二重書き込みバッファ
    • テーブルスペースを元に戻す
  • 画像表現

2つの異なるREAD_COMMITTEDトランザクションが同じ方法で更新されている同じテーブル/行にアクセスしている場合は、テーブルロックではなく、gen_clust_index(別名クラスター化インデックス)内の排他ロックを期待できるように準備してください。あなたの単純化されたケースからのクエリを考えると:

  • トランザクション1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • トランザクション2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

gen_clust_indexの同じ場所をロックしています。「しかし、各トランザクションには異なる主キーがあります。」と言うかもしれません。残念ながら、これはInnoDBの目には当てはまりません。id 1とid 2が同じページにあるのは偶然です。

information_schema.innodb_locks質問で提供されたあなたを振り返ってください

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

除いてlock_idlock_trx_idロックの説明の残りの部分は同じです。トランザクションは同じレベルの競争条件にあるため(同じトランザクション分離)、これは実際に発生するはずです。

私を信じて、私は以前にこのような状況に対処したことがあります。これに関する私の過去の投稿は次のとおりです。


MySQLのドキュメントであなたが説明していることについて読みました。しかし、私にとって、READ COMMITTEDの主な機能は、ロックの処理方法です。一致しない行のインデックスロックを解放する必要がありますが、解放しません。
vitalidze

エラーの結果として単一のSQLステートメントのみがロールバックされた場合、ステートメントによって設定されたロックの一部が保持される可能性があります。InnoDBは、それがどの文によって設定されたロック後で知ることができないような形式で行ロックを保存するためです:dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html
RolandoMySQLDBA

ロックのために同じページに2つの行が存在する可能性について言及したことに注意してください(を参照Look back at information_schema.innodb_locks you supplied in the Question
RolandoMySQLDBA

単一のステートメントのロールバックについて-単一のトランザクション内で単一のステートメントが失敗した場合でも、ロックを保持する可能性があることを理解しています。それで大丈夫です。私の大きな質問は、DELETEステートメントが正常に処理された後、なぜ一致しない行ロックが解放されないのかということです。
vitalidze

2つのロックを完了すると、1つをロールバックする必要があります。ロックが残っている可能性があります。WORKING THEORY:ロールバックしたトランザクションは再試行する可能性があり、それを保持していた前のトランザクションからの古いロックが発生する可能性があります。
RolandoMySQLDBA 2012

2

クエリと説明を見ました。よくわかりませんが、次のような問題があると直感しています。クエリを見てみましょう:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1;

同等のSELECTは次のとおりです。

SELECT pw.id
FROM proc_warnings pw
INNER JOIN day_position dp
   ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
   ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=16 AND dp.dirty_data=1;

その説明を見ると、実行プランがproc_warningsテーブルから始まっていることがわかります。つまり、MySQLはテーブルの主キーをスキャンし、各行について条件がtrueであるかどうかを確認し、trueである場合は行を削除します。つまり、MySQLは主キー全体をロックする必要があります。

必要なのは、JOIN順序を逆にすることです。つまり、すべてのトランザクションIDを見つけvd.ivehicle_id=16 AND dp.dirty_data=1て、proc_warningsテーブルに結合します。

つまり、インデックスの1つにパッチを適用する必要があります。

ALTER TABLE `day_position`
 DROP INDEX `day_position__id`,
 ADD INDEX `day_position__id`
   USING BTREE (`ivehicle_day_id`, `dirty_data`, `transaction_id`);

削除クエリを書き換えます。

DELETE pw
FROM (
  SELECT DISTINCT dp.transaction_id
  FROM ivehicle_days vd
  JOIN day_position dp ON dp.ivehicle_day_id = vd.id
  WHERE vd.ivehicle_id=? AND dp.dirty_data=1
) as tr_id
JOIN proc_warnings pw ON pw.transaction_id = tr_id.transaction_id;

残念ながら、これは役に立ちませんproc_warnings。つまり、行はまだロックされています。とにかくありがとう。
vitalidze

2

方法を指定せずにトランザクションレベルを設定すると、次のトランザクションのみにコミット済み読み取りが適用されるため、(自動コミットを設定する)。これは、autocommit = 0の後、Read Committedではなくなったことを意味します。私はそれをこのように書きます:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
DELETE c FROM child c
INNER JOIN parent p ON
    p.id = c.parent_id
WHERE p.id = 1;

クエリを実行することで、現在の分離レベルを確認できます

SELECT @@tx_isolation;

それは真実ではない。なぜSET AUTOCOMMIT=0次のトランザクションの分離レベルをリセットする必要があるのですか?以前に何も開始されなかった場合は、新しいトランザクションが開始されると思います(これは私の場合です)。したがって、より正確には、次のSTART TRANSACTIONor BEGINステートメントは不要です。自動コミットを無効にする目的は、DELETEステートメントの実行後にトランザクションを開いたままにすることです。
vitalidze

1
@SqlKiwiこれがこの投稿を編集する方法であり、これが;-)にコメントする方法でした
jcolebrand
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.