ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

MySQLのXAトランザクションについて調べていた(1)

ドキュメント

英語読めないから5.1の日本語ドキュメントも併用して読んでいる。

検証環境

  • MySQL 5.6.22
  • REPEATABLE-READ

使い方

ゆるふわWebアプリケーションエンジニアなもんで、幸か不幸か今までのエンジニア人生で複数DB環境(シャーディング/マスタDB分散)に遭遇したことがない。なのでXAの使い方をドキュメントから読み取っていく。

XAトランザクションの流れ

普通のトランザクションではBEGINで始めるが、XAトランザクションの場合は

mysql> XA START 'xid';

で始める。

で、このxidってやつは何なのかというと、上記ドキュメントでは

xid: gtrid [, bqual [, formatID ]]

とあり、gtridっていうのが必須らしい。グローバルトランザクション識別子というやつなのだけれど、かぶらなければ適当にクライアントで生成してもよいっぽい? uuidでも突っ込んでおくのかな。

とりあえずサンプルでは'xatest'なんていう超適当っぽい文字列を突っ込んでいるのでそんな感じでよしなにやってく。

さてXA START 'xid'した状態はそのトランザクションACTIVEと呼ばれる状態になる。

mysql> XA START 'xatest1';
Query OK, 0 rows affected (0.00 sec)

そこからSELECTだのUPDATEだのINSERTだののSQLを発行しXA END 'xid'なるSQLを発行する。

mysql> INSERT INTO user (name, status, created_at, updated_at) VALUES ("macopy", 1, NOW(), NOW());
Query OK, 1 row affected (0.00 sec)
mysql> XA END 'xatest1';                                                                                                    │
Query OK, 0 rows affected (0.00 sec)

するとトランザクションIDLEと呼ばれる状態になる。 ただしこの時点ではまだ別のトランザクションからは変更が行われていない。そうなるにはここからまだ2段階ほどステップを踏む必要がある。 XA PREAPRE 'xid'というSQLを発行する。すると、PREPAREという状態になる。

mysql> XA PREPARE 'xatest1';                                                                                                │
Query OK, 0 rows affected (0.00 sec)

すると別のトランザクションからもこのトランザクションは反映待ちというような感じで見ることが出来る。これにはXA RECOVERというSQLを用いる。

mysql> XA RECOVER;
+----------+--------------+--------------+---------+
| formatID | gtrid_length | bqual_length | data    |
+----------+--------------+--------------+---------+
|        1 |            7 |            0 | xatest1 |
+----------+--------------+--------------+---------+
1 row in set (0.00 sec)

ただしまだこの状態ではコミットは行われていない。仕上げにXA COMMIT 'xid'とする。

mysql> XA COMMIT 'xatest1';

XA RECOVER;のリストからは消えて、トランザクション内で行われたデータベースに対する変更がコミットされる。これでトランザクションは終わりだ。

疑問点いろいろ

XAトランザクション中の制限

まずXAトランザクション内でBEGINしようとしてみる。

mysql> XA START 'xatest1';
Query OK, 0 rows affected (0.00 sec)

mysql> BEGIN;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the  ACTIVE state

はい、ごめんなさいという感じである。 じゃあBEGINってどこまで出来ないのかというと、結局XA {COMMIT|ROLLBACK} 'xid'するまで無理なようである。

mysql> XA END 'xatest1';
Query OK, 0 rows affected (0.05 sec)

mysql> BEGIN;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the  IDLE state
mysql> XA PREPARE 'xatest1';
Query OK, 0 rows affected (0.00 sec)

mysql> BEGIN;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the  PREPARED state
mysql> XA COMMIT 'xatest1';
Query OK, 0 rows affected (0.00 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

なお、XA ROLLBACKとは普通のトランザクションで言うROLLBACKのことなんですけれど、こいつはACTIVEでは発行できない。XA END 'xid'IDLEにしてトランザクションから一旦抜けてからでないと出来ない。

また、ACTIVEだと「暗黙のコミットを引き起こすステートメント」は実行できないとのこと。なにそれってなりますが、主に我々に関係するのはDDLであったりTRUNCATE文です。

mysql> XA START 'xatest1';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE t1 (id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY);
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the  ACTIVE state

いわゆるMySQLにおけるトランザクション内に入れても即コミットされちゃうやつ。

あとそれから、普通のトランザクションを始めた後にXAを始めようとするのも怒られる。

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> XA START 'xatest2';
ERROR 1400 (XAE09): XAER_OUTSIDE: Some work is done outside global transaction

なお、他のコネクションもXAを始めた後はXAではないといけないのではと思いましたが、そんなことはありませんでした。

PKでぶつからせる

以下の例ではコネクションの区別をTA/TBとして2つのコネクションがあるケースを扱っていく。

スキーマ

mysql> DESC t1;
+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
+-------+------------------+------+-----+---------+----------------+
1 row in set (0.03 sec)

以下は時系列順

TA> XA START 'xatest1';
Query OK, 0 rows affected (0.00 sec)

TB> XA START 'xatest2';
Query OK, 0 rows affected (0.00 sec)

TA> INSERT INTO t1 (id) VALUES (1);
Query OK, 1 row affected (0.02 sec)

TB> INSERT INTO t1 (id) VALUES (1); # ブロックされる

TA> XA END 'xatest1';
Query OK, 0 rows affected (0.00 sec)

TA> XA PREPARE 'xatest1';
Query OK, 0 rows affected (0.00 sec)

TA> XA COMMIT 'xatest1';
Query OK, 0 rows affected (0.00 sec)

TB>
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' # ブロックされていたのがここで解放

とまあXA COMMITするまで待たされる。普通のトランザクションと同じような挙動である。 なお、ROLLBACK時の挙動はROLLBACKした瞬間に開放される。IDLEになったあとはいつでもROLLBACK可能なので、まあそうなるか。

さてここまでは普通のトランザクションがめんどくさくなっただけな感じだったが、次からはデッドロックとかその辺りの挙動を見て行きたい。普通のトランザクションとどう違うのかが知りたい。