2017年2月13日月曜日

mysqlimportはトランザクションがきくのかどうか

TL;DR

- 1テキストファイル内でのトランザクションは利く
- 複数テキストファイル食わせた時のテキストファイル間のトランザクションは autocommit 依存 効かない
  - というか、 autocommit=0 だと mysqlimport さん使えないことが判明
  - ただし --use-threads を指定していない場合に限る(使ってる場合はそもそも別のトランザクションとしてパラレルで実行される)


今は英語化した MySQL CasualのSlack でそんな話題があったから調べてみた。

ざっと mysqlimportのソース を追ってみたけど、なんかどうもトランザクションをハンドルしている箇所はなさげ。ということはコマンドラインクライアントで LOAD DATA INFILE する時と同じになるのかな?

というわけでここからテスト。

まずは準備。t1.txtとt2.txtをそれぞれ datadir/d1 の下に作る。
中身は何でもいい。

$ cat t1.txt
1       one
2       two

$ cat t2.txt
1       one
2       two


main関数 を読む限り、 --use-threads の指定がない場合は左から順に引数を読んで LOAD DATA INFILE ステートメントに変換するので、t2の方をロックしてやればいいはず。

mysql57> SELECT @@session.autocommit, @@global.autocommit;
+----------------------+---------------------+
| @@session.autocommit | @@global.autocommit |
+----------------------+---------------------+
|                    1 |                   1 |
+----------------------+---------------------+
1 row in set (0.00 sec)

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

mysql57> SELECT * FROM t2 FOR UPDATE;
Empty set (0.00 sec)


これで t1にはロードできるけどt2にはロードできない 状態になったので、別のターミナルからmysqlimportを実行。

$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0


ターミナルはここでハングする(t2の行ロック待ちで)
Ctrl + Cで終了して、さっきのターミナルに戻る。

mysql57> show processlist;
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
| Id | User | Host      | db   | Command | Time | State     | Info                                                       |
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
| 77 | root | localhost | d1   | Query   |    0 | starting  | show processlist                                           |
| 86 | root | localhost | d1   | Query   |   11 | executing | LOAD DATA   INFILE 't2.txt' INTO TABLE `t2` IGNORE 0 LINES |
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
2 rows in set (0.00 sec)


ハマりどころその1。
mysqlimportのプロセスを終了しても、mysqldの中のスレッドは残ったままだったので、コイツをKILLする前にロックを解除すると

(゜∀。) あれなんでロードされてんの?

ってなる。

mysql57> KILL 86;
Query OK, 0 rows affected (0.00 sec)

mysql57> COMMIT; -- REPEATABLE-READをリフレッシュするためにコミット
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT * FROM t1;
+-----+------+
| num | val  |
+-----+------+
|   1 | one  |
|   2 | two  |
+-----+------+
2 rows in set (0.00 sec)

mysql57> SELECT * FROM t2;
Empty set (0.00 sec)


予想通り、t1に対するLOAD DATA INFILE, オートコミット, t2に対するLOAD DATA INFILE, 行ロック待ちの間にKILL、でt1だけにデータがロードされる。
一度t1とt2をTRUNCATEして次。

mysql57> SELECT @@session.autocommit, @@global.autocommit;
+----------------------+---------------------+
| @@session.autocommit | @@global.autocommit |
+----------------------+---------------------+
|                    0 |                   0 |
+----------------------+---------------------+
1 row in set (0.00 sec)

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

mysql57> SELECT * FROM t2 FOR UPDATE;
Empty set (0.01 sec)


$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0


mysql57> show processlist;
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
| Id | User | Host      | db   | Command | Time | State     | Info                                                       |
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
| 87 | root | localhost | d1   | Query   |    0 | starting  | show processlist                                           |
| 88 | root | localhost | d1   | Query   |   16 | executing | LOAD DATA   INFILE 't2.txt' INTO TABLE `t2` IGNORE 0 LINES |
+----+------+-----------+------+---------+------+-----------+------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql57> kill 88;
Query OK, 0 rows affected (0.00 sec)

mysql57> commit;
Query OK, 0 rows affected (0.00 sec)

mysql57> SELECT * FROM t1;
Empty set (0.00 sec)

mysql57> SELECT * FROM t2;
Empty set (0.00 sec)

autocommit= 0なので複数のLOAD DATA INFILEが全部1つのトランザクションとして扱われる。
なるほど。


…ここでなんか違和感を感じる。アレ?

ざっと mysqlimportのソース を追ってみたけど、なんかどうもトランザクションをハンドルしている箇所はなさげ。ということはコマンドラインクライアントで LOAD DATA INFILE する時と同じになるのかな?


ん? autocommit=0 でコマンドラインクライアントからLOAD DATA INFILE投げて、commitせずにquitしたらデータ残らなくね?


$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0
d1.t2: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0
d1.t2: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0
d1.t2: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0
d1.t2: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

$ mysqlimport -S /usr/mysql/5.7.17/data/mysql.sock d1 t1.txt t2.txt
d1.t1: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0
d1.t2: Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

:(;゙゚'ω゚'): 残ってない…残ってたら

mysqlimport: Error: 1062, Duplicate entry '1' for key 'num', when using table: t1

って言われるから…autocommit=0だと本当に残ってない…

0 件のコメント :

コメントを投稿