Quantcast
Channel: 日々の覚書
Viewing all 581 articles
Browse latest View live

MySQL Casual Talks vol.10でトークしてきたMySQL的アンチパターン

$
0
0
昨夜の MySQL Casual Talks vol.10 は盛り上がりましたね!

総勢10名の登壇者ズがおよそ3時間にわたって繰り広げた **カジュアルな** トークの模様は↓にまとめました。

MySQL Casual Talks vol.10まとめ - Togetterまとめ


で、俺のヤーツは久々に(?) 愚痴っぽい「これをやるとMySQLは死ぬ」あるいは「これをやるとMySQLerが疲弊する」みたいなのをまとめたやつでした。

ちなみにこれ去年の新卒研修で使った資料を 90% くらいリライトしたやつで(リライト #とは)、もともとはもうちょっと本来のSQLアンチパターンっぽい項目が多かったはず。




ちょくちょく笑っていただけたようで何よりです。

MySQL Casual Talksに参加するような人自身はこんなことしないかも知れませんけど、油断していると足元から火が付いたりするので啓蒙活動にご活用ください。





ほんとだよ!!! _| ̄|○

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

$
0
0

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だと本当に残ってない…

MariaDB 10.2.4の --flashback を触ってみる

$
0
0
ドキュメントはこちら。
Flashback - MariaDB Knowledge Base

"Common use case"をとっても雑に説明すると、
- `--flashback` をつけたmysqldが吐いたバイナリーログに対して
- `mysqlbinlog --flashback` でデコードすると、フラッシュバックっぽいことができる
という感じ。


まず、サーバー側の `--flashback` について。

https://github.com/MariaDB/server/blob/mariadb-10.2.4/sql/mysqld.cc#L9541-L9551

binlog_format= ROWにセットしてくれるだけぽい。 `--flashback` じゃなくても `--log-bin --binlog_format=ROW --binlog-row-format=FULL` で吐かせたバイナリーログでも大丈夫だった。ので、特に気にしなくて良さそう。


mysqlbinlog側の `--flashback` が肝。

https://github.com/MariaDB/server/blob/mariadb-10.2.4/client/mysqlbinlog.cc#L1455-L1468

↑で順番にイベントを読み込んでバッファに入れておいたものを(この時点でchange_to_flashback_eventを呼んでフラッシュバック用にクエリーを書き換えているぽい)
↓で後ろから順番に取り出す。

https://github.com/MariaDB/server/blob/mariadb-10.2.4/client/mysqlbinlog.cc#L3019-L3034

バイナリーログのイベントを逆転させてるのは↓のあたり。

https://github.com/MariaDB/server/blob/mariadb-10.2.4/sql/log_event.cc#L3417-L3460


というわけで、


MariaDB [(none)]> use d2;
MariaDB [d2]> create table t2 (num serial, val varchar(32));
MariaDB [d2]> INSERT INTO t2 VALUES (1, 'eins');
MariaDB [d2]> UPDATE t2 SET val = 'one' WHERE num = 1;

なんてことをやったbinlogを見ると、


C:\Users\yoku0825\Desktop\mariadb-10.2.4-winx64>bin\mysqlbinlog.exe -vv data\myhost-bin.000002
..
use `d2`/*!*/;
SET TIMESTAMP=1487580544/*!*/;
create table t2 (num serial, val varchar(32))
/*!*/;
# at 745
#170220 17:49:20 server id 1 end_log_pos 787 CRC32 0x3a401e2f GTID 0-1-9 trans

/*!100001 SET @@session.gtid_seq_no=9*//*!*/;
BEGIN
/*!*/;
# at 787
# at 843
#170220 17:49:20 server id 1 end_log_pos 843 CRC32 0xee91dd13 Annotate_rows:
#Q> INSERT INTO t2 VALUES (1, 'eins')
#170220 17:49:20 server id 1 end_log_pos 889 CRC32 0xfac503f9 Table_map: `d2`.`t2` mapped to number 23
# at 889
#170220 17:49:20 server id 1 end_log_pos 936 CRC32 0x9cc43e21 Write_rows: table id 23 flags: STMT_END_F

BINLOG '
kK2qWBMBAAAALgAAAHkDAAAAABcAAAAAAAEAAmQyAAJ0MgACCA8CIAAC+QPF+g==
kK2qWBcBAAAALwAAAKgDAAAAABcAAAAAAAEAAv/8AQAAAAAAAAAEZWlucyE+xJw=
'/*!*/;
### INSERT INTO `d2`.`t2`
### SET
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='eins' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
# at 936
#170220 17:49:20 server id 1 end_log_pos 967 CRC32 0x7e5f60b7 Xid = 9
COMMIT/*!*/;
# at 967
#170220 17:49:31 server id 1 end_log_pos 1009 CRC32 0x55d0a0bc GTID 0-1-10 trans
/*!100001 SET @@session.gtid_seq_no=10*//*!*/;
BEGIN
/*!*/;
# at 1009
# at 1071
#170220 17:49:31 server id 1 end_log_pos 1071 CRC32 0xb32de27c Annotate_rows:
#Q> UPDATE t2 SET val = 'one' WHERE num = 1
#170220 17:49:31 server id 1 end_log_pos 1117 CRC32 0x4ee231f4 Table_map: `d2`.`t2` mapped to number 23
# at 1117
#170220 17:49:31 server id 1 end_log_pos 1178 CRC32 0xe87e056f Update_rows: table id 23 flags: STMT_END_F

BINLOG '
m62qWBMBAAAALgAAAF0EAAAAABcAAAAAAAEAAmQyAAJ0MgACCA8CIAAC9DHiTg==
m62qWBgBAAAAPQAAAJoEAAAAABcAAAAAAAEAAv///AEAAAAAAAAABGVpbnP8AQAAAAAAAAADb25l
bwV+6A==
'/*!*/;
### UPDATE `d2`.`t2`
### WHERE
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='eins' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
### SET
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='one' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
# at 1178
#170220 17:49:31 server id 1 end_log_pos 1209 CRC32 0xdd417c80 Xid = 10

COMMIT/*!*/;
..

これが、 `--flashback` をつけるとこうなる。


C:\Users\yoku0825\Desktop\mariadb-10.2.4-winx64>bin\mysqlbinlog.exe --flashback -vv data\myhost-bin.000002
..

BEGIN/*!*/;
#170220 17:49:31 server id 1 end_log_pos 1178 CRC32 0xe87e056f Update_rows: table id 23 flags: STMT_END_F

BINLOG '
m62qWBMBAAAALgAAAF0EAAAAABcAAAAAAAEAAmQyAAJ0MgACCA8CIAAC9DHiTg==
m62qWBgBAAAAPQAAAJoEAAAAABcAAAAAAAEAAv///AEAAAAAAAAAA29uZfwBAAAAAAAAAARlaW5z
bwV+6A==
'/*!*/;
### UPDATE `d2`.`t2`
### WHERE
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='one' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
### SET
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='eins' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
COMMIT
/*!*/;
#170220 17:49:20 server id 1 end_log_pos 967 CRC32 0x7e5f60b7 Xid = 9
BEGIN/*!*/;
#170220 17:49:20 server id 1 end_log_pos 936 CRC32 0x9cc43e21 Delete_rows: table id 23 flags: STMT_END_F

BINLOG '
kK2qWBMBAAAALgAAAHkDAAAAABcAAAAAAAEAAmQyAAJ0MgACCA8CIAAC+QPF+g==
kK2qWBkBAAAALwAAAKgDAAAAABcAAAAAAAEAAv/8AQAAAAAAAAAEZWlucyE+xJw=
'/*!*/;
### DELETE FROM `d2`.`t2`
### WHERE
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='eins' /* VARSTRING(32) meta=32 nullable=1 is_null=0 */
COMMIT
/*!*/;
..

`--flashback` の方は時間降順に並んでいるのがちょっと面白い。
これで、`--start-datetime` で時間を指定してやれば、それ以降のDMLをフラッシュバック(個人的にはリバートと呼びたい)するためのDMLが手に入って、それを適用すればフラッシュバック(個人的には略)完了。

RBRだからそれなりには時間がかかるはず。やっぱリバートで良いじゃん。。




めいじさんエスパー! :)



この辺( `--review` みたいなのがある)は後々なのかな(このマクロが有効化されてる箇所は見当たらなかった)

https://github.com/MariaDB/server/blob/mariadb-10.2.4/client/mysqlbinlog.cc#L1587-L1599

OSC 2017 Tokyo/Spring でMySQL 8.0を雑に予測してきた

$
0
0
ネタ的には YAPC::Hokkaido 2016 Sapporo の時のリライト(8.0.1のリリースノートの情報を追加した感じ)で、相変わらずいくつかの側面に分けてMySQL 8.0に期待していることやこうなるんじゃないかな感を発表しました。

MySQL 8.0.1のリリースノートが結構量が増えていて、時間も前回の20分に対して45分と倍増していたんですが、時間ギリギリまで結構色々しゃべりました。





MySQL 8.0は相当ゴツいな、というイメージなんですが、MySQL 5.7の時も同じようなこと言ってたし、きっと出てきたら出てきたで楽しく 地雷を踏み抜く新機能で遊ぶんだろうな、とこのスライドのためにリリースノートを読みながらほんのり思いました。

さあ、みんないっしょにMySQL 8.0で遊びましょう :)
Have Fun!!

sql_select_limitを設定したらXtraBackupがsignal 11で転けた

$
0
0

TL;DR

SET GLOBAL sql_select_limit = ?で結果セットのサイズを制限していると、xtrabackupが内部的に発行している SHOWステートメントの出力結果が切り詰められてクラッシュすることがある。

xtrabackupで遊んでいたら、ある時からおもむろにsignal 11で落ちるようになった。それ以前はフツーにバックアップ取れてたのに。
$ innobackupex -S /usr/mysql/5.7.17/data/mysql.sock -uroot .
170321 10:43:26 innobackupex: Starting the backup operation

IMPORTANT: Please check that the backup run completes successfully.
At the end of a successful backup run innobackupex
prints "completed OK!".

Unrecognized character \x01; marked by <-- HERE after <-- HERE near column 1 at - line 1374.
170321 10:43:26 Connecting to MySQL server host: localhost, user: root, password: not set, port: not set, socket: /usr/mysql/5.7.17/data/mysql.sock
01:43:26 UTC - xtrabackup got signal 11 ;
This could be because you hit a bug or data is corrupted.
This error can also be caused by malfunctioning hardware.
Attempting to collect some information that could help diagnose the problem.
As this is a crash and something is definitely wrong, the information
collection process might fail.

Thread pointer: 0x0
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 0 thread_stack 0x10000
innobackupex(my_print_stacktrace+0x2c)[0xc2078c]
innobackupex(handle_fatal_signal+0x262)[0xa36bf2]
/lib64/libpthread.so.0(+0xf370)[0x7f3150796370]
/lib64/libc.so.6(+0x1354ab)[0x7f314e5954ab]
innobackupex(_Z14get_mysql_varsP8st_mysql+0x44b)[0x734e5b]
innobackupex(_Z7xb_initv+0x12b)[0x71f77b]
innobackupex(main+0x37b)[0x6ff14b]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f314e481b35]
innobackupex[0x7175d4]

Please report a bug at https://bugs.launchpad.net/percona-xtrabackup
最初は Unrecognized character \x01; marked by <-- HERE after <-- HERE near column 1 at - line 1374.が怪しいのかと思って色々調べてみてたけれど、なんか正常終了するケースでも出力されているぽい(2.4.5では出なかった && 2.4.6では常に出る)
ステップ実行してみたら、ここを通り抜けた後にsignal 11なのでこれが直接の原因ではなさげ。
次の容疑者(?)は get_mysql_varsなのでここにブレークポイントを打ってステップ実行。
$ gdb --args innobackupex -S /usr/mysql/5.7.17/data/mysql.sock -uroot .
(gdb) b get_mysql_vars
(gdb) r
Breakpoint 1, get_mysql_vars (connection=0x1b1e630)
at /usr/src/debug/percona-xtrabackup-2.4.6/storage/innobase/xtrabackup/src/backup_mysql.cc:369
(gdb) n
..
461 if (!(ret = check_server_version(server_version, version_var,
(gdb)
+n

Program received signal SIGSEGV, Segmentation fault.
__strstr_sse42 (s1=0x0, s2=0x111b524 "Percona") at ../sysdeps/x86_64/multiarch/strstr.c:174
174 if (__builtin_expect (p1[0] == '\0', 0))
(gdb)
+n
handle_fatal_signal (sig=11) at /usr/src/debug/percona-xtrabackup-2.4.6/sql/signal_handler.cc:57
57 {
ここで落ちた。
もう一度そこにブレークポイントを入れて渡してる変数を見てみる。
Breakpoint 2, get_mysql_vars (connection=0x1b1e630)
at /usr/src/debug/percona-xtrabackup-2.4.6/storage/innobase/xtrabackup/src/backup_mysql.cc:461
461 if (!(ret = check_server_version(server_version, version_var,
(gdb) p server_version
+p server_version
$1 = 50717
(gdb) p version_var
+p version_var
$2 = 0x0
(gdb) p version_comment_var
+p version_comment_var
$3 = 0x0
(gdb) p innodb_version_var
+p innodb_version_var
$4 = 0x0
あれー。いくつかNULLになってる。これがSEGVの原因か。
ここで使っている変数を読み取っているのは このへんで、 やってることはどうも SHOW VARIABLESの出力結果を素直に配列に詰めてるだけに見えるんだけどな。。
mysql57> SHOW VARIABLES;
+-----------------------------------------+-----------------------------------+
| Variable_name | Value |
+-----------------------------------------+-----------------------------------+
..
| innodb_adaptive_flushing | ON |
| innodb_adaptive_flushing_lwm | 10 |
+-----------------------------------------+-----------------------------------+
100 rows in set (0.00 sec)
(つд⊂)ゴシゴシ
あれ? 明らかにInnoDB関連のパラメーター足りなくね? 100 rowsしかない?
mysql57> SHOW VARIABLES LIKE 'innodb%version%';
+----------------+--------+
| Variable_name | Value |
+----------------+--------+
| innodb_version | 5.7.17 |
+----------------+--------+
1 row in set (0.00 sec)
これは引ける…。。あ。
これを見てた時に SET GLOBAL sql_select_limit= 100したからいけないのか…。orz
本番で起きるとは思えないけど、こんなことがあったメモ。

mysqlディレクトリーに知らない.ibdファイルがある in MySQL 8.0.0

$
0
0
InnoDBログをcatしたら見知らぬibdファイルの名前が書いてあることに気が付いた。 mysql/character_sets.ibdなるファイルに書き込みをしているようだが、
mysql80> SHOW TABLES FROM mysql LIKE '%char%';
Empty set (0.00 sec)
そんなテーブルは存在しない。
$ ll data/mysql/character_sets.ibd
-rw-r----- 1 yoku0825 yoku0825 163840 Apr 3 14:44 data/mysql/character_sets.ibd
ファイルは確かにある。 なんだこれ…? と思ってたら、なんか他にもいっぱいあった。
$ diff -y <(./use -sse "SHOW TABLES FROM mysql" | sort) <(ls data/mysql/*.ibd | perl -nle 's/.+\///; s/\.ibd//; print' | sort)
> catalogs
> character_sets
> collations
> columns
columns_priv columns_priv
column_stats column_stats
> column_type_elements
component component
db db
default_roles default_roles
engine_cost engine_cost
> events
> foreign_key_column_usage
> foreign_keys
func func
general_log <
gtid_executed gtid_executed
help_category help_category
help_keyword help_keyword
help_relation help_relation
help_topic help_topic
> index_column_usage
> indexes
> index_partitions
> index_stats
innodb_index_stats innodb_index_stats
innodb_table_stats innodb_table_stats
> parameters
> parameter_type_elements
plugin plugin
procs_priv procs_priv
proxies_priv proxies_priv
role_edges role_edges
> routines
> schemata
server_cost server_cost
servers servers
slave_master_info slave_master_info
slave_relay_log_info slave_relay_log_info
slave_worker_info slave_worker_info
slow_log | st_spatial_reference_systems
> table_partitions
> table_partition_values
> tables
> tablespace_files
> tablespaces
tables_priv tables_priv
> table_stats
time_zone time_zone
time_zone_leap_second time_zone_leap_second
time_zone_name time_zone_name
time_zone_transition time_zone_transition
time_zone_transition_type time_zone_transition_type
> triggers
user user
> version
> view_routine_usage
> view_table_usage
向かって右がibdファイルしかないやつ。 general_logとslow_log(なんか変にst_spatial_reference_systemsと混じってやんの。。)はCSVストレージエンジンだからテーブルしかないのは良いとして、tables.ibdとかがテーブルのメタデータを集めたibdファイルになっているっぽい( od -cしてみたらどうやらそれっぽい) これが、「 information_schema.tablesは実際のInnoDBテーブルへの参照になって、メタデータを .frm から集めなくなるから速くなる」の正体か。
.ibdファイルからレコードを引き出すユーティリティーが欲しくなるなあ。。 とか思ったけど innodb_rubyがまだフツーに使えた。やった。
mysql80> CREATE TABLE t1 (num serial, val varchar(32)) comment = 'yoku0825';
Query OK, 0 rows affected (0.02 sec)

$ innodb_space -s data/ibdata1 -T mysql/tables space-indexes
id name root fseg used allocated fill_factor
35 PRIMARY 5 internal 1 1 100.00%
35 PRIMARY 5 leaf 28 28 100.00%
36 schema_id 6 internal 1 1 100.00%
36 schema_id 6 leaf 0 0 0.00%
37 engine 7 internal 1 1 100.00%
37 engine 7 leaf 0 0 0.00%
38 engine_2 8 internal 1 1 100.00%
38 engine_2 8 leaf 0 0 0.00%
39 collation_id 9 internal 1 1 100.00%
39 collation_id 9 leaf 0 0 0.00%
40 tablespace_id 10 internal 1 1 100.00%
40 tablespace_id 10 leaf 0 0 0.00%

$ innodb_space -s data/ibdata1 -T mysql/tables -p 5 page-dump | less
{:format=>:compact,
:offset=>668,
:header=>
{:next=>112,
:type=>:node_pointer,
:heap_number=>29,
:n_owned=>0,
:min_rec=>false,
:deleted=>false,
:nulls=>[],
:lengths=>{},
:externs=>[],
:length=>5},
:next=>112,
:type=>:clustered,
:key=>[{:name=>"id", :type=>"BIGINT UNSIGNED", :value=>318}],
:row=>[],
:sys=>[],
:child_page_number=>38,
:length=>12}

$ innodb_space -s data/ibdata1 -T mysql/tables -p 38 page-dump | less
{:format=>:compact,
:offset=>12645,
:header=>
{:next=>112,
:type=>:conventional,
:heap_number=>6,
:n_owned=>0,
:min_rec=>false,
:deleted=>false,
:nulls=>
["se_private_data",
"se_private_id",
"tablespace_id",
"partition_type",
"partition_expression",
"default_partitioning",
"subpartition_type",
"subpartition_expression",
"default_subpartitioning",
"view_definition",
"view_definition_utf8",
"view_check_option",
"view_is_updatable",
"view_algorithm",
"view_security_type",
"view_definer",
"view_client_collation_id",
"view_connection_collation_id"],
:lengths=>{"name"=>2, "engine"=>6, "comment"=>8, "options"=>105},
:externs=>[],
:length=>12},

:next=>112,
:type=>:clustered,
:key=>[{:name=>"id", :type=>"BIGINT UNSIGNED", :value=>322}],
:row=>
[{:name=>"schema_id", :type=>"BIGINT UNSIGNED", :value=>6},
{:name=>"name", :type=>"VARCHAR(192)", :value=>"t1"},
{:name=>"type", :type=>"CHAR(1) UNSIGNED", :value=>"\x01"},
{:name=>"engine", :type=>"VARCHAR(192)", :value=>"InnoDB"},
{:name=>"mysql_version_id", :type=>"INT UNSIGNED", :value=>80000},
{:name=>"row_format", :type=>"CHAR(1) UNSIGNED", :value=>"\x02"},
{:name=>"collation_id", :type=>"BIGINT UNSIGNED", :value=>8},
{:name=>"comment", :type=>"VARCHAR(6144)", :value=>"yoku0825"},
{:name=>"hidden", :type=>"TINYINT", :value=>0},
{:name=>"options",
:type=>"BLOB",
:value=>
"avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;"},
{:name=>"se_private_data", :type=>"BLOB", :value=>:NULL},
{:name=>"se_private_id", :type=>"BIGINT UNSIGNED", :value=>:NULL},
{:name=>"tablespace_id", :type=>"BIGINT UNSIGNED", :value=>:NULL},
{:name=>"partition_type", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"partition_expression", :type=>"VARCHAR(6144)", :value=>:NULL},
{:name=>"default_partitioning", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"subpartition_type", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"subpartition_expression", :type=>"VARCHAR(6144)", :value=>:NULL},
{:name=>"default_subpartitioning",
:type=>"CHAR(1) UNSIGNED",
:value=>:NULL},

{:name=>"created", :type=>"TIMESTAMP", :value=>"2017-04-03 06:47:50"},
{:name=>"last_altered", :type=>"TIMESTAMP", :value=>"2017-04-03 06:47:50"},
{:name=>"view_definition", :type=>"BLOB", :value=>:NULL},
{:name=>"view_definition_utf8", :type=>"BLOB", :value=>:NULL},
{:name=>"view_check_option", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"view_is_updatable", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"view_algorithm", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"view_security_type", :type=>"CHAR(1) UNSIGNED", :value=>:NULL},
{:name=>"view_definer", :type=>"VARCHAR(279)", :value=>:NULL},
{:name=>"view_client_collation_id",
:type=>"BIGINT UNSIGNED",
:value=>:NULL},
{:name=>"view_connection_collation_id",
:type=>"BIGINT UNSIGNED",
:value=>:NULL}],
:sys=>
[{:name=>"DB_TRX_ID", :type=>"TRX_ID", :value=>1571},
{:name=>"DB_ROLL_PTR",
:type=>"ROLL_PTR",
:value=>
{:is_insert=>true, :rseg_id=>58, :undo_log=>{:page=>303, :offset=>272}}}],
:length=>173,
:transaction_id=>1571,
:roll_pointer=>
{:is_insert=>true, :rseg_id=>58, :undo_log=>{:page=>303, :offset=>272}}}
ふむ。。この手の隠しibdはもうちょっと探ってみても面白い鴨。

MySQL 8.0.1でutf8mb4_ja_0900_as_csが導入された

$
0
0

MySQL 8.0.1で実装されていたので試してみた。
mysql80> SHOW COLLATION LIKE 'utf8%ja%';
+-----------------------+---------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+-----------------------+---------+-----+---------+----------+---------+
| utf8mb4_ja_0900_as_cs | utf8mb4 | 303 | | Yes | 24 |
+-----------------------+---------+-----+---------+----------+---------+
1 row in set (0.00 sec)
まずは「ハハ=パパ」問題。 (MySQLは真偽値を0(=FALSE)と1(=TRUE)で返すのでそのつもりで)
mysql80> SELECT 'ハハ' = 'パパ' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------+
| 'ハハ' = 'パパ' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------+
| 0 |
+---------------------------------------------------+
1 row in set (0.04 sec)
ハハパパケースセンシティブ。 ひらがな=カタカナ問題。
mysql80> SELECT 'ハハ' = 'はは' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------+
| 'ハハ' = 'はは' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------+
| 1 |
+---------------------------------------------------+
1 row in set (0.00 sec)
ひらがなカタカナケースインセンシティブ。 次は半角全角。
mysql80> SELECT 'ハハ' = 'ハハ' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------+
| 'ハハ' = 'ハハ' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------+
| 1 |
+---------------------------------------------------+
1 row in set (0.00 sec)

mysql80> SELECT 'はは' = 'ハハ' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------+
| 'はは' = 'ハハ' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------+
| 1 |
+---------------------------------------------------+
1 row in set (0.00 sec)
半角全角ケースインセンシティブ。 拗音。
mysql80> SELECT 'びょういん' = 'びよういん' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------------------------+
| 'びょういん' = 'びよういん' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------------------------+
| 0 |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)
病院≠美容院。拗音ケースセンシティブ。 最後🍣=🍺だけ俺はウインドーズでターミナルから直接打ち込めないので画像で。
ちょっと見にくいけど0。
utf8mb4_binutf8mb4_general_ciutf8mb4_unicode_ciutf8mb4_unicode_520_ciutf8mb4_ja_0900_as_cs
Hiragana-Katakanacs (unkind)cs (unkind)ci (good)ci(good)ci(good)
Youoncs (good)cs (good)ci (critical)ci(critical)cs(good)
Dakuten-Handakutencs (good)cs (good)ci (critical)ci(critical)cs(good)
Wide-Narrowcs (unkind)cs (unkind)ci (good)ci(good)ci(good)
Sushi-Beercscicicscs
おおー、結構いいセン行ってるんじゃないだろうか。
なお、斎藤さんは斉藤さんかとかそういうことを考え出すと、どうすればいいのか俺にもよくわからないけど一応センシティブ(中国語圏の人とかどうあるべきだと思うんだろう)
mysql80> SELECT '斎藤' = '斉藤' COLLATE utf8mb4_ja_0900_as_cs;
+---------------------------------------------------+
| '斎藤' = '斉藤' COLLATE utf8mb4_ja_0900_as_cs |
+---------------------------------------------------+
| 0 |
+---------------------------------------------------+
1 row in set (0.00 sec)
あとはこの設定を秘伝のmy.cnfのmysqldセクションに書き込んでおけばOK。 character_set_serverはデフォルトがutf8mb4になったけれど一応ついでに。
$ vim my.cnf
..
[mysqld]
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs
..

MySQL 8.0.1の新顔、GROUPING集約関数

$
0
0
TL;DR
WITH ROLLUPの結果行をHAVING条件に書けるようすることができる。 それ以外の時には使わない。
使い方。 そもそも WITH ROLLUPの使い方を知らないと楽しくもなんともないので WITH ROLLUPの説明から。
まずは WITH ROLLUPなしバージョン(SUM関数を噛ませてるのはあとで WITH ROLLUPした時のため)
mysql80> SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name;
+---------------+----------------------------------------------+------------+
| Continent | Name | Population |
+---------------+----------------------------------------------+------------+
| North America | Aruba | 103000 |
| Asia | Afghanistan | 22720000 |
| Africa | Angola | 12878000 |
..
| Africa | South Africa | 40377000 |
| Africa | Zambia | 9169000 |
| Africa | Zimbabwe | 11669000 |
+---------------+----------------------------------------------+------------+
239 rows in set (0.00 sec)
おっと… GROUP BYが暗黙のソートをしなくなった件を垣間見ることもできた。 5.7とそれ以前と出力結果を一緒にするためには、 ORDER BY Continent, Nameも追加する必要がある。
ともあれ、こんなフツーの GROUP BYなクエリーに WITH ROLLUPを足してやると
mysql80> SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name WITH ROLLUP;
+---------------+----------------------------------------------+------------+
| Continent | Name | Population |
+---------------+----------------------------------------------+------------+
| Asia | Afghanistan | 22720000 |
| Asia | Armenia | 3520000 |
| Asia | Azerbaijan | 7734000 |
..
| Asia | Yemen | 18112000 |
| Asia | NULL | 3705025700 |
| Europe | Albania | 3401200 |
..
| Europe | Yugoslavia | 10640000 |
| Europe | NULL | 730074600 |
| North America | Anguilla | 8000 |
..
| South America | Venezuela | 24170000 |
| South America | NULL | 345780000 |
| NULL | NULL | 6078749450 |
+---------------+----------------------------------------------+------------+
247 rows in set (0.00 sec)
こうなる。 Continent単位で合計したものが Name IS NULLとして集約行が作られて、全てを合計した値で Continent IS NULL, Name IS NULLとして集約行が作られる。
これ、NULLになったカラムはSQLの中から条件指定が不可能(WHEREはGROUP BYが処理される前のフィルターだし、HAVINGフィルターよりも更に後に集約行が作成されるのでダメらしい( MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.19.2 GROUP BY 修飾子
なので、この集約行にだけアクセスしたい場合(最初からそこでGROUP BYしろよとは思うけれどなんでなのか俺もやりたがった記憶がある)、アプリケーションの中で結果セットを受け取ってからカラムがNULLかどうかチェックして集約行判定をしなければならなかった。 たとえばこんな風に( !(defined($_->{Continent})) && !(defined(_->{Name}))
my $conn= DBI->connect("dbi:mysql:world;mysql_socket=/usr/mysql/8.0.1/data/mysql.sock", "root", "");

my $sql= "SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name WITH ROLLUP";
foreach (@{$conn->selectall_arrayref($sql, {Slice => {}})})
{
print Dumper $_ if !(defined($_->{Continent})) && !(defined($_->{Name}));
}

=pod
$VAR1 = {
'Continent' => undef,
'Name' => undef,
'Population' => '6078749450'
};
=cut
で、これをHAVINGの中で言及できるようにする関数が GROUPINGらしい。
mysql80> SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name WITH ROLLUP HAVING GROUPING(Continent) AND GROUPING(Name);
+-----------+------+------------+
| Continent | Name | Population |
+-----------+------+------------+
| NULL | NULL | 6078749450 |
+-----------+------+------------+
1 row in set (0.00 sec)

mysql80> SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name WITH ROLLUP HAVING GROUPING(Name);
+---------------+------+------------+
| Continent | Name | Population |
+---------------+------+------------+
| Asia | NULL | 3705025700 |
| Europe | NULL | 730074600 |
| North America | NULL | 482993000 |
| Africa | NULL | 784475000 |
| Oceania | NULL | 30401150 |
| Antarctica | NULL | 0 |
| South America | NULL | 345780000 |
| NULL | NULL | 6078749450 |
+---------------+------+------------+
8 rows in set (0.00 sec)
最初っからそこで GROUP BY しなよ感があるけれど、GROUPING が1か0を返すからORDER BYで集約行を先に持ってこられたらいいかな? と思った。
mysql80> SELECT Continent, Name, SUM(Population) AS Population FROM country GROUP BY Continent, Name WITH ROLLUP HAVING GROUPING(Name) ORDER BY GROUPING(Name);
ERROR 1221 (HY000): Incorrect usage of CUBE/ROLLUP and ORDER BY
WITH ROLLUPとORDER BYが同時に使えないという制約があるので並べ替えには使えなかった。 残念。。

xtrabackupが実行中かどうかをSQLだけで確認する思考実験

$
0
0
はじまりは
畜生ペンギン@keny_lalaのひとこと。






Percona Serverには LOCK TABLES FOR BACKUPとかあったよなと思いつつ、たぶんPercona Serverじゃないので置いておく。
xtrabackup-2.4.6のソースコードをナナメに読んでいくと、 SET SESSION wait_timeout = 2147483を押し込んでいる箇所があったので、ここで検出できないかなと思い付く。
取り敢えず王道(?)として、 performance_schema.variables_by_threadで引いてみた。
mysql57> SELECT * FROM performance_schema.variables_by_thread WHERE variable_name = 'wait_timeout' AND variable_value = 2147483;
+-----------+---------------+----------------+
| THREAD_ID | VARIABLE_NAME | VARIABLE_VALUE |
+-----------+---------------+----------------+
| 50 | wait_timeout | 2147483 |
+-----------+---------------+----------------+
1 row in set (0.02 sec)
ビンゴ。 そんなにキリの良い数字じゃないと思うんだけど、なんで2147483でハードコードしてあるんだろう。 ともあれ、これならフツーのアプリが使うこともなさそうなのでこれで検出できそう。





:(;゙゚’ω゚’): あ、5.6もサポートしないとダメ? ってかこのテーブル5.7で追加されたんだっけか。。。
如何にも爪痕を残しそうな PERCONA_SCHEMA.xtrabackup_historyなるものをCREATEしている箇所があったけど、これはxtrabackupが終わった後に通るのだそう(´・ω・`)
仕方ない、ユーザーロックするパッチ当てるか。。
*** storage/innobase/xtrabackup/src/backup_mysql.cc.orig        2017-02-27 16:47:06.000000000 +0900
--- storage/innobase/xtrabackup/src/backup_mysql.cc 2017-04-13 14:44:07.149003517 +0900
***************
*** 162,167 ****
--- 162,169 ----

xb_mysql_query(connection, "SET SESSION wait_timeout=2147483",
false, true);
+ xb_mysql_query(connection, "SELECT get_lock('xtrabackup', @@wait_timeout)",
+ false, true);

return(connection);
}
***************
*** 1697,1702 ****
--- 1699,1706 ----

free(uuid);
free(server_version);
+ xb_mysql_query(connection, "SELECT release_lock('xtrabackup')",
+ false, false);

return(true);
}
というわけで接続時に get_lockして終了時に release_lockするクエリーを入れ込んだ。
これなら is_used_lock 関数だけでSQLインターフェイスから確認できるし
mysql57> SELECT is_used_lock('xtrabackup');
+----------------------------+
| is_used_lock('xtrabackup') |
+----------------------------+
| 25 |
+----------------------------+
1 row in set (0.00 sec)
ついでにxbの二重起動も防げる。
mysql57> SHOW PROCESSLIST;
+----+------+-----------+------+---------+------+-----------+-----------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+-----------+-----------------------------------------------+
| 25 | root | localhost | NULL | Sleep | 691 | | NULL |
| 28 | root | localhost | NULL | Query | 7 | User lock | SELECT get_lock('xtrabackup', @@wait_timeout) |
| 29 | root | localhost | NULL | Query | 0 | starting | SHOW PROCESSLIST |
+----+------+-----------+------+---------+------+-----------+-----------------------------------------------+
3 rows in set (0.00 sec)
よし、Feature Request出しに行くか?

MySQL 8.0.1からJOIN_ORDERヒントが書ける

$
0
0
こんな、ORDER BY狙いのキーを使いたくなるクエリーがあるじゃろ?
mysql80> EXPLAIN SELECT Name, Language, Population, Percentage FROM CountryLanguage LEFT JOIN Country ON Country.Code= CountryLanguage.CountryCode WHERE Country.continent = 'Asia' ORDER BY Percentage LIMIT 5;
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | Country | NULL | ALL | PRIMARY,index_code_continent | NULL | NULL | NULL | 239 | 14.29 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | CountryLanguage | NULL | ref | PRIMARY,CountryCode | PRIMARY | 3 | world.Country.Code | 4 | 100.00 | NULL |
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
  1. STRAIGHT_JOINに書き換えてORDER BYで使っているカラムを駆動表に固定する。ただしSTRAIGHT_JOINは内部結合なので、LEFT JOINは書き換えられない。
  2. USE INDEXかFORCE INDEXでORDER BY狙いのキーを狙い撃つ。大概の場合はこれで上手く動くんだけれど、最悪の場合 内部表のままORDER BY狙いのキーを使ってインデックススキャンががががが
INNER JOINなら 1. + 2. (たまに、STRAIGHT_JOINでもORDER BY狙いのキーを取らないことがあったりした。最近少ない気がする)、そうでなければ 2. だけとしてORDER BY狙いのキーを押し込むことが多かったけれど、MySQL 8.0.1からは JOIN_ORDERのヒント句が使えるようになった。
mysql80> EXPLAIN SELECT /*+ JOIN_ORDER (CountryLanguage, Country) */ Name, Language, Population, Percentage FROM CountryLanguage LEFT JOIN Country ON Country.Code= CountryLanguage.CountryCode WHERE Country.continent = 'Asia' ORDER BY Percentage LIMIT 5;
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
| 1 | SIMPLE | CountryLanguage | NULL | index | PRIMARY,CountryCode | index_percentage | 4 | NULL | 5 | 100.00 | Using index |
| 1 | SIMPLE | Country | NULL | eq_ref | PRIMARY,index_code_continent | PRIMARY | 3 | world.CountryLanguage.CountryCode | 1 | 14.29 | Using where |
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

mysql80> EXPLAIN SELECT /*+ JOIN_PREFIX (Country) */ Name, Language, Population, Percentage FROM CountryLanguage LEFT JOIN Country ON Country.Code= CountryLanguage.CountryCode WHERE Country.continent = 'Asia' ORDER BY Percentage LIMIT 5;
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | Country | NULL | ALL | PRIMARY,index_code_continent | NULL | NULL | NULL | 239 | 14.29 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | CountryLanguage | NULL | ref | PRIMARY,CountryCode | PRIMARY | 3 | world.Country.Code | 4 | 100.00 | NULL |
+----+-------------+-----------------+------------+------+------------------------------+---------+---------+--------------------+------+----------+----------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql80> EXPLAIN SELECT /*+ JOIN_SUFFIX (Country) */ Name, Language, Population, Percentage FROM CountryLanguage LEFT JOIN Country ON Country.Code= CountryLanguage.CountryCode WHERE Country.continent = 'Asia' ORDER BY Percentage LIMIT 5;
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
| 1 | SIMPLE | CountryLanguage | NULL | index | PRIMARY,CountryCode | index_percentage | 4 | NULL | 5 | 100.00 | Using index |
| 1 | SIMPLE | Country | NULL | eq_ref | PRIMARY,index_code_continent | PRIMARY | 3 | world.CountryLanguage.CountryCode | 1 | 14.29 | Using where |
+----+-------------+-----------------+------------+--------+------------------------------+------------------+---------+-----------------------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
JOIN_ORDER(先に来るテーブル, 後に来るテーブル), または JOIN_PREFIX(先に来るテーブル), JOIN_SUFFIX(後に来るテーブル)の3つの書き方で指定できるぽい。
ORDER BY狙いのキーなら一番外側にあればそれでいいので、3つ以上の時も JOIN_ORDERより JOIN_PREFIXがいいのかな。
これでLEFT JOINでもORDER BY狙いのキーが狙いやすくなってすてきだ。

MySQL 5.7.17からエラーログに出るようになった deprecated partition engine に関するNote

$
0
0
こんなやつのこと。
2017-04-18T23:54:08.224673+09:00 0 [Note] /usr/mysql/5.7.18/bin/mysqld: ready for connections.
Version: '5.7.18-log' socket: '/usr/mysql/5.7.18/data/mysql.sock' port: 64057 Source distribution
2017-04-18T23:54:08.224691+09:00 0 [Note] Executing 'SELECT * FROM INFORMATION_SCHEMA.TABLES;' to get a list of tables using the deprecated partition engine. You may use the startup option '--disable-partition-engine-check' to skip this check.
2017-04-18T23:54:08.224696+09:00 0 [Note] Beginning of list of non-natively partitioned tables
2017-04-18T23:54:08.316219+09:00 0 [Note] End of list of non-natively partitioned tables
MySQL 8.0.0で完全になくなることが決まった(というか8.0.0の時点でもうない) PARTITIONストレージエンジンを「使ってないよね? チェックするぞ?」という機能が MySQL 5.7.17に入った。
MySQLのパーティショニングはストレージエンジンとして実装されてい ./configure --helpcmake -iを使ったことがあれば、 -DWITH_PARTITION_STORAGE_ENGINE=ONとかそういうのに憶えがあるかも知れない)
MySQL 5.7のInnoDBに関しては InnoDBネイティブパーティショニングといってPARTITIONストレージエンジンを使わずにInnoDBの内部でパーティションを表現するようになった。
「今後、パーティションを使っててもFOREIGN KEY制約がつけられるようになるかも知れない」というのは、このInnoDBネイティブパーティショニングによるもの。
フツーに mysql_upgradeをかましていれば5.7にアップグレードした時点で変換されるはずなのだが、 mysql_upgrade -sとか avoid_temporal_upgradeを使ってゴニョゴニョするとかをしている…あるいはいっこ飛ばして5.6から8.0へダイレクトジャンプするとかだと、 PARTITIONストレージエンジンによってパーティショニングされていたInnoDBのテーブルがそのまま残ってしまう。
Prior to MySQL 5.7.6, partitioned InnoDB tables used the generic ha_partition partitioning handler employed by MyISAM and other storage engines not supplying their own partitioning handlers; in MySQL 5.7.6 and later, such tables are created using the InnoDB storage engine's own (or “native”) partitioning handler. Beginning with MySQL 5.7.9, you can upgrade an InnoDB table that was created in MySQL 5.7.6 or earlier (that is, created using ha_partition) to the InnoDB native partition handler using ALTER TABLE ... UPGRADE PARTITIONING. (Bug #76734, Bug #20727344) This version of ALTER TABLE does not accept any other options and can be used only on a single table at a time. You can also use mysql_upgrade in MySQL 5.7.9 or later to upgrade older partitioned InnoDB tables to the native partitioning handler.
PARTITIONストレージエンジンそのものがなくなってしまうMySQL 8.0とそれ以降ではそのテーブルはそのままでは生きていけない……というわけで、過渡期にあたるMySQL 5.7にこのログが追加されたのだ(と思う)
default_password_lifetimeのときWe agree with the original bug reporter that the default of 360 is surprising for users upgrading from previous releases of MySQL.と言っていたので、おそらく多少 ショックの少なそうな方法を模索した結果なんだと思う。
ちなみに PARTITIONストレージエンジンなテーブルがあってもリストされるだけ(だと思う)なので、 ALTER TABLE .. ENGINE = INNODBはセルフサービスでやる必要がある。
さて、 InnoDBネイティブパーティショニングなので、もちろんInnoDB以外のストレージエンジンはサポートされていない(というかInnoDBの実装なのだから他のストレージエンジンに手を出せるわけがない)ので、MyISAMでパーティショニングしているヤーツがもし万一あったら今のうちにInnoDBにしておくのがよろしいかと思います。

MySQLのDATETIME型で秒の小数部を扱うときのDEFAULT句

$
0
0
この CREATE TABLEステートメントは転ける。
mysql57> CREATE TABLE t1 (num INT UNSIGNED NOT NULL, dt DATETIME(3) DEFAULT CURRENT_TIMESTAMP);
ERROR 1067 (42000): Invalid default value for 'dt'
CURRENT_TIMESTAMP関数DATETIME(0)型を返すので、dtカラムの型である DATETIME(3)型と合わないからだというわけで、
mysql57> CREATE TABLE t1 (num INT UNSIGNED NOT NULL, dt DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3));
Query OK, 0 rows affected (0.01 sec)

mysql57> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`dt` datetime(3) DEFAULT CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
これで通る。 なお、 ON UPDATE句を書いた場合も同じ。
mysql57> CREATE TABLE t2 (num INT UNSIGNED NOT NULL, dt DATETIME(3) ON UPDATE CURRENT_TIMESTAMP);
ERROR 1294 (HY000): Invalid ON UPDATE clause for 'dt' column

mysql57> CREATE TABLE t2 (num INT UNSIGNED NOT NULL, dt DATETIME(3) ON UPDATE CURRENT_TIMESTAMP(3));Query OK, 0 rows affected (0.01 sec)

mysql57> SHOW CREATE TABLE t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`num` int(10) unsigned NOT NULL,
`dt` datetime(3) DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
とても余談として、CURRENT_TIMESTAMP関数が NOW関数のシノニムってことは、これひょっとして DEFAULT NOW()でもいけるのでは? と思ったらいけた。しかも昔からだった。
mysql55> CREATE TABLE t1 (num INT UNSIGNED NOT NULL, dt TIMESTAMP DEFAULT NOW()); -- 5.5だからTIMESTAMP型じゃないとデフォルトを受けられない
Query OK, 0 rows affected (0.01 sec)

mysql55> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(10) unsigned NOT NULL,
`dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
ちゃんとCURRENT_TIMESTAMPとして扱われている。 ただし、CURRENT_TIMESTAMPと違ってNOWはかっこは省略できない。
mysql55> SELECT CURRENT_TIMESTAMP;
+---------------------+
| CURRENT_TIMESTAMP |
+---------------------+
| 2017-04-20 15:07:50 |
+---------------------+
1 row in set (0.00 sec)

mysql55> SELECT CURRENT_TIMESTAMP();
+---------------------+
| CURRENT_TIMESTAMP() |
+---------------------+
| 2017-04-20 15:08:03 |
+---------------------+
1 row in set (0.00 sec)

mysql55> SELECT NOW;
ERROR 1054 (42S22): Unknown column 'NOW' in 'field list'

mysql55> SELECT NOW();
+---------------------+
| NOW() |
+---------------------+
| 2017-04-20 15:08:15 |
+---------------------+
1 row in set (0.00 sec)

MySQL 8.0のDROP TABLEがアトミックになっているっぽい件

$
0
0
MySQL 5.7とそれ以前で、「存在するテーブルと存在しないテーブルを一緒にDROP TABLE」しようとすると
mysql57> CREATE TABLE t1 (num int);
Query OK, 0 rows affected (0.01 sec)

mysql57> CREATE TABLE t3 (num int);
Query OK, 0 rows affected (0.01 sec)

mysql57> SHOW TABLES;
+--------------+
| Tables_in_d1 |
+--------------+
| t1 |
| t3 |
+--------------+
2 rows in set (0.00 sec)

mysql57> DROP TABLE t1, t2, t3;
ERROR 1051 (42S02): Unknown table 'd1.t2'

mysql57> SHOW TABLES;
Empty set (0.00 sec)
エラーにはなるけど消せるものは消える。
MySQL 8.0だと
mysql80> CREATE TABLE t1 (num int);
Query OK, 0 rows affected (0.01 sec)

mysql80> CREATE TABLE t3 (num int);
Query OK, 0 rows affected (0.01 sec)

mysql80> SHOW TABLES;
+--------------+
| Tables_in_d1 |
+--------------+
| t1 |
| t3 |
+--------------+
2 rows in set (0.00 sec)

mysql80> DROP TABLE t1, t2, t3;
ERROR 1051 (42S02): Unknown table 'd1.t2'

mysql80> SHOW TABLES;
+--------------+
| Tables_in_d1 |
+--------------+
| t1 |
| t3 |
+--------------+
2 rows in set (0.00 sec)
おおおおお消えてない! ちゃんと「エラーが返った = 操作は失敗している」が成立しているぞぞぞ。
mysql80> ALTER TABLE t1 ENGINE= MyISAM;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80> DROP TABLE t1, t2, t3;
ERROR 1051 (42S02): Unknown table 'd1.t2'

mysql80> SHOW TABLES;
+--------------+
| Tables_in_d1 |
+--------------+
| t1 |
| t3 |
+--------------+
2 rows in set (0.00 sec)
マイア勇 MyISAMに変えても同じ動きだった。ちょっとびっくり。

MySQL 8.0.1でバイナリーログに original_commit_timestamp と immediate_commit_timestamp が追加された

$
0
0
original_committed_timestampはマスターで実行された時のタイムスタンプが、immediate_commit_timestampはそのサーバーで実際に実行された時のタイムスタンプがそれぞれ入る。 単位はいずれもマイクロ秒。
マスターで実行した CREATE DATABASE d1のバイナリーログ。
$ /usr/mysql/8.0.1/bin/mysqlbinlog master/data/mysql-bin.000002
..
#170428 17:54:25 server id 1 end_log_pos 226 CRC32 0xe0efd740 GTID last_committed=0 sequence_number=1 original_committed_timestamp=1493369665593157 immediate_commit_timestamp=1493369665593157
# original_commit_timestamp=1493369665593157 (2017-04-28 17:54:25.593157 JST)
# immediate_commit_timestamp=1493369665593157 (2017-04-28 17:54:25.593157 JST)
/*!80001 SET @@session.original_commit_timestamp=1493369665593157*//*!*/;
SET @@SESSION.GTID_NEXT= '00012009-1111-1111-1111-111111111111:1'/*!*/;
# at 226
#170428 17:54:25 server id 1 end_log_pos 323 CRC32 0xc5704ebe Query thread_id=8 exec_time=0 error_code=0 Xid = 44
..
CREATE DATABASE d1
/*!*/;
それがレプリケートされたスレーブのバイナリーログ。
$ /usr/mysql/8.0.1/bin/mysqlbinlog node2/data/mysql-bin.000002
..
# at 154
#170428 17:54:25 server id 1 end_log_pos 233 CRC32 0xddb16cfd GTID last_committed=0 sequence_number=1 original_committed_timestamp=1493369665593157 immediate_commit_timestamp=1493369665643968
# original_commit_timestamp=1493369665593157 (2017-04-28 17:54:25.593157 JST)
# immediate_commit_timestamp=1493369665643968 (2017-04-28 17:54:25.643968 JST)
/*!80001 SET @@session.original_commit_timestamp=1493369665593157*//*!*/;
SET @@SESSION.GTID_NEXT= '00012009-1111-1111-1111-111111111111:1'/*!*/;
# at 233
#170428 17:54:25 server id 1 end_log_pos 330 CRC32 0x1ced03f5 Query thread_id=8 exec_time=0 error_code=0 Xid = 11
..
CREATE DATABASE d1
/*!*/;
これに合わせて、 performance_schema.replication_applier_status_by_workerにも LAST_APPLIED_TRANSACTION_*_TIMESTAMPAPPLYING_TRANSACTION_*_TIMESTAMPが追加されてる。
これを使えば5.7とそれまでの Seconds_Behind_Master みたいにがんばって現在時刻との差とかを求めなくても良くなるようになる(んだと思う)
mysql> SELECT * FROM replication_applier_status_by_worker\G
*************************** 1. row ***************************
CHANNEL_NAME:
WORKER_ID: 0
THREAD_ID: 39
SERVICE_STATE: ON
LAST_ERROR_NUMBER: 0
LAST_ERROR_MESSAGE:
LAST_ERROR_TIMESTAMP: 0000-00-00 00:00:00.000000
LAST_APPLIED_TRANSACTION: 00012009-1111-1111-1111-111111111111:1
LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP: 2017-04-28 17:54:25.593157
LAST_APPLIED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP: 2017-04-28 17:54:25.593157
LAST_APPLIED_TRANSACTION_START_APPLY_TIMESTAMP: 2017-04-28 17:54:25.596175
LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP: 2017-04-28 17:54:25.645874
APPLYING_TRANSACTION:
APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP: 0000-00-00 00:00:00.000000
APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP: 0000-00-00 00:00:00.000000
APPLYING_TRANSACTION_START_APPLY_TIMESTAMP: 0000-00-00 00:00:00.000000
1 row in set (0.01 sec)
ところで、スレーブのバイナリーログ上の immediate_commit_timestampp_s. replication_applier_status_by_workerLAST_APPLIED_TRANSACTION_*_TIMESTAMPのどれとも合わないんだけど、これってこれでいいの…?

MySQLユーザ会会 in 長野 2017に参加してきましたよ

$
0
0
MySQLユーザ会会 in 長野 2017に逝ってきました!
開催の経緯とかはとみたさんのブログ MySQLユーザ会会 in 長野 を開催しました - @tmtms のメモ、当日のふいんき(何故か変換できない)はTogetter MySQLユーザ会会 in 長野 2017 - Togetterまとめがまとまってます。
セッションの内容は坂井さんMySQLとはみたいなことやると聞いていたし、かじやまさんは「MySQL 8.0」だって聞いてたし、bizstationさんTransactd PHP ORMと聞いていたので(というか、それが聞きたいと頼んだのはわたしだ)
( ´-`).oO(この豪勢なメンツに対抗して自分の色が出せそうなセッション…? 運用ネタ…?
とか考えたんですが、結局
いつもどおり「MySQLを楽しんでいるおじさんっぷり」を話すことにしました。






背景の舞奈たんがさかさまになっているのが謎。手元のPDFファイル(や、SlideshareからダウンロードしたPDF)では天地正しいんですけれども。
最近のお気に入りのGTID、本当はmysqlslavetrxの話も入れたかったし8.0のSET @@GLOBAL.GTID_PURGED = '+gtid_set'も入れたかったなあと思い出してみたり、PMMのデモ(というか本番だけど)を見せられたのは良かったなあと思ったりしました。
yoku0825は絶賛楽しそうなことを探していますので、他に楽しそうなものがあったら教えてください :)

(交通費は自腹でしたが)登壇枠くれたとみたさん、会場提供のケイケンシステムさんありがとうございました!

MySQLのSELECT .. FOR UPDATEはREPEATABLE-READでも直近にコミットされたレコードを返す

$
0
0

TL;DR


ドキュメント探してみたけどほんのちょっとだけしか書いてないような気がする。
SELECT … LOCK IN SHARE MODE は、これらの行のいずれかがコミットされていない別のトランザクションによって変更された場合、クエリーはそのトランザクションが終了するまで待機してから、最新の値を使用します。
しゃらっと 最新の値を使用しますと書いてあるけど、最新の値が意味するのは「最後にコミットされた時の値」であって、REPEATABLE-READのはずの分離レベル内でREAD-COMMITTEDっぽい動作が見える。
どういうことかというと
mysql57 4> START TRANSACTION;
mysql57 5> START TRANSACTION;

mysql57 4> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)

mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)

mysql57 4> UPDATE t1 SET val = 'after' WHERE num = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql57 4> COMMIT AND CHAIN;
Query OK, 0 rows affected (0.00 sec)

mysql57 4> SELECT * FROM t1;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)
ほぼ同時に開始したトランザクション45がいて、4はある行の値をアップデートしてコミットした。 この時に”after”が見えるのがREAD-COMMITTED、”before”が見えるのがREPEATABLE-READのはずで確かにそうなるんだけど、ブロッキングリード(SELECT .. FOR UPDATE, SELECT .. LOCK IN SHARE MODE)の場合は
mysql57 5> SELECT * FROM t1 FOR UPDATE;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)
コミット済みの値が見える。 同じトランザクションの中で非ブロッキングリードとブロッキングリードをすると
mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)

mysql57 5> SELECT * FROM t1 FOR UPDATE;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)

mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)
返ってくる値が変わる。
この話自体は有名な気がしていたんだけれど、ドキュメントには小さくしか見つけられなかったのでひょっとしたら有名じゃないのかなって。 (これ実演してみせたら爆笑されたw)

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

$
0
0

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
なんなんだろうなあ。。 しかしこれどっかで調べた気がするんだよなあ。。どこだったっけなあ。。

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

$
0
0

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の差が生死を分けることはないと思いますが、みなさまご注意ください。

mysqld_safeに "--ledir option can only be used as command line option"と言われたら

$
0
0

TL;DR

  • my.cnfに書いてある ledirの行を消す
  • 今までmy.cnfに書いてあった ledirmysqld_safeにコマンドラインオプションとして渡す
    • ex. mysqld_safe --defaults-file=/data/mysql/my.cnf --ledir=/usr/local/mysql/bin

パッケージもの以外のMySQLで、 mysqld_safeを起動した時に↓のように言われることがある。
mysqld_safe --ledir option can only be used as command line option, found in config file
読んでそのまま、 ledirはコマンドラインオプション( mysqld_safe --ledir=..の形式)で渡さなければいけないのに、コンフィグファイルから ledirの項目が見つかった、という意味。
MySQL 5.5.55とそれ以降、MySQL 5.6.36とそれ以降、MySQL 5.7.17とそれ以降がこのエラーを吐く。
対応するコミットはこちら。
CVE-2016-6662に関連する何かかと思ったら何の関係もなさそうな時期だった。

割と安全にpt-online-schema-changeとかpt-table-checksumを取るための簡単なやり方

$
0
0

TL;DR


想定しているトポロジーはこんな感じ。
  • マスター/スレーブ構成(そもそもスレーブいなければcheck_slave_lagもへったくれもない)
  • pt-osc, pt-tcsなどはマスターサーバーで実行する
check_slave_lag用にユーザーを作る(図の実線部分を担当するためのアカウント) 短期間でDROPするのが前提で、色々面倒なのでスーパーユーザーで行く。FILE権限くらいは確実に要らないからREVOKEしとくか。。
接続元ホストはマスターの外側のIPアドレス。
mysql> CREATE USER pt_osc@xxx.xxx.xxx.xxx IDENTIFIED BY 'yyyyyyyy';
mysql> GRANT ALL ON *.* TO pt_osc@xxx.xxx.xxx.xxx;
mysql> REVOKE FILE ON *.* FROM pt_osc@xxx.xxx.xxx.xxx;
このユーザーはレプリケーションを通じて全てのスレーブに作られるので、一発叩くとマスター上のpt-oscとかが直接アクセスできるようになる。
次はcheck_slave_lagを効かせる スレーブのDSNを指定するためのテーブルを作る。 DSNを直接コマンドラインオプションで指定ではなく、コマンドラインオプションで スレーブのDSNを指定するためのテーブルをDSN指定するのが面倒くさい。 (See also, 日々の覚書: pt-online-schema-change(と、pt-table-checksumとかもろもろ)の—recursion-methodについて
mysql> CREATE TABLE mysql.pt_osc (id int, dsn varchar(255));
mysql> INSERT INTO mysql.pt_osc VALUES (1, 'h=aaa.bbb.ccc.ddd,P=3306');
mysql> INSERT INTO mysql.pt_osc VALUES (1, 'h=eee.fff.ggg.hhh,P=3317');
..
idカラムはカブっても特に問題ないようだ。
↑で作ったユーザーがそのまま全サーバーにいるので、テーブルの中に格納するDSNはホストとポートだけでいいはず。 ここで指定しなかったからといってpt-oscやpt-tcsが流れないわけではないので大丈夫(飽くまでcheck_slave_lagを効かせるかどうか)
敢えてcheck_slave_lagを効かせたくないサーバーがあればINSERTしなければOK。 mysqlスキーマに作っちゃってるけど丁寧にやるならそれ用のスキーマを作った方がいいかも知れない。
テーブルを用意したらpt-oscを流す(progressは趣味) この時のマスターの接続先はソケット接続や127.0.0.1への接続でなく、外側のIPアドレスを指定して、作成したユーザーで接続する。
$ pt-online-schema-change --execute --alter "MODIFY col1 varchar(100) COLLATE utf8_bin NOT NULL" D=d1,t=t1,u=pt_osc,p=yyyyyyyy,h=xxx.xxx.xxx.xxx,P=3306 --recursion-method="dsn=D=mysql,t=pt_osc" --progress=time,5
Found 2 slaves:
マスターのホスト名 -> aaa.bbb.ccc.ddd:3306
マスターのホスト名 -> eee.fff.ggg.hhh:3317
Will check slave lag on:
マスターのホスト名 -> aaa.bbb.ccc.ddd:3306
マスターのホスト名 -> eee.fff.ggg.hhh:3317
..
Found .. slaves:, Will check slave lag on:に指定したものが表示されてればチェックされてる。接続エラーが出てないかどうかだけは注意して見る。
流れ終わったらお掃除しておしまい。
mysql> DROP TABLE mysql.pt_osc;
mysql> DROP USER pt_osc@xxx.xxx.xxx.xxx;
recursion-method=hostsやprocesslistで足りるならそっちを使った方が楽だけど、dsnを使いたい時はこう決め打つと楽だよってことで。
Viewing all 581 articles
Browse latest View live