2017年6月28日水曜日

MySQLのauto_incrementは実質ULLONG_MAX - 2で打ち止まるはなし

TL;DR


とある事情があってInnoDBのAUTO_INCREMENT関連のコードを読んでいた時に、innobase_next_autoincの中でこんなコードになっていることに気が付いた。
 2497         if (block >= max_value
 2498             || offset > max_value
 2499             || current >= max_value
 2500             || max_value - offset <= offset) {
 2501
 2502                 next_value = max_value;
 2503         } else {
いわゆるフツーのオートインクリメント処理の前段に入るチェックで、現在のauto_incrementの値にauto_increment_incrementの値を足してもデータ型の限界を超えないかとかそんなチェックをしているんだけれど、どうやら 足した結果データ型の限界を超える場合はデータ型の限界値を返す らしい。
データ型の最大値が常に返るから、ぶち抜いた時のエラーは ERROR 1062 (23000): Duplicate entry '..' for key 'PRIMARY' になるのか。ふむふむ。
しかし流れで読んでいたhandler::update_auto_incrementの中にちょっと気になる記述を見つけた。
3589       if (nr == ULLONG_MAX)
3590         DBUG_RETURN(HA_ERR_AUTOINC_READ_FAILED);  // Mark failure
はて。
  • 「払い出されたauto_incrementの値がULLONG_MAXだったらHA_ERR_AUTOINC_READ_FAILED」
ということは
  • 「現在のauto_increment値がUULONG_MAX - 1の時にauto_incrementの払い出しをしようとすると、UULONG_MAXが払い出されるから(データ型上はまだ入るけど)エラー」
なのか?
mysql57 13> INSERT INTO t1 VALUES (18446744073709551614, 'BIGINT MAX - 1');
Query OK, 1 row affected (0.00 sec)

mysql57 13> SELECT * FROM t1;
+----------------------+----------------+
| num                  | val            |
+----------------------+----------------+
| 18446744073709551614 | BIGINT MAX - 1 |
+----------------------+----------------+
1 row in set (0.00 sec)

mysql57 13> INSERT INTO t1 VALUES (NULL, 'NEXT');
ERROR 1467 (HY000): Failed to read auto-increment value from storage engine

mysql57 13> SELECT * FROM t1;
+----------------------+----------------+
| num                  | val            |
+----------------------+----------------+
| 18446744073709551614 | BIGINT MAX - 1 |
+----------------------+----------------+
1 row in set (0.00 sec)
おおおホントだ入らない。 しかしデータ型としてはまだもう1つ行けるので、直接値を指定すれば入る。
mysql57 13> INSERT INTO t1 VALUES (18446744073709551615, 'BIGINT MAX');
Query OK, 1 row affected (0.01 sec)

mysql57 13> SELECT * FROM t1;
+----------------------+----------------+
| num                  | val            |
+----------------------+----------------+
| 18446744073709551614 | BIGINT MAX - 1 |
| 18446744073709551615 | BIGINT MAX     |
+----------------------+----------------+
2 rows in set (0.00 sec)
しかもこの時も、auto_incrementの値がBIGINT UNSIGNEDの最大値の18446744073709551615以上になるから18446744073709551615が返ってDuplicate Entryエラーになる前に、払い出しそのものに失敗した扱いでER_AUTOINC_READ_FAILEDが返るのね。。
18446744073709551614と18446744073709551615の差が生死を分けることはないと思いますが、みなさまご注意ください。

2017年6月21日水曜日

MySQL 5.6のmysqldumpでMySQL 5.7のサーバーに接続してダンプを取ろうとするとコア吐く件

TL;DR

  • MySQL 5.7.12とそれ以降のmysqldumpなら大丈夫
  • 実はバージョン依存ではなく sql_mode=ONLY_FULL_GROUP_BY 依存

別のものを調べている時に /var/log/messages を見てたら、なんかがコア吐いてるのに気が付いた。
Jun 20 04:26:42 archive_host kernel: mysqldump[28611]: segfault at 0 ip 00000030b152859a sp 00007fffd760a358 error 4 in libc-2.12.so[30b1400000+18b000]
Jun 20 04:26:42 archive_host abrtd: Directory 'ccpp-2017-06-20-04:26:42-28611' creation detected
Jun 20 04:26:42 archive_host abrt[28613]: Saved core dump of pid 28611 (/data01/mysqlbin/mysql-5.6.20-linux-glibc2.5-x86_64/bin/mysqldump) to /var/spool/abrt/ccpp-2017-06-20-04:26:42-28611 (2609152 bytes)
Jun 20 04:26:42 archive_host kernel: mysqldump[28615]: segfault at 0 ip 00000030b152859a sp 00007fffbd02f9d8 error 4 in libc-2.12.so[30b1400000+18b000]
Jun 20 04:26:42 archive_host abrt[28617]: Not saving repeating crash in '/data01/mysqlbin/mysql-5.6.20-linux-glibc2.5-x86_64/bin/mysqldump'
Jun 20 04:27:12 archive_host abrtd: 電子メールを送信しています... 
Jun 20 04:27:12 archive_host abrtd: 電子メールが送信されました: root@localhost
Jun 20 04:27:13 archive_host abrtd: Duplicate: UUID
Jun 20 04:27:13 archive_host abrtd: DUP_OF_DIR: /var/spool/abrt/ccpp-2017-01-31-00:25:08-30738
Jun 20 04:27:13 archive_host abrtd: Deleting problem directory ccpp-2017-06-20-04:26:42-28611 (dup of ccpp-2017-01-31-00:25:08-30738)
Jun 20 04:27:13 archive_host abrtd: No actions are found for event 'notify-dup'
mysqldumpさんがSEGVしてコア吐いてる。 しかもなんか今年1月から出てるみたいでDUP_OF_DIRとか言われた(こんな機能あったんですがabrtさん)
$ pwd
/var/spool/abrt/ccpp-2017-01-31-00:25:08-30738

$ file coredump
coredump: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from '/usr/local/mysql56/bin/mysqldump --user=dev_backup --host=172.19.140.61 --port='
開発環境のダンプを取るmysqldumpだった。パスからわかるようにmysqldumpはMySQL 5.6のもの。
$ gdb /usr/local/mysql56/bin/mysqldump coredump
..
(gdb) bt
#0  0x00000030b152859a in __strcmp_sse42 () from /lib64/libc.so.6
#1  0x000000000040e90f in dump_tablespaces (ts_where=0x0)
    at /export/home/pb2/build/sb_0-12734909-1405700492.82/mysql-5.6.20/client/mysqldump.c:4026
#2  0x0000000000416312 in dump_all_tablespaces (argc=0, argv=0x246f1a8)
    at /export/home/pb2/build/sb_0-12734909-1405700492.82/mysql-5.6.20/client/mysqldump.c:3893
#3  main (argc=0, argv=0x246f1a8) at /export/home/pb2/build/sb_0-12734909-1405700492.82/mysql-5.6.20/client/mysqldump.c:5853
刺さったのは ここ
直前で初期化してる buf[0] がマズいとは考えにくいので、その手前 で組み立てられているクエリーを眺めてみる。
…あれ、俺これなんか見たことあるような気がするぞ………?
$ /usr/local/mysql56/bin/mysql --user=xxx --host=172.19.140.61 --port=3309 --execute="SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE ORDER BY LOGFILE_GROUP_NAME"

ERROR 1055 (42000) at line 1: Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'information_schema.FILES.TOTAL_EXTENTS' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
うん、 sql_mode=ONLY_FULL_GROUP_BY にやられて転けるやつ。
しかしなんでこれが mysql_query(mysql, sqlbuf.str) で0(=成功) 以外が返ってifの中に落ち込まないのかがよくわからない。
(gdb) p mysql->net.last_errno
$4 = 0
なんなんだろうなあ。。 しかしこれどっかで調べた気がするんだよなあ。。どこだったっけなあ。。