mita2 database life

主にMySQLに関するメモです

Group Replication を試す(2) ロックの挙動

テスト用のテーブル

mysql> CREATE TABLE grplt.tbl (pk SERIAL, col1 int, who_update varchar(10));
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO grplt.tbl (col1, who_update) VALUES (1, 'node1') , (2, 'node1'), (3, 'node1');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM grplt.tbl;
+----+------+------------+
| pk | col1 | who_update |
+----+------+------------+
| 1 | 1 | node1 |
| 8 | 2 | node1 |
| 15 | 3 | node1 |
+----+------+------------+
3 rows in set (0.00 sec)

いつものデッドロック

同じサーバに対して、入れ子の行ロックを取る2つのトランザクションを実行するパターン。
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction が発生し、発生したデッドロックの情報は SHOW ENGINE INNODB STATUS で確認できる。

TIME T1 T2
1 > BEGIN;
Query OK, 0 rows affected (0.00 sec)

> UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
2 > BEGIN;
Query OK, 0 rows affected (0.00 sec)

> UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 15;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
3 T2 が pk = 15 を開放するのを待つ。
> UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 15;
4 T2 が pk = 1 を要求したため、T1は強制ロールバックしpk = 1 を開放
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
> UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SHOW ENGINE INNODB STATUS\G
------------------------
LATEST DETECTED DEADLOCK
------------------------
2016-12-29 16:02:59 0x7f539018a700
*** (1) TRANSACTION:
TRANSACTION 6102, ACTIVE 42 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 3838, OS thread handle 139996877612800, query id 11406 localhost root updating
UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 15
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 72 index pk of table `grplt`.`tbl` trx id 6102 lock_mode X locks rec but not gap waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 8; hex 000000000000000f; asc ;;
1: len 6; hex 0000000017d7; asc ;;
2: len 7; hex 30000001880233; asc 0 3;;
3: len 4; hex 8000000a; asc ;;
4: len 5; hex 6e6f646531; asc node1;;
*** (2) TRANSACTION:
TRANSACTION 6103, ACTIVE 9 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 3841, OS thread handle 139996876547840, query id 11407 localhost root updating
UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 1

Group Replication でサーバ間でロックが競合

Group Replication で複数のサーバで同時に同じレコードを更新した場合の挙動。 サーバ間のロックのアルゴリズムは「楽観的ロック」になっている。
コミット時に、そのレコードが他のサーバで既に更新済みだった場合、そのトランザクションは強制ロールバックされる。
要するに「先勝ち」

pk = 1 のレコードを別々のサーバで更新。エラーメッセージが異なり通常のデッドロックと区別することができる。
※ ちなみに、Galera Cluster の場合、通常のデッドロックと、複数サーバ間で競合しロールバックされた場合で同じエラーメッセージが出力されるため区別が付かない。

しかし、もう少しわかりやすいメッセージにできなかったんだろうか。
SHOW ENGINE INNODB STATUS に情報は出力されない。

TIME T1 T2
1 > BEGIN;
Query OK, 0 rows affected (0.00 sec)

> UPDATE grplt.tbl SET col1 = 10, who_update = 'node1' WHERE pk = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
2 > BEGIN;
Query OK, 0 rows affected (0.00 sec)

> UPDATE grplt.tbl SET col1 = 10, who_update = 'node2' WHERE pk = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
3 pk = 1 の行をノード1で更新確定する
> COMMIT;
Query OK, 0 rows affected (0.00 sec)
(この時点でT1が更新したことが伝わってくる)
4 > COMMIT;
ERROR 1180 (HY000): Got error 149 during COMMIT
mysql> pager grep DEAD
PAGER set to 'grep DEAD'

mysql> SHOW ENGINE INNODB STATUS\G
1 row in set (0.00 sec)