web-dev-qa-db-ja.com

INSERTとSELECTの同時実行によるMySQLのデッドロック

  • MySQLバージョン:5.6
  • ストレージエンジン:InnoDB

デッドロックは、2つのタスクが同じテーブルをselectしてからinsertしようとしたときに発生しました。手順は次のようになります。

          Task_1       Task_2
          ------      ------
Phase 1 | SELECT      SELECT
Phase 2 | INSERT      INSERT

SELECT count(id) from mytbl where name = 'someValue' and timestampdiff(hour, ts, now()) < 1;
INSERT mytbl (id, name, ts) values ('newId', 'anotherValue', now());

デッドロックログは次のとおりです(一部の詳細は切り捨てられています)。

------------------------
LATEST DETECTED DEADLOCK
------------------------
151225  8:22:17
*** (1) TRANSACTION:
TRANSACTION 0 746402, ACTIVE 0 sec, process no 4690, OS thread id 140411390486272 inserting
mysql tables in use 1, locked 1
LOCK WAIT 1172 lock struct(s), heap size 112624, 32914 row lock(s)
MySQL thread id 3909, query id 31751474 10.20.36.38 mydb update
INSERT INTO mytbl -- truncated
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`mytbl` trx id 0 746402 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 0 746449, ACTIVE 0 sec, process no 4690, OS thread id 140411389953792 inserting, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
1172 lock struct(s), heap size 112624, 32914 row lock(s)
MySQL thread id 3906, query id 31751477 10.20.36.38 mydb update
INSERT INTO mytbl  -- truncated
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`MYTBL` trx id 0 746449 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 5044 n bits 88 index `PRIMARY` of table `MYDB`.`MYTBL` trx id 0 746449 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)

質問

  1. MySQLのマニュアルによると、単純なSELECTステートメントはスナップショット読み取りを使用します。これはSロックを必要としません。 INSERTステートメントでは、挿入する単一の行にX lockが必要です。それでなんで Task_2Sロックを保持し、デッドロックが発生しましたか?

編集

結果として SHOW CREATE TABLEは次のとおりです。

| task_content | CREATE TABLE `mytbl` (
`id` bigint(20) NOT NULL,
`ts` timestamp NULL DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
16
Zelong

現在の分離レベルが_repeatable read_以上の場合、トランザクション内でselect count(id) ...に対して同じ結果を繰り返すことができるようにするには、MySQLは主キー全体(またはによって使用される別のキーの一部)をロックする必要がありますWHERE条件)。次に、新しい値を挿入してキーを変更します。しかし、並行トランザクションは、すでに見られているキーの状態を変更します。どちらも同じキーの状態で開始し、もう一方が変更なしで終了するまで待機して、独自の変更を適用することができます。

7
newtover

記事 ここ は、ロックと分離レベルについて徹底的に説明しています。

分離レベルについての手がかりを与えてくれた@newtoverに感謝します。記事の要約と私自身の質問への回答は次のとおりです。

InnoDBのデフォルトの分離レベルはRepeatable Readで、トランザクションが終了するまでインデックスをロックします(データテーブルはロックしません)

私の状況では、唯一のインデックスはPRIMARYであり、これは私のSELECTクエリでは役に立ちませんでした(explain select...で確認できます)。その結果、PRIMARYインデックスのallエントリがロックされました。 TXN_2が特定のエントリでXロックを待機したとき、エントリはSロックによってロックされました。 TXN_1。同様に、TXN_1は別のエントリでXロックを待機しましたが、エントリはSロックによってロックされました。自体。 A "one S two X" デッドロックが発生しました。

対照的に、列nameにインデックスnameを作成した後、インデックスnameSELECTステートメントで使用されるため(explain select ...で確認できます)、ロックはnameではなくインデックスPRIMARYで発行されます。さらに重要なことに、SELECTステートメントは、インデックスsomeValueのすべてのエントリではなく、nameに等しいエントリに対してSロックのみを発行します。さらに、INSERTに必要なIXロックおよびXロックは、インデックスPRIMARYで発行されます。 SロックIXロックXロックの間の競合は解決されます。

nameのインデックスは、クエリを高速化するだけでなく、さらに重要なことに、インデックスのすべてのエントリをロックすることを防ぎました。

9
Zelong

ここで、name = 'someValue'およびtimestampdiff(hour、ts、now())<1;

それはかなり非効率的です。デッドロックの可能性を減らすために、物事をスピードアップするためにそれをクリーンアップしましょう。

timestampdiff(hour, ts, now()) < 1は、tsでインデックスを非表示にします。に書き直してみましょう

_ts < NOW() - INTERVAL 1 HOUR
_

予期しない方法で切り捨てられました。私は「1時間以上前」と言っていますが、あなたが望んでいたのではないかと思います。

これで、tsにインデックスを付けて効果を上げることができます。しかし、「複合」インデックスを使用して、それをさらに進めましょう。

_INDEX(name, ts)
_

これにより、行を見つけるためにWHERE句の両方の部分が効率的に使用されます。

あなたはCOUNT(id)と言います-これは、NULLsidを避ける必要があることを意味します。おそらくそれは問題ではなく、単にCOUNT(*)と言うことができます。

これらはSELECTをより速くするはずです。ここで、そのSELECTINSERTが互いに関係がある理由を理解しましょう。それらは同じトランザクションにありますか?または、自動コミットをオフにしましたが、COMMITと言うのを忘れましたか?トランザクション全体と_SHOW CREATE TABLE_を表示してください。

7
Rick James

すべてのクエリを[〜#〜] begin [〜#〜]および[〜#〜] endに記述します[〜#〜]トランザクション。私はそれが起こらないことを望みます。

詳細: ここ

1
Monty

クエリの記述部分は正しいようであり、間違いなく問題の原因ではありません。あなたのタスクはインターリーブを実行していて、最初に各タスクがトランザクションを開始していると思います。これらのタスクを実行する方法と、各タスクを実行するときの主キーの値については何も言わなかったのですか?主キーフィールドをAUTO_INCREMENTに変更するか、タスクで使用される主キーが本当に一意であることを確認することをお勧めします。
それが役に立たなかった場合、代替の(しかし提案されていない)解決策は、mutextによって上位レベルのコードを呼び出すプロシージャを保護することです。

0