web-dev-qa-db-ja.com

MySQLのinnodbでのマルチカラムインデックスによる奇妙なデッドロック

MySQLで奇妙な(私にとって)動作が見られます。私が話しているテーブルから始めましょう。

CREATE TABLE `active_foo` (
  `active_foo_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c_id` bigint(20) NOT NULL,
  `aa_id` bigint(20) NOT NULL,
  `bar` varchar(60) DEFAULT NULL,
  `foo_string` varchar(60) NOT NULL,
  `handle_id` bigint(20) NOT NULL DEFAULT '-1',
  `hostname` varchar(64) DEFAULT NULL,
  `usage` int(11) DEFAULT NULL,
  `foo_id` int(11) NOT NULL,
  `bucket_id` int(11) NOT NULL,
  PRIMARY KEY (`active_foo_id`),
  KEY `idx_active_foo_foo_config` (`foo_id`),
  KEY `idx_active_foo_aa` (`aa_id`),
  KEY `foo_index` (`c_id`,`foo_id`,`foo_string`),
  KEY `idx_active_foo_buckets1` (`bucket_id`),
  KEY `handle_idx` (`handle_id`),
  KEY `Host_idx` (`hostname`),
  CONSTRAINT `fk_active_foo_aa_idx` FOREIGN KEY (`aa_id`) REFERENCES `aa` (`access_id`) ON DELETE CASCADE,
  CONSTRAINT `fk_active_foo_buckets1` FOREIGN KEY (`bucket_id`) REFERENCES `foo_buckets` (`bucket_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_active_foo_foo_config` FOREIGN KEY (`foo_id`) REFERENCES `foo_config` (`foo_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

次のような2つのインサートがあります。

insert into active_foo(c_id, foo_string, `usage`, foo_id, bucket_id, aa_id) 
values(6, '#TAG', 1, 1, 2, 3268192)

そして

insert into active_foo(c_id, foo_string, `usage`, foo_id, bucket_id, aa_id) 
values(7, '#TAG', 1, 1, 1, 3268193)

どういうわけか、それらは同じインデックスでデッドロックしているように見えますが、デッドロックが発生するのは、一方が保持し、もう一方が待機している2番目のインデックスが存在する場合に限られるため、困惑します。最新のデッドロックレポートは、実際には同じインデックスで待機していることを示しているようです。デッドロックではなく、わずかな遅延が発生すると考えられます。

180904 12:32:02
*** (1) TRANSACTION:
TRANSACTION 34018365, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 23 lock struct(s), heap size 3112, 17 row lock(s), undo log entries 3
MySQL thread id 459744, OS thread handle 0x2b42d5470700, query id 603275321     someotherip myuser update
insert into active_foo(c_id, foo_string, `usage`, foo_id, bucket_id, aa_id) values(6, '#TAG', 1, 1, 2, 3268192)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 60 page no 4 n bits 152 index `foo_index` of table `mydb`.`active_foo` trx id 34018365 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 34 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 80000000000000b7; asc         ;;
 1: len 4; hex 80000001; asc     ;;
 2: len 23; hex 23544147; asc #TAG;;
 3: len 8; hex 800000000005b184; asc         ;;

*** (2) TRANSACTION:
TRANSACTION 3401837B, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
21 lock struct(s), heap size 3112, 13 row lock(s), undo log entries 3
MySQL thread id 460530, OS thread handle 0x2b415579b700, query id 603275331 someip myuser update
insert into active_foo(c_id, foo_string, `usage`, foo_id, bucket_id, aa_id) values(7, '#TAG', 1, 1, 1, 3268193)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 60 page no 4 n bits 152 index `foo_index` of table `mydb`.`active_foo` trx id 3401837B lock_mode X locks gap before rec
Record lock, heap no 34 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 80000000000000b7; asc         ;;
 1: len 4; hex 80000001; asc     ;;
 2: len 23; hex 23544147; asc #TAG;;
 3: len 8; hex 800000000005b184; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 60 page no 4 n bits 152 index `foo_index` of table `mydb`.`active_foo` trx id 3401837B lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 34 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 8; hex 80000000000000b7; asc         ;;
 1: len 4; hex 80000001; asc     ;;
 2: len 23; hex 23544147; asc #TAG;;
 3: len 8; hex 800000000005b184; asc         ;;

*** WE ROLL BACK TRANSACTION (2)

foo_indexはトランザクション2によって保持されるロックであるだけでなく、付与されるのを待機しているロックでもあることに注意してください。特に最新のデッドロックレポートに基づいてどの行がロックされているかはわかりません。どちらの場合も、foo_string列の#TAGが含まれていることがわかります。 1つのロックはc_id 6用で、もう1つのロックはc_id 7用であると思います。

これが、特に複数列のインデックスに関して、インデックスのロックが正確にどのように機能するかについての私の理解に疑問を投げかけるところです。ロックする行を決定するために使用される列の順序は未定義ですか?たとえば、次のインデックスの場合:

KEY `foo_index` (`c_id`,`foo_id`,`foo_string`)

1つの挿入は、最初に左から右に行く列に一致する行をロックし、2番目の挿入は、右から左に行く列に一致する行をロックしますか? foo_string #TAGは、あるクエリでは最初にロックされ、別のクエリでは最後にロックされるため、デッドロックが発生する可能性があります。単にロックし、待機し、次に進むべきだと思われるときに、なぜ私がデッドロックしているのかに関するアイデアはありますか?

2
VaultNerd
lock_mode X locks gap before rec insert 

そこから引き出すキーワードは「ロックのギャップ」です。 MySQLには、ページ内のインデックスレコード間のギャップをロックするGap Locksがあります。 2番目のレコードは最初のレコードの隣に挿入されているため、どちらも同じgap lockで待機しています。

https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/

ギャップロックは、インデックスレコード間のギャップのロックです。このギャップロックのおかげで、同じクエリを2回実行すると、そのテーブルの他のセッションの変更に関係なく、同じ結果が得られます。これにより、読み取りの一貫性が保たれるため、サーバー間のレプリケーションの一貫性が保たれます。 SELECT * FROM id> 1000 FOR UPDATEを2回実行すると、同じ値が2回取得されることになります。これを実現するために、InnoDBはWHERE句によって検出されたすべてのインデックスレコードを排他ロックでロックし、それらの間のギャップを共有ギャップロックでロックします。

あなたがこれらを経験しているのはあなただけではありません: MySql Gap Lock Deadlock on Inserts 。その上で実際の解決策はありません。

2
Kevin Bott

MySQLは(8.0で)データロックを検査するための新しいperformance_schemaテーブルを提供します。

テーブルを確認してくださいdata_locksおよびdata_lock_waits、これらは、各セッションがロックして待機する内容をより正確に説明します。

https://dev.mysql.com/doc/refman/8.0/en/data-locks-table.html

https://dev.mysql.com/doc/refman/8.0/en/data-lock-waits-table.html

1
Marc Alff