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

gh-ostでスレーブの遅延を見ながら処理速度を調整する

$
0
0

TL;DR

  • マスターに接続する( --allow-on-master)場合、 --throttle-control-replicas myhost1.com:3306,myhost2.com:3306で遅延監視対象を指定する
    • スレーブからbinlogを吸い上げてマスターに当て込む場合は、接続先のスレーブで遅延監視をする
  • 閾値は gh-ost --max-lag-millis 1000で指定する (デフォルト1500ミリ秒)
  • SHOW SLAVE STATUSは叩かず、 *_ghcテーブルに書き込んだハートビートの行と現在時刻の差分で計算する
    • --test-on-replica--migrate-on-replicaの時だけ SHOW SLAVE STATUSするっぽい

2019-04-16T07:13:51.784309Z        22 Query     select value from `mysqlslap`.`_t1_ghc` where hint = 'heartbeat' and id <= 255
2019-04-16T07:13:52.784030Z 22 Query select value from `mysqlslap`.`_t1_ghc` where hint = 'heartbeat' and id <= 255
2019-04-16T07:13:53.784096Z 22 Query select value from `mysqlslap`.`_t1_ghc` where hint = 'heartbeat' and id <= 255
こんなレコードが入ってた。
slave1 [localhost] {msandbox} (mysqlslap) > SELECT * FROM _t1_ghc WHERE hint= 'heartbeat' AND id <= 255;
+----+---------------------+-----------+----------------------------------------+
| id | last_update | hint | value |
+----+---------------------+-----------+----------------------------------------+
| 1 | 2019-04-16 16:13:31 | heartbeat | 2019-04-16T16:13:31.979520869+09:00 |
+----+---------------------+-----------+----------------------------------------+
1 rows in set (0.00 sec)
この valueに入っている値を現在時刻と比較して遅延を計算する、と。
なるほど。 pt-heartbeatと同じような仕組みですね。
今日はこれまで。

gh-ostのinteractive-commandを使う

$
0
0
gh-ostには interactive-commandなるものが用意されている。
これは gh-ost起動しちゃった後、後からオプションを変更したりする機能を提供しているっぽい。

デフォルトではソケットファイルのみオープンする。デフォルトのパスは /tmp/gh-ost.<スキーマ名>.<テーブル名>.sock
パスを変えたかったりTCP経由も受け付けてほしい時は gh-ost --serve-socket-file=/tmp/hogegh-ost --serve-tcp-port=9999とかで設定できる。
$ gh-ost --database=mysqlslap --table=t1 --alter="ADD KEY (intcol1)" --host=127.0.0.1 --port=21801 --user=msandbox --password=msandbox --execute --serve-socket-file=/tmp/hoge --serve-tcp-port=9999
..
ソケットでもTCPでも ncで1行押し込むのが良いっぽい。
パイプ通さなくてもいいけれど、その場合は1つコマンドを押し込むたびに切断されるので面倒っちゃ面倒。
$ echo "status" | nc -U /tmp/hoge
# Migrating `mysqlslap`.`t1`; Ghost table is `mysqlslap`.`_t1_gho`
# Migrating 163-44-175-117:21800; inspecting 163-44-175-117:21801; executing on 163-44-175-117
# Migration started at Thu Apr 18 13:54:50 +0900 2019
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/hoge
# Serving on TCP port: 9999
Copy: 0/11761 0.0%; Applied: 0; Backlog: 1000/1000; Time: 20s(total), 16s(copy); streamer: mysql-bin.000002:578358174; State: throttled, lag=2.786607s; ETA: N/A

$ echo "help" | nc localhost 9999
available commands:
status # Print a detailed status message
sup # Print a short status message
coordinates # Print the currently inspected coordinates
chunk-size=<newsize> # Set a new chunk-size
dml-batch-size=<newsize> # Set a new dml-batch-size
nice-ratio=<ratio> # Set a new nice-ratio, immediate sleep after each row-copy operation, float (examples: 0 is aggressive, 0.7 adds 70% runtime, 1.0 doubles runtime, 2.0 triples runtime, ...)
critical-load=<load> # Set a new set of max-load thresholds
max-lag-millis=<max-lag> # Set a new replication lag threshold
replication-lag-query=<query> # Set a new query that determines replication lag (no quotes)
max-load=<load> # Set a new set of max-load thresholds
throttle-query=<query> # Set a new throttle-query (no quotes)
throttle-http=<URL> # Set a new throttle URL
throttle-control-replicas=<replicas> # Set a new comma delimited list of throttle control replicas
throttle # Force throttling
no-throttle # End forced throttling (other throttling may still apply)
unpostpone # Bail out a cut-over postpone; proceed to cut-over
panic # panic and quit without cleanup
help # This message
- use '?' (question mark) as argument to get info rather than set. e.g. "max-load=?" will just print out current max-load.

$ nc localhost 9999
status
# Migrating `mysqlslap`.`t1`; Ghost table is `mysqlslap`.`_t1_gho`
# Migrating 163-44-175-117:21800; inspecting 163-44-175-117:21801; executing on 163-44-175-117
# Migration started at Thu Apr 18 13:54:50 +0900 2019
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/hoge
# Serving on TCP port: 9999
Copy: 0/11761 0.0%; Applied: 0; Backlog: 1000/1000; Time: 3m6s(total), 3m3s(copy); streamer: mysql-bin.000002:578358174; State: throttled, lag=7.586699s; ETA: N/A
status

Ncat: Broken pipe.
今のところよく使いそうな気がしているのはこれ。
$ echo "max-lag-millis=500" | nc -U /tmp/hoge ### どのくらいレプリケーション遅延が出た時にSleepするかの閾値を変える。単位はミリ秒
$ echo "throttle" | nc -U /tmp/hoge ### gh-ostをサスペンドする
$ echo "no-throttle" | nc -U /tmp/hoge ### ↑でサスペンドしたのを再開する
--mal-lag-millsによるスロットルと throttleコマンドによるスロットルはどっちも State: throttledで表示されるけど、処理は独立しているっぽい。
max-lag-millis=10State: throttled -> throttleコマンド -> max-lag-millis=10000000State: throttledのまま -> no-throttleコマンドで State: migrating
さあ、次は 本番で試す実践投入ですね!

gh-ostの最後のステップの `RENAME TABLE ..` を任意のタイミングまで遅延させる

$
0
0

TL;DR

  • --postpone-cut-over-flag-fileでテキトーなファイルを指定する
  • RENAME TABLE ..(gh-ostの cut-overフェーズ)をしても良いタイミングになったら、「指定したファイルを消す」または 「 Interactive commandsunpostponeコマンドを放り込む」

gh-ostは起動してからよしなにバイナリーログを吸い上げてゴーストテーブル(スキーマに対する変更を適用しつつ、オリジナルのテーブルへの更新とオリジナルのテーブルからデータを少しずつフェッチしてマージしたもの)を作り、最後にはオリジナルのテーブルとゴーストテーブルを RENAME TABLE ..で入れ替えることでオンラインスキーマ変更を実現している。デフォルトでは用済みになったオリジナルのテーブルもドロップしない。
pt-online-schema-changeがトリガーを仕掛けてゴーストテーブル(Percona Toolkitではそうは呼ばないけれど)を組み立てて、 RENAME TABLE ..をかけてトリガーも片付けて更に入れ替えて用済みになったオリジナルのテーブルに対して DROP TABLEまでかけてくれる(デフォルト。しないようにもできる)のに比べれば、メタデータロックを取らないといけない操作も少ないし突然のLazy Drop Tableで死ぬことも少ないのだけれども。
それでも「その瞬間だけSELECTすらブロックするような」でかいロックを取る操作の前には心の準備として一拍置きたい気持ちがあったりなかったりする。
オリジナルのテーブルへのアクセスが十分少ない(人によって変わると思う)場合、pt-oscくらい何回もメタデータロックを取っていても全然問題なかったりする。なので別にこれがデフォルトになってほしいとは思わない(大概の場合、迷っているより流れちゃった方がさっさと済むのだ)
前置きが長くなったけど、やり方は TL;DRの通り。
$ gh-ost --database=d1 --table=t1 --alter="Engine = InnoDB" --port=64057 --user=root --host=127.0.0.1 --allow-on-master --execute --ok-to-drop-table --postpone-cut-over-flag-file=/tmp/moge
..
Copy: 0/2 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: bin.000014:566423; State: migrating; ETA: N/A
Copy: 0/2 0.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000014:570719; State: migrating; ETA: N/A
Copy: 2/2 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000014:571447; State: migrating; ETA: due
Copy: 2/2 100.0%; Applied: 0; Backlog: 0/1000; Time: 2s(total), 1s(copy); streamer: bin.000014:575747; State: postponing cut-over; ETA: due
Copy: 2/2 100.0%; Applied: 0; Backlog: 0/1000; Time: 3s(total), 1s(copy); streamer: bin.000014:580057; State: postponing cut-over; ETA: due
..
cut-overの遅延が有効になっている場合、ゴーストテーブルを組み立て終わった時点で State: postponing cut-overになる。
この状態ではもうオリジナルテーブルからのマージは終わっているから、ひたすらバイナリーログを吸い上げてはその差分を適用し続けるだけの状態。
$ ll /tmp/moge
-rwxr-xr-x 1 yoku0825 yoku0825 0 Apr 22 15:51 /tmp/moge

$ file /tmp/moge
/tmp/moge: empty
--postpone-cut-over-flag-fileに渡したパスには空っぽのファイルが出来上がっていて、これを消すかInteractive commandsから unpostponeを放り込むとcut-overフェーズに突入する。
$ rm /tmp/moge

..
Copy: 2/2 100.0%; Applied: 0; Backlog: 1/1000; Time: 10s(total), 1s(copy); streamer: bin.000014:611206; State: migrating; ETA: due
# Migrating `d1`.`t1`; Ghost table is `d1`.`_t1_gho`
# Migrating 163-44-175-117:64057; inspecting 163-44-175-117:64057; executing on 163-44-175-117
# Migration started at Mon Apr 22 16:49:10 +0900 2019
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# postpone-cut-over-flag-file: /tmp/moge
# Serving on unix socket: /tmp/gh-ost.d1.t1.sock
Copy: 2/2 100.0%; Applied: 0; Backlog: 0/1000; Time: 10s(total), 1s(copy); streamer: bin.000014:611647; State: migrating; ETA: due
[2019/04/22 16:49:20] [info] binlogsyncer.go:164 syncer is closing...
[2019/04/22 16:49:21] [error] binlogstreamer.go:77 close sync with err: sync is been closing...
[2019/04/22 16:49:21] [info] binlogsyncer.go:179 syncer is closed
# Done
State: migratingになって、最後の処理である RENAME TABLE ..などが行われる。

ところで、この cut-overを遅延させるための(ファイルを指定する以外の)オプションだと思っていた --cut-overというのがあったんだけれど、こっちは何の関係もなかった。。
$ gh-ost .. --cut-over=two-step
..

2019-04-22T15:37:48.792707+09:00 40 Query lock /* gh-ost */ tables `d1`.`t1` write
2019-04-22T15:37:49.793151+09:00 40 Query alter /* gh-ost */ table `d1`.`t1` rename `_t1_del`
2019-04-22T15:37:49.798139+09:00 42 Query alter /* gh-ost */ table `d1`.`_t1_gho` rename `t1`
2019-04-22T15:37:49.800834+09:00 40 Query unlock /* gh-ost */ tables

$ gh-ost .. --cut-over=atomic
..

2019-04-22T16:49:19.941893+09:00 81 Query select get_lock('gh-ost.81.lock', 0)
2019-04-22T16:49:19.949646+09:00 81 Query lock /* gh-ost */ tables `d1`.`t1` write, `d1`.`_t1_del` write
2019-04-22T16:49:20.942595+09:00 87 Query rename /* gh-ost */ table `d1`.`t1` to `d1`.`_t1_del`, `d1`.`_t1_gho` to `d1`.`t1`
2019-04-22T16:49:20.948139+09:00 81 Query unlock tables
2019-04-22T16:49:21.039657+09:00 81 Quit
atomic(こっちがデフォルト)の方が良さそう。

MySQL 8.0.15現在、 SET PERSIST_ONLY にはあんまり手を出さない方が良いと思う

$
0
0

TL;DR

  • 手を出さない方がいいのはSET PERSIST_ONLYの話で、SET PERSISTは手を出してもいいと思う
  • SET GLOBAL, SET PERSISTには値をセットするためのバリデーター(各サーバー変数ごとにある)が用意されているが、 SET PERSIST_ONLYはそのバリデーターを通らないため、不正な値を突っ込んでもエラーになってくれない
  • mysqldを再起動しようとした時に、 mysqld-auto.cnfSET PERSIST, SET PERSIST_ONLYの保管先)に不正な値が突っ込んであるとそれを適用できなくて mysqldが起動してくれない

最初に見つけた時は innodb_ft_aux_tableで見つけたのでそんなにアレじゃないかなと思ってたんだけれども、 SET GLOBAL, SET PERSISTには値をセットするためのバリデーター(各サーバー変数ごとにある)が用意されているが、 SET PERSIST_ONLYはそのバリデーターを通らないため、不正な値を突っ込んでもエラーになってくれない、という不具合を3月にレポートしていた。
数値型のやつに文字列型、みたいなやつはちゃんと弾いてくれる。

mysql80 8> SET PERSIST_ONLY max_connections= 'abc';

ERROR 1232 (42000): Incorrect argument type to variable 'max_connections'
問題になるのは、「文字列型を受け取るけどそれにもちゃんとルールがある」やつだ。
ばぐれぽで使っている innodb_ft_aux_tableは「フルテキストインデックスが存在するテーブル」を指定しなければならない。それ以外のテーブルを指定した場合はエラーになる。
mysql80 8> SHOW CREATE TABLE d1.t1\G -- フルテキストインデックスがない
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`val` varchar(32) DEFAULT NULL,
UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql80 8> SET GLOBAL innodb_ft_aux_table = 'd1/t1';
ERROR 1231 (42000): Variable 'innodb_ft_aux_table' can't be set to the value of 'd1/t1'
SET PERSIST_ONLYはこれをすり抜けるので、次に mysqldを起動しようとした時にこの不正な値がキマって起動しなくなる。
mysql80 8> SET PERSIST_ONLY innodb_ft_aux_table = 'd1/t1';
Query OK, 0 rows affected (0.00 sec)

mysql80 8> SELECT * FROM performance_schema.persisted_variables;
+---------------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+---------------------+----------------+
| innodb_ft_aux_table | d1/t1 |
+---------------------+----------------+
1 row in set (0.01 sec)
ちなみに「間違った!」というのがわかるのであれば、 RESET PERSISTステートメントでクリアできる。 mysqldを落とす前なら。
mysql80 8> RESET PERSIST innodb_ft_aux_table;
Query OK, 0 rows affected (0.00 sec)

mysql80 8> SELECT * FROM performance_schema.persisted_variables;
Empty set (0.00 sec)
話が厄介なのは、「ENUMなサーバー変数」で、コイツは「文字列でも数値でもセットできる」「本来正しい(?)値は文字列型」「数値を渡された時に文字列に変換しているのはどうやら変数セット時のバリデーター」というアレがあり、
mysql80 8> SELECT @@binlog_error_action;
+-----------------------+
| @@binlog_error_action |
+-----------------------+
| ABORT_SERVER |
+-----------------------+
1 row in set (0.00 sec)

mysql80 8> SET GLOBAL binlog_error_action = 0; -- SET GLOBALで数値をセットするじゃろ?
Query OK, 0 rows affected (0.00 sec)

mysql80 8> SELECT @@binlog_error_action; -- ちゃんと文字列で表示されるじゃろ?
+-----------------------+
| @@binlog_error_action |
+-----------------------+
| IGNORE_ERROR |
+-----------------------+
1 row in set (0.00 sec)
mysql80 8> SET PERSIST_ONLY binlog_error_action = 0; -- PERSIST_ONLYで数値をセットするじゃろ?
Query OK, 0 rows affected (0.00 sec)

mysql80 8> SELECT * FROM performance_schema.persisted_variables; -- 0のままじゃろ?
+---------------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+---------------------+----------------+
| binlog_error_action | 0 |
+---------------------+----------------+
1 row in set (0.00 sec)

mysql80 8> RESTART; -- キマるんじゃ
Query OK, 0 rows affected (0.01 sec)
エラーログはこんなんなりました。
無限にプロセスの起動失敗と再起動を繰り返しております(たぶん RESTARTステートメントじゃなくて systemctl restart mysqldとかだと綺麗に死んでくれるかな…わからん)
2019-04-23T08:29:56.321633Z 8 [System] [MY-011086] [Server] Received RESTART from user root.  Restarting mysqld (Version: 8.0.15).
..
2019-04-23T08:30:00.242090Z 0 [System] [MY-010910] [Server] /usr/mysql/8.0.15/bin/mysqld: Shutdown complete (mysqld 8.0.15) Source di
stribution.
2019-04-23T08:30:00.388768Z mysqld_safe Number of processes running now: 0
2019-04-23T08:30:00.393888Z mysqld_safe mysqld restarted

..
2019-04-23T08:30:12.326063Z 5 [ERROR] [MY-011268] [Server] Configuring persisted options failed: "Variable 'binlog_error_action' can't
be set to the value of '0'".
2019-04-23T08:30:12.326121Z 0 [ERROR] [MY-010175] [Server] Setting persistent options failed.
2019-04-23T08:30:12.440194Z mysqld_safe Number of processes running now: 0
2019-04-23T08:30:12.444490Z mysqld_safe mysqld restarted

..
2019-04-23T08:30:13.507286Z 5 [ERROR] [MY-011268] [Server] Configuring persisted options failed: "Variable 'binlog_error_action' can't
be set to the value of '0'".
2019-04-23T08:30:13.507327Z 0 [ERROR] [MY-010175] [Server] Setting persistent options failed.
2019-04-23T08:30:13.633597Z mysqld_safe Number of processes running now: 0
2019-04-23T08:30:13.638389Z mysqld_safe mysqld restarted

..
2019-04-23T08:30:15.253603Z 5 [ERROR] [MY-011268] [Server] Configuring persisted options failed: "Variable 'binlog_error_action' can't
be set to the value of '0'".
2019-04-23T08:30:15.253652Z 0 [ERROR] [MY-010175] [Server] Setting persistent options failed.
2019-04-23T08:30:15.364141Z mysqld_safe Number of processes running now: 0
2019-04-23T08:30:15.368649Z mysqld_safe mysqld restarted

..





MySQL 8.0.16にCHECK制約が来て、NOT ENFORCEDなんてものまでついてきた

$
0
0

TL;DR

  • MySQL 8.0.16についに CHECK制約が実装された
  • NOT ENFORCEDなんてオプションが “MySQLっぽい”
  • ちなみにCHECK制約の追加はオンラインDDL不可、引っぺがすのはできる

最初のFeature Requestから15年、ついにMySQL 8.0.16にCHECK制約が実装された。
アルファベットしか入ってほしくない valカラムに対して、ひらがなの “さん” とかが入力されるのを制限できる、みたいなヤーツ。
フツーの遊び方は @taka_yuki_04さんの記事に書いてある(というかドキュメントにも書いてある)ので、俺は「既存のテーブルにCHECK制約」ででも遊んでみる。
まず、こんなテーブルがあるじゃろ?
mysql80 8> SELECT @@version;
+-----------+
| @@version |
+-----------+
| 8.0.16 |
+-----------+
1 row in set (0.00 sec)

mysql80 8> CREATE TABLE t1 (num serial, val varchar(32));
Query OK, 0 rows affected (0.02 sec)

mysql80 8> INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'さん');
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql80 8> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | one |
| 2 | two |
| 3 | さん |
+-----+--------+
3 rows in set (0.00 sec)
valにアルファベット以外( val RLIKE '^[a-z]+$'を満たさない)を入れさせないようなCHECK制約をかけようとするじゃろ?
mysql80 8> ALTER TABLE t1 ADD CHECK(val RLIKE '^[a-z]+$');
ERROR 3819 (HY000): Check constraint 't1_chk_1' is violated.
既に制約違反しているから ALTER TABLEがエラーになるんじゃ。
という訳で制約に違反している行を消すじゃろ?
mysql80 8> DELETE FROM t1 WHERE num = 3;
Query OK, 1 row affected (0.00 sec)

mysql80 8> SELECT * FROM t1;
+-----+------+
| num | val |
+-----+------+
| 1 | one |
| 2 | two |
+-----+------+
2 rows in set (0.00 sec)
すると通るんじゃ。
mysql80 8> ALTER TABLE t1 ADD CHECK(val RLIKE '^[a-z]+$');
Query OK, 2 rows affected (0.03 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql80 8> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`val` varchar(32) DEFAULT NULL,
UNIQUE KEY `num` (`num`),
CONSTRAINT `t1_chk_1` CHECK (regexp_like(`val`,_utf8mb4'^[a-z]+$'))
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
CHECK制約があれば、 “さん” はそもそも入らないんじゃ。
mysql80 8> INSERT INTO t1 VALUES (3, 'さん');
ERROR 3819 (HY000): Check constraint 't1_chk_1' is violated.

mysql80 8> SELECT * FROM t1;
+-----+------+
| num | val |
+-----+------+
| 1 | one |
| 2 | two |
+-----+------+
2 rows in set (0.00 sec)
Σ(゚д゚lll) フツーのDBMSっぽい!
しかしまあ、MySQLっぽいと言われる謎の NOT ENFORCEDなんて句がついてきて
mysql80 8> ALTER TABLE t1 DROP CHECK t1_chk_1;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 8> ALTER TABLE t1 ADD CHECK(val RLIKE '^[a-z]+$') NOT ENFORCED;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 8> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`val` varchar(32) DEFAULT NULL,
UNIQUE KEY `num` (`num`),
CONSTRAINT `t1_chk_1` CHECK (regexp_like(`val`,_utf8mb4'^[a-z]+$')) /*!80016 NOT ENFORCED */
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql80 8> INSERT INTO t1 VALUES (3, 'さん');
Query OK, 1 row affected (0.01 sec)

mysql80 8> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | one |
| 2 | two |
| 3 | さん |
+-----+--------+
3 rows in set (0.00 sec)
:(;゙゚’ω゚’): なんと入る
誰得なんだろう…せめてワーニングとか出てくれるといいんだけどな。。
ところで、 ALTER TABLEでCHECK制約を追加するということは、それってオンラインでできるの的な疑問が湧いてくるんですが、2019年4月25日 20時10分02秒 JST 現在、InnoDBのオンラインDDL関連のページにはCHECK制約については特に記載なし。
mysql80 20> ALTER TABLE t1 ADD CHECK ('ワレワレ'<> '宇宙人'), ALGORITHM= INSTANT;
ERROR 1845 (0A000): ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY.

mysql80 20> ALTER TABLE t1 ADD CHECK ('ワレワレ'<> '宇宙人'), ALGORITHM= INPLACE;
ERROR 1845 (0A000): ALGORITHM=INPLACE is not supported for this operation. Try ALGORITHM=COPY.

mysql80 20> ALTER TABLE t1 ADD CHECK ('ワレワレ'<> '宇宙人'), ALGORITHM= COPY, LOCK= NONE;
ERROR 1846 (0A000): LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED.

mysql80 20> ALTER TABLE t1 ADD CHECK ('ワレワレ'<> '宇宙人'), ALGORITHM= COPY, LOCK= SHARED;
Query OK, 3 rows affected (0.04 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql80 20> ALTER TABLE t1 ADD CHECK ('ワレワレ'<> '宇宙人'), ALGORITHM= COPY, LOCK= EXCLUSIVE;
Query OK, 3 rows affected (0.03 sec)
Records: 3 Duplicates: 0 Warnings: 0
試した感じ、 ALGORITHM= COPY(テーブルコピー)で LOCK= SHARED(共有ロック、読めるけど書けない)か LOCK = EXCLUSIVE(排他ロック、読めないし書けない)しか選べない模様。。
このへん、せめてINPLACEとかできるようになるといいな(いくら全行チェックが必要とはいえ、テーブルコピーする必要はないはず…)

MySQL 8.0のSHOW TABLE STATUSが全然更新されない件

$
0
0

TL;DR


空っぽのテーブルを用意する。
mysql80 111566> SHOW TABLE STATUS\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: NULL
Create_time: 2019-05-16 15:41:52
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
データを1万行くらい入れる。
$ for n in $(seq 1 10000) ; do mysql80 -e "INSERT INTO t1 VALUES ($n, '')" d1; done

mysql80 111566> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
| 10000 |
+----------+
1 row in set (0.00 sec)
でも SHOW TABLE STATUSの結果は変わらない。。
mysql80 111566> SHOW TABLE STATUS\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: NULL
Create_time: 2019-05-16 15:41:52
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
ちなみに SHOW CREATE TABLEの方の AUTO_INCREMENTの値はちゃんと増えてる。
mysql80 111566> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`val` varchar(32) DEFAULT NULL,
UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
これはMySQL 8.0.3から information_schema_stats_expiryなるオプションが増えてからの挙動で、どうも information_schema.tablesに含まれるほとんどの情報は mysql.table_statsテーブルにキャッシュされ、そのキャッシュがexpireされるまではずっとその値を使う様子( mysql.table_statsは隠れシステムテーブルなので、フツーにSQLを使っているだけだとそのテーブルは見えない)
mysql80 111566> SELECT * FROM mysql.table_stats;
ERROR 3554 (HY000): Access to data dictionary table 'mysql.table_stats' is rejected.
オプションにも書いてある通り、単位は秒でデフォルトは86400秒(= 24時間)
つまりどれだけテーブルを更新しようと、 information_schema.tablesSHOW TABLE STATUSの値は「最後に mysql.table_statsが更新されてから24時間」は全く変更されない(ように見える)
コード的には このへんでキャッシュがexpireされてるかとかそのへんを判定してて、元をたどって進んでいくと ここらへんでキャッシュを更新する処理に突入している。
(現在時刻 - キャッシュの更新時刻) > information_schema_stats_expiryになった場合はキャッシュを更新してその結果を、 information_schema_stats_expiry = 0の場合はそもそも mysql.table_statsテーブルをキャッシュとして 使わないみたいな動作になるようだ。
mysql80 111566> SET SESSION information_schema_stats_expiry= 1;
Query OK, 0 rows affected (0.01 sec)

mysql80 111566> SHOW TABLE STATUS\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 10000
Avg_row_length: 32
Data_length: 327680
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: 10001
Create_time: 2019-05-16 15:41:52
Update_time: 2019-05-16 16:02:34
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
セッションスコープ, グローバルスコープが両方あるタイプの変数なので、監視用のセッションにだけ押し込んでおけばいいかと。
しかし SET_VAR構文は使えないらしいので残念。

エラーログに MY-010956 と MY-010957 が出まくるはなし

$
0
0

TL;DR

  • MySQL 8.0同士でレプリケーションを組んでいると、スレーブから以下のような MY-010956MY-010957がぼこぼこエラーログに吐かれることがある
2019-05-20T14:25:17.121864+09:00 5 [Warning] [MY-010956] [Server] Invalid replication timestamps: original commit timestamp is more recent than the immediate commit timestamp. This may be an issue if delayed replication is active. Make sure that servers have their clocks set to the correct time. No further message will be emitted until after timestamps become valid again.
2019-05-20T14:25:17.177804+09:00 5 [Warning] [MY-010957] [Server] The replication timestamps have returned to normal values.

日々の覚書: MySQL 8.0.1でバイナリーログに original_commit_timestamp と immediate_commit_timestamp が追加されたという記事で書いたように、MySQL 8.0からはバイナリーログのイベントに(正確には、トランザクションがコミットされた時にイベントに先行して記録されるGtid_log_eventに)マイクロ秒単位でタイムスタンプが記録される。
このタイムスタンプが、 original_commit_timestamp > immediate_commit_timestamp(スレーブで実行されたタイムスタンプよりマスターで実行されたタイムスタンプが未来)になってしまった時にエラーログに記録されるワーニングが MY-010956MY-010956が記録された後に original_commit_timestamp <= immediate_commit_timestampに戻った時に記録されるワーニングが MY-010957
ところで、運用しているMySQLの載っているマシンが「マイクロ秒単位で」正しいシステムクロックに設定されていることに自信のある方はいらっしゃるだろうか?
俺には自信はない、というか、いいとこ1桁ミリ秒単位ならズレるだろうとも思っている。ホストとクロックを定期的に同期するVMならまだしも(試してない)、NTPではこのくらいの精度は限界なんではなかろうか。
というわけで、だ。
$ date "+%s.%N" ### at master
1558337325.766668654

$ date "+%s.%N" ### at slave
1558337325.767114036
こんな状況は起こりえる。500usマスターが進んでいる。
この状態でマスターに更新を叩き込んで、 “500us以内にそのバイナリーログの適用が終わった場合” に MY-010956が発火する(適用に500us以上かかれば、再びスレーブのタイムスタンプがマスターのそれを追い越すので何も起こらない。これが出まくったってことはそれくらいのレイテンシーで更新できてるってことで望ましいことではある)
そして MY-010956が発火して以降( MY-010956は一度発火すると、次にタイムスタンプの順序が戻る= MY-010957が出るまでは連続で発火しない)、 “適用に500us以上かかったバイナリーログのイベント” が来ると MY-010957が着火し、次に500us未満で適用が終わったイベントが…と、延々発火し得る。
きっちり一致させられないにしろ、マスターのシステムクロックを進めればいいのでは? と思ったりしたけど、いつNTPでじりじりと補正されるかわからないし、マスターを切り替えちゃったらまた出ることには変わりはないし、だいたいmysqldを起動したまま手で時刻を修正するとか嫌な予感しかしない。
ワーニングだし「これはどう考えても出る時は出る」って感じなので無視するのがいいんだろうけれど、 log_error_verbosityを1(エラーのみロギング、ワーニング以下を無視する)にするのはちょっと大雑把かなと思うし、かといってエラーログはでっかくなるし。
閾値を設定できるようにしませんか? というFeature Requestがこれ。
いいな(?)と思ったら “Affects me” ください…

第一印象 of MySQL Ripple

$
0
0

TL;DR

  • MySQL Rippleを試してそっと閉じたメモ。
  • 多分こういう状態で使うんだと思う
    • マスターの部分はよしなに冗長化されていると思いねえ
    • (おそらく準同期レプリケーションで)マスターに直接スレーブをぶら下げると死んじゃうようなケース
    • あと、このMySQL Ripple自体もたぶん多段構成するんだろうな、とデフォルトのパラメーター(主に -ripple-master-port)を見て思う

ビルド

Dockerイメージで起動

$ docker run -v $PWD:/root/binlog mysql-ripple \
--ripple-server-address=0.0.0.0 \
--ripple-master-address=172.17.0.3 \
--ripple-master-port=3306 \
--ripple-datadir=/root/binlog
ホスト側にbinlogの保管ディレクトリーをマウントしてるのは深い意味はない。
-ripple-server-addressは外側のIPアドレスなり0.0.0.0なりにしておかないと他所のホストからつつけない(デフォルトがlocalhostなので)
-ripple-master-portのデフォルトが3306 ではなく MySQL Rippleが使う51005なので、MySQL Ripple同士で多段構成するのが前提なのかも知れない。

コマンドラインクライアントで接続してみる

  • デフォルトのポートは51005
  • 接続すること自体はできるが、クエリーのパースはできないので、何かコマンドやSQLを叩いても大抵エラーになる
$ mysql -h172.17.0.4 -P51005
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 5.6.0-ripple ripple version comment

mysql> status
ERROR 2013 (HY000): Lost connection to MySQL server during query

mysql> SHOW MASTER LOGS;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 28
Current database: *** NONE ***

ERROR 2013 (HY000): Lost connection to MySQL server during query

マスターにぶら下げてみる

  • gtid_mode= ONのみのサポート
  • master_log_fileとか設定するオプションはないし、 gtid_mode != ONなところにぶら下げようとすると怒られる
I0603 09:05:54.490025     7 mysql_client_connection.cc:148] connected to host: 172.17.0.5, port: 3306
I0603 09:05:54.493810 7 mysql_master_session.cc:137] Connected to host: 172.17.0.5, port: 3306, server_id: 102, server_name:
W0603 09:05:54.495193 7 mysql_master_session.cc:197] master does not support semi sync
I0603 09:05:54.495231 7 mysql_master_session.cc:206] start replicating from 'f9874359-85da-11e9-a9fb-0242ac110003:0-0-4'
I0603 09:05:54.495659 7 mysql_master_session.cc:229] Master session entering main loop
E0603 09:05:54.495913 7 mysql_master_session.cc:296] Failed to read packet: Got error reading packet from server: The replication sender thread cannot start in AUTO_POSITION mode: this server has GTID_MODE = OFF instead of ON.
I0603 09:05:54.495939 7 binlog.cc:675] Connection closed last position binlog file: binlog.000000:1608, gtid: 0-0-4

スレーブをぶら下げてみる

  • こっちももちろん gtid_mode= ONのみのサポート
mysql> CHANGE MASTER TO master_host= '172.17.0.4', master_port= 51005, master_user= 'root' /* Non-GTID */;
Query OK, 0 rows affected, 2 warnings (0.04 sec)

mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 172.17.0.4
Master_User: root
Master_Port: 51005
..
Last_IO_Errno: 1593
Last_IO_Error: The replication receiver thread cannot start because the master has GTID_MODE = ON and this server has GTID_MODE = OFF.
Last_SQL_Errno: 0
Last_SQL_Error:
..
  • gtid_mode >= ON_PERMISSIVEにして master_auto_position= 1で設定すると動く
    • ちゃんとデータも転送されてきた。
mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)

mysql> CHANGE MASTER TO master_auto_position = 1;
Query OK, 0 rows affected (0.01 sec)

mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.17.0.4
Master_User: root
Master_Port: 51005
..
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
..

MySQL Rippleの吐いたバイナリログファイル

  • よく訓練されたMySQLerにはわかると思うんだけど、フツーのバイナリログとちょっと違う。
    • ちなみにマスターはMySQL 5.7.26でbinlog_format= ROW
$ xxd binlog.000000
0000000: fe62 696e 0000 0000 0f53 b601 00f0 0000 .bin.....S......
0000010: 00f4 0000 0000 0004 0035 2e36 2e30 2d72 .........5.6.0-r
0000020: 6970 706c 6500 0000 0000 0000 0000 0000 ipple...........
..
00000f0: 0413 0400 0000 0000 0fea 0300 0073 0000 .............s..
0000100: 0067 0100 0000 0004 0035 2e37 2e32 362d .g.......5.7.26-
0000110: 6c6f 6700 0000 0000 0000 0000 0000 0000 log.............
0000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000130: 0000 0000 0000 0000 0000 008a dcf4 5c13 ..............\.
0000140: 380d 0008 0012 0004 0404 0412 0000 5f00 8............._.
0000150: 041a 0800 0000 0808 0802 0000 000a 0a0a ................
0000160: 2a2a 0012 3400 0000 0000 0096 53b6 0100 **..4.......S...
0000170: 2b00 0000 9201 0000 0000 ff00 0000 81dd +...............
0000180: f45c 78c0 0e77 0815 e466 fa6c bd26 d9f5 .\x..w...f.l.&..
0000190: b607 3200 0000 b929 f7d4 aa18 4bdf 954e ..2....)....K..N
  • マスターから取り出した生のバイナリログはこちら
$ xxd 935510b1506e-bin.000001
0000000: fe62 696e 8adc f45c 0fea 0300 0077 0000 .bin...\.....w..
0000010: 007b 0000 0001 0004 0035 2e37 2e32 362d .{.......5.7.26-
0000020: 6c6f 6700 0000 0000 0000 0000 0000 0000 log.............
..
00000f0: 0000 0000 0000 0200 001f 0000 0000 0000 ................
0000100: 0120 00a0 5500 0000 0006 0373 7464 042d . ..U......std.-
0000110: 002d 002d 000c 0164 3100 6431 0043 5245 .-.-...d1.d1.CRE
0000120: 4154 4520 4441 5441 4241 5345 2064 31e9 ATE DATABASE d1.
0000130: 5801 e9d4 dcf4 5c21 ea03 0000 4100 0000 X.....\!....A...
0000140: 7401 0000 0000 01f9 8743 5985 da11 e9a9 t........CY.....
0000150: fb02 42ac 1100 0302 0000 0000 0000 0002 ..B.............
0000160: 0100 0000 0000 0000 0200 0000 0000 0000 ................
0000170: 69f5 fb70 d4dc f45c 02ea 0300 0070 0000 i..p...\.....p..
0000180: 00e4 0100 0000 0004 0000 0000 0000 0002 ................
0000190: 0000 1f00 0000 0000 0001 2000 a055 0000 .......... ..U..
00001a0: 0000 0603 7374 6404 2d00 2d00 2d00 0c01 ....std.-.-.-...
00001b0: 6431 0064 3100 6372 6561 7465 2074 6162 d1.d1.create tab
00001c0: 6c65 2074 3120 286e 756d 2069 6e74 2c20 le t1 (num int,
00001d0: 7661 6c20 7661 7263 6861 7228 3332 2929 val varchar(32))
..
binlog_format= ROWでもフツーに記録されるはずのDDLが目視できない。 mysqlbinlogで食わせようとしても食えない。
どうやら暗号化されているっぽい。
$ docker run mysql-ripple --help | less
..
-ripple_encryption_scheme (Encryption scheme used by ripple for local
binlogs) type: int32 default: 255
..
-ripple-encryption-scheme=0にすると暗号化されなくなって読めるようになる。
ただし rippledさん、結構バイナリログをバッファリングする(メモリー上にため込んで、 bin.000000になかなか吐かない)ので、このあたりの動作を試して変だなと思ったらコンテナを止めてみるとフラッシュされたりする。
あと、 -ripple-encryption-scheme=255で吐かせたバイナリログが残っているとそれに合わせて(?)同じ方式で暗号化し続けるっぽいので、 bin.000000とかも削除して吸わせなおした方がいいと思われる。試すなら。
バイナリログを暗号化しておけるのは良いとして “MySQL Rippleからなら特に気にせず吸い上げられちゃう” ような気がするので、今のところあんまり期待してはいけないのかも知れない。難読化、くらいのつもりで。

MySQL 8.0.17でついにCloneプラグインが入った

$
0
0

TL;DR


取り敢えず CLONE LOCAL DATAの方。
準備は INSTALL PLUGINで一発。
mysql80 35> INSTALL PLUGIN CLONE SONAME "mysql_clone.so";
Query OK, 0 rows affected (0.01 sec)

mysql80 35> SHOW PLUGINS;
+---------------------------------+----------+--------------------+-----------------+---------+
| Name | Status | Type | Library | License |
+---------------------------------+----------+--------------------+-----------------+---------+
..
| clone | ACTIVE | CLONE | mysql_clone.so | GPL |
+---------------------------------+----------+--------------------+-----------------+---------+
46 rows in set (0.00 sec)
使い方も簡単。 CLONE LOCAL DATA DIRECTORY /tmp/cloneとかこれだけ。
ディレクトリーは書き込み権限があれば大丈夫。 CLONE LOCAL DATAステートメントを実行するアカウントには BACKUP_ADMIN権限が要るが、それ以外(たとえば、グローバルSELECTとかFLUSHとか)は要らない。
mysql80 35> create user yoku0825;
Query OK, 0 rows affected (0.01 sec)

mysql80 35> grant backup_admin on *.* TO yoku0825;
Query OK, 0 rows affected (0.00 sec)

mysql80 70> SHOW GRANTS;
+---------------------------------------------+
| Grants for yoku0825@% |
+---------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`%` |
| GRANT BACKUP_ADMIN ON *.* TO `yoku0825`@`%` |
+---------------------------------------------+
2 rows in set (0.00 sec)

mysql80 70> CLONE LOCAL DATA DIRECTORY '/tmp/clone';
Query OK, 0 rows affected (3.66 sec)
tpcc_start -u tpcc -w 10 -S /usr/mysql/8.0.17/data/mysql.sock -d tpcc -l 1200 -r 0しながらローカルクローンしてみる。
mysql80 35> CLONE LOCAL DATA DIRECTORY '/tmp/clone';
Query OK, 0 rows affected (7.78 sec)

$ ll /tmp/clone
total 161804
drwxr-x--- 2 yoku0825 yoku0825 85 Jul 24 18:31 #clone
drwxr-x--- 2 yoku0825 yoku0825 19 Jul 24 18:31 d1
-rw-r----- 1 yoku0825 yoku0825 12582912 Jul 24 18:31 ibdata1
-rw-r----- 1 yoku0825 yoku0825 50331648 Jul 24 18:31 ib_logfile0
-rw-r----- 1 yoku0825 yoku0825 50331648 Jul 24 18:31 ib_logfile1
drwxr-x--- 2 yoku0825 yoku0825 6 Jul 24 18:31 mysql
-rw-r----- 1 yoku0825 yoku0825 24117248 Jul 24 18:31 mysql.ibd
drwxr-x--- 2 yoku0825 yoku0825 27 Jul 24 18:31 sys
drwxr-x--- 2 yoku0825 yoku0825 4096 Jul 24 18:31 tpcc
-rw-r----- 1 yoku0825 yoku0825 14680064 Jul 24 18:31 undo_001
-rw-r----- 1 yoku0825 yoku0825 13631488 Jul 24 18:31 undo_002

$ du -sh /tmp/clone
1.1G /tmp/clone
その間のCommit per Secはこんな感じになった。
2019-07-24 18:30:33     value:0/1s      variable_name:Handler_commit
2019-07-24 18:30:34 value:3365/1s variable_name:Handler_commit
2019-07-24 18:30:35 value:4616/1s variable_name:Handler_commit
2019-07-24 18:30:36 value:5040/1s variable_name:Handler_commit
2019-07-24 18:30:37 value:5043/1s variable_name:Handler_commit
2019-07-24 18:30:38 value:4081/1s variable_name:Handler_commit
2019-07-24 18:30:39 value:5448/1s variable_name:Handler_commit
2019-07-24 18:30:40 value:4167/1s variable_name:Handler_commit
2019-07-24 18:30:41 value:5259/1s variable_name:Handler_commit
2019-07-24 18:30:42 value:4897/1s variable_name:Handler_commit
2019-07-24 18:30:43 value:4807/1s variable_name:Handler_commit
2019-07-24 18:30:44 value:5556/1s variable_name:Handler_commit
2019-07-24 18:30:45 value:5892/1s variable_name:Handler_commit
2019-07-24 18:30:46 value:5263/1s variable_name:Handler_commit
2019-07-24 18:30:47 value:5093/1s variable_name:Handler_commit
2019-07-24 18:30:48 value:5727/1s variable_name:Handler_commit
2019-07-24 18:30:49 value:5506/1s variable_name:Handler_commit
2019-07-24 18:30:50 value:5612/1s variable_name:Handler_commit
2019-07-24 18:30:51 value:6066/1s variable_name:Handler_commit
2019-07-24 18:30:52 value:4491/1s variable_name:Handler_commit
2019-07-24 18:30:53 value:4460/1s variable_name:Handler_commit
2019-07-24 18:30:54 value:4901/1s variable_name:Handler_commit
2019-07-24 18:30:56 value:4807/1s variable_name:Handler_commit
2019-07-24 18:30:57 value:5649/1s variable_name:Handler_commit
2019-07-24 18:30:58 value:4307/1s variable_name:Handler_commit
2019-07-24 18:30:59 value:4661/1s variable_name:Handler_commit
2019-07-24 18:31:00 value:4864/1s variable_name:Handler_commit
2019-07-24 18:31:01 value:5460/1s variable_name:Handler_commit
2019-07-24 18:31:02 value:4245/1s variable_name:Handler_commit
2019-07-24 18:31:03 value:4109/1s variable_name:Handler_commit
2019-07-24 18:31:04 value:1661/1s variable_name:Handler_commit <-- CLONE LOCAL DATA開始
2019-07-24 18:31:05 value:1950/1s variable_name:Handler_commit
2019-07-24 18:31:06 value:2399/1s variable_name:Handler_commit
2019-07-24 18:31:07 value:2168/1s variable_name:Handler_commit
2019-07-24 18:31:08 value:2121/1s variable_name:Handler_commit <-- CLONE LOCAL DATA終了
2019-07-24 18:31:09 value:4773/1s variable_name:Handler_commit
2019-07-24 18:31:10 value:4563/1s variable_name:Handler_commit
2019-07-24 18:31:11 value:4592/1s variable_name:Handler_commit
2019-07-24 18:31:12 value:5245/1s variable_name:Handler_commit
2019-07-24 18:31:13 value:4650/1s variable_name:Handler_commit
2019-07-24 18:31:14 value:5380/1s variable_name:Handler_commit
2019-07-24 18:31:15 value:4874/1s variable_name:Handler_commit
2019-07-24 18:31:16 value:4629/1s variable_name:Handler_commit
2019-07-24 18:31:17 value:4493/1s variable_name:Handler_commit
優秀なのでは!?
エラーログはこんな感じに出てた。Noteなので log_error_verbosity= 3が必要。
2019-07-24T18:31:03.279399+09:00 35 [Note] [MY-013457] [InnoDB] Clone Begin Master Task by root@localhost
2019-07-24T18:31:03.279482+09:00 35 [Note] [MY-013457] [InnoDB] Clone Apply Master Loop Back
2019-07-24T18:31:03.279494+09:00 35 [Note] [MY-013457] [InnoDB] Clone Apply Begin Master Task
2019-07-24T18:31:03.279727+09:00 35 [Note] [MY-013458] [InnoDB] Clone set state change ACK: 1
2019-07-24T18:31:03.279749+09:00 35 [Note] [MY-013458] [InnoDB] Clone Master received state change ACK
2019-07-24T18:31:03.279779+09:00 35 [Note] [MY-013458] [InnoDB] Clone State Change : Number of tasks = 1
2019-07-24T18:31:03.279793+09:00 35 [Note] [MY-013458] [InnoDB] Clone State BEGIN FILE COPY
2019-07-24T18:31:03.295060+09:00 35 [Note] [MY-011845] [InnoDB] Clone Start PAGE ARCH : start LSN : 1354569844, checkpoint LSN : 13458
13923
2019-07-24T18:31:03.295198+09:00 35 [Note] [MY-013458] [InnoDB] Clone State FILE COPY : 25 chunks, chunk size : 64 M
2019-07-24T18:31:03.295584+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State Change : Number of tasks = 1
2019-07-24T18:31:03.295609+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State FILE COPY:
2019-07-24T18:31:03.295674+09:00 35 [Note] [MY-011978] [InnoDB] Clone estimated size: 1.00 GiB Available space: 3.06 GiB
2019-07-24T18:31:03.775468+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 20% completed.
2019-07-24T18:31:04.884874+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 40% completed.
2019-07-24T18:31:06.387871+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 60% completed.
2019-07-24T18:31:07.846118+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 80% completed.
2019-07-24T18:31:08.347343+09:00 35 [Note] [MY-013272] [Clone] Plugin Clone reported: 'Client: Tune Threads from: 1 to: 2 prev: 1 target: 2.'
2019-07-24T18:31:08.348284+09:00 0 [Note] [MY-013457] [InnoDB] Clone Begin Task ID: 1
2019-07-24T18:31:08.348325+09:00 0 [Note] [MY-013457] [InnoDB] Clone Apply Begin Task ID: 1
2019-07-24T18:31:08.521355+09:00 35 [Note] [MY-013272] [Clone] Plugin Clone reported: 'Client: Total Data: 928 MiB @ 177 MiB/sec, Network: 0 MiB @ 0 MiB/sec.'
2019-07-24T18:31:08.521409+09:00 35 [Note] [MY-013458] [InnoDB] Clone set state change ACK: 2
2019-07-24T18:31:08.521428+09:00 35 [Note] [MY-013458] [InnoDB] Clone Master received state change ACK
2019-07-24T18:31:08.521444+09:00 35 [Note] [MY-013458] [InnoDB] Clone State Change : Number of tasks = 2
2019-07-24T18:31:08.521455+09:00 35 [Note] [MY-013458] [InnoDB] Clone State BEGIN PAGE COPY
2019-07-24T18:31:08.544317+09:00 35 [Note] [MY-011840] [InnoDB] Clone Start LOG ARCH : start LSN : 1347785138
2019-07-24T18:31:08.544373+09:00 35 [Note] [MY-011846] [InnoDB] Clone Stop PAGE ARCH : end LSN : 1347785138, log sys LSN : 1355428039
2019-07-24T18:31:08.544804+09:00 35 [Note] [MY-013458] [InnoDB] Clone State PAGE COPY : 1551 pages, 78 duplicate pages, 1 chunks, chunk size : 64 M
2019-07-24T18:31:08.646061+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State Change : Number of tasks = 2
2019-07-24T18:31:08.646130+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State PAGE COPY:
2019-07-24T18:31:10.222406+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 100% completed.
2019-07-24T18:31:10.222447+09:00 35 [Note] [MY-013272] [Clone] Plugin Clone reported: 'Client: Total Data: 952 MiB @ 137 MiB/sec, Network: 0 MiB @ 0 MiB/sec.'
2019-07-24T18:31:10.222461+09:00 35 [Note] [MY-013458] [InnoDB] Clone set state change ACK: 3
2019-07-24T18:31:10.222470+09:00 35 [Note] [MY-013458] [InnoDB] Clone Master received state change ACK
2019-07-24T18:31:10.222480+09:00 35 [Note] [MY-013458] [InnoDB] Clone State Change : Number of tasks = 2
2019-07-24T18:31:10.222487+09:00 35 [Note] [MY-013458] [InnoDB] Clone State BEGIN REDO COPY
2019-07-24T18:31:10.340523+09:00 35 [Note] [MY-011841] [InnoDB] Clone Stop LOG ARCH : end LSN : 1356084650
2019-07-24T18:31:10.340576+09:00 35 [Note] [MY-013458] [InnoDB] Clone State REDO COPY : 3 chunks, chunk size : 64 M
2019-07-24T18:31:10.442258+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State Change : Number of tasks = 2
2019-07-24T18:31:10.442296+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State REDO COPY:
2019-07-24T18:31:10.450725+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 33% completed.
2019-07-24T18:31:10.450778+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 66% completed.
2019-07-24T18:31:10.450791+09:00 35 [Note] [MY-013458] [InnoDB] Stage progress: 100% completed.
2019-07-24T18:31:10.550946+09:00 35 [Note] [MY-013272] [Clone] Plugin Clone reported: 'Client: Total Data: 960 MiB @ 132 MiB/sec, Network: 0 MiB @ 0 MiB/sec.'
2019-07-24T18:31:10.550995+09:00 35 [Note] [MY-013458] [InnoDB] Clone set state change ACK: 4
2019-07-24T18:31:10.551009+09:00 35 [Note] [MY-013458] [InnoDB] Clone Master received state change ACK
2019-07-24T18:31:10.551019+09:00 35 [Note] [MY-013458] [InnoDB] Clone State Change : Number of tasks = 2
2019-07-24T18:31:10.551026+09:00 35 [Note] [MY-013458] [InnoDB] Clone State DONE
2019-07-24T18:31:10.654535+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State Change : Number of tasks = 2
2019-07-24T18:31:10.654584+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State FLUSH DATA:
2019-07-24T18:31:10.697683+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State FLUSH REDO:
2019-07-24T18:31:11.015694+09:00 35 [Note] [MY-013458] [InnoDB] Clone Apply State DONE
2019-07-24T18:31:11.059224+09:00 0 [Note] [MY-013457] [InnoDB] Clone Apply End Task ID: 1 Passed, code: 0:
2019-07-24T18:31:11.059277+09:00 0 [Note] [MY-013457] [InnoDB] Clone End Task ID: 1 Passed, code: 0:
2019-07-24T18:31:11.059677+09:00 35 [Note] [MY-013457] [InnoDB] Clone Apply End Master Task ID: 0 Passed, code: 0:
2019-07-24T18:31:11.059910+09:00 35 [Note] [MY-013457] [InnoDB] Clone End Master Task ID: 0 Passed, code: 0:
取り敢えず感動をお伝えしたかった。

MySQL 8.0.17のCLONE INSTANCE FROMを使うとすごく楽にレプリケーションスレーブが作れる

$
0
0

TL;DR

  • 日々の覚書: MySQL 8.0.17でついにCloneプラグインが入ったで紹介した CLONE LOCAL DATAは現在のdatadirをローカルのファイルシステムに一貫性のある形でコピーするステートメントだった
  • これをスレーブになるサーバーに転送してやってももちろん良いんだけど、そこまで一括でやってくれる CLONE INSTANCE FROMのステートメントも存在する
  • スレーブ増やすのが捗る

CLONE INSTANCE FROMを使うためには、データのコピー元( ドキュメント上では ドナー(Donor))とコピー先(同 レシピエント(Recipient))それぞれのMySQLでCloneプラグインが有効化されていないといけない。
donor> INSTALL PLUGIN clone SONAME 'mysql_clone.so';
recipient> INSTALL PLUGIN clone SONAME 'mysql_clone.so';
ちなみにレシピエントでCloneプラグインが有効になってないとそもそもCLONEステートメントが転けるし、
recipient> CLONE INSTANCE FROM cloner@127.0.0.1:12345 IDENTIFIED BY '';
ERROR 1524 (HY000): Plugin 'clone' is not loaded
ドナーで有効になっていないと
recipient> CLONE INSTANCE FROM cloner@127.0.0.1:12345 IDENTIFIED BY '';
ERROR 3862 (HY000): Clone Donor Error: 1524 : Plugin 'clone' is not loaded.
とそれぞれ違うエラーが出る(レシピエントとしてのエラー3862と、ドナーが返したであろうエラーの1524が1つのメッセージに混じっていてちょっと面白い)
Cloneプラグインを両方で有効化したら、ドナーになるインスタンスでアカウントを作成する。必要になる権限は BACKUP_ADMINのみ。ここは CLONE LOCAL DATAの時といっしょ。
donor> CREATE USER clone_user;
Query OK, 0 rows affected (0.01 sec)

donor> GRANT BACKUP_ADMIN ON *.* TO clone_user;
Query OK, 0 rows affected (0.00 sec)
レシピエント側で CLONE INSTANCE FROMを実行する。ユーザー名、ホスト名やIPアドレス、ポートの部分は クォートしない ( CHANGE MASTER TOはクォートしないといけないのに…)、パスワードを設定していなくても(そんなことがあるのか) IDENTIFIED BY ..はつけないと構文エラーになる、あたりが注意点。
ポート番号も、デフォルトの3306でも省略できずちゃんとフルで書かないとシンタックスエラーって言われる。
recipient> CLONE INSTANCE FROM clone_user@172.17.0.10:3306 IDENTIFIED BY '';
ERROR 3869 (HY000): Clone system configuration: 172.17.0.10:3306 is not found in clone_valid_donor_list:
おっとエラー。
CLONE INSTANCE FROMで指定する先は clone_valid_donor_listというパラメーターにセットしてやらないといけない。
recipient> SET GLOBAL clone_valid_donor_list = '172.17.0.10:3306';
Query OK, 0 rows affected (0.00 sec)

recipient> CLONE INSTANCE FROM clone_user@172.17.0.10:3306 IDENTIFIED BY '';
これでデータのコピーが始まる。
コピーが終わると勝手に RESTARTステートメントが走っておしまい。
RESTARTの条件である「 mysqld_safesystemdにぶら下がっていること」が満たされてないとシャットダウンだけするっぽい)
再起動してきたら、 performance_schema.clone_statusあたりからbinlogの情報とかgtid_executedとかが拾えるのでいつも通り CHANGE MASTER TOでレプリケーションを構成してやればスレーブが出来上がる。
レプリケーション用のアカウントとかはよしなに今までと同じ感じで作っておく。
donor> use performance_schema
Database changed

donor> show tables like '%clone%';
+----------------------------------------+
| Tables_in_performance_schema (%clone%) |
+----------------------------------------+
| clone_progress |
| clone_status |
+----------------------------------------+
2 rows in set (0.01 sec)

donor> SELECT * FROM clone_status;
+------+------+-----------+-------------------------+-------------------------+-----------------+----------------+----------+---------------+---------------+-----------------+-------------------------------------------------------------------------------------------+
| ID | PID | STATE | BEGIN_TIME | END_TIME | SOURCE | DESTINATION | ERROR_NO | ERROR_MESSAGE | BINLOG_FILE | BINLOG_POSITION | GTID_EXECUTED |
+------+------+-----------+-------------------------+-------------------------+-----------------+----------------+----------+---------------+---------------+-----------------+-------------------------------------------------------------------------------------------+
| 1 | 0 | Completed | 2019-07-26 10:49:46.370 | 2019-07-26 11:01:59.458 | 172.17.0.10:3306 | LOCAL INSTANCE | 0 | | binlog.000003 | 155 | 7470a831-4aed-11e9-b334-70106f4f8c56:1-8218993,
ee20abe3-33e8-11e9-8a63-1c98ec2973a4:1-16 |
+------+------+-----------+-------------------------+-------------------------+-----------------+----------------+----------+---------------+---------------+-----------------+-------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

donor> CHANGE MASTER TO master_host= '172.17.0.1', master_port= 3306, master_user = 'replicator', master_password= '', master_log_file= 'binlog.000003', master_log_pos= 155;
gtid_mode=ONならもっと楽ですね! 捗る!!!

MySQLが刺さった時にざっと見るためのメモ

$
0
0

TL;DR

  • mysqldが 本当に刺さって動作を停止している時のためのメモです。重いだけの時に使うものじゃない。
  • ざっと見る方法であって、解決方法じゃない。

まずは慌てず騒がずエラーログを見る。クラッシュしてるログが出てるならそれはそれでOK。mysqld_safeとか systemdを使っている場合、 mysqldが自動再起動されているかも知れない。クラッシュリカバリーの真っ最中はプロセスが浮いていても接続できないので、クラッシュリカバリー中だったら見守る。
OOM Killerに殺された場合はエラーログに何も出ずにsyslogだけ吐くので、 /var/log/messagesあたりもチラ見しておくと吉。
特にクラッシュはしていないようであれば、 dstatでCPU使用率やDisk書き込み、IOPSあたりを見る。
Disk書き込み量やIOPSがガリガリ増えているなら、datadirにでもいって ls -ltrとかで更新がされてるかどうか見る。バイナリーログやInnoDBログが更新されているなら、刺さっててもまだ何かしている(=そのまま kill -9でバッサリやると設定によってはファイルが壊れる)可能性がある。
設定によってはここでコアファイルをガリガリ書いている最中かも知れない。
その場合、ストレージ容量を食い破られないようにだけ注意(コアを吐き終わるまでmysqldが終了せず、コアは容量が足りなくて吐けず…でいつまでも終わらない)。
食い破られそうなら mysqldkill -9で仕留めるしかない。
Diskが何も書いてなさげでCPUだけ回っているならバッファプール周りで何か起こっていそうな予感。体感ではチェンジバッファとかページクリーナーがぐるぐるしていることが多い。 poor man’s profilerやPercona Toolkit化された pt-pmpで見てもいいけど、見たところで多分幸せにはならない(解決方法はわからないから)
ただこのケースは kill -9でも壊れる可能性は低い(体感)、ただしMyISAMとMroonga、オメーは別だ。
解析のためにコアファイルを取りたければ ulimit -cとかabrtdの設定を確認してから kill -6。容量には注意。見かけ上のサイズにVSZ、実容量でRSSを取る。
Diskにも書いてないしCPUも回っていないことがまれにだがよくある(特に5.7でデカめのインスタンスに mysqladmin shutdownを放り投げたあと…)が、その場合はなんか変なところで刺さっているんだろう…コア取ったりpt-pmpでスタックトレースを送り付けた方がいいかも知れない。
復旧(InnoDBのみ、安全なパラメーター、クラッシュリカバリーが成功していればプロセスを起動するだけでもいい)させたら今度は中身を見ていく。バックアップや他のスレーブからデータをコピーしてくる場合、エラーログをどこかに残しておかないと追うことも出来なくなるので注意。
基本はエラーログ。綺麗に(?)クラッシュしてる場合は “mysqld got signal x” のログがあるはず。これが無くていきなりプロセスが消えている場合はOOM Killerに殺された可能性が高い。
06:37:30 UTC - mysqld got signal 6 ;
Dive into MySQL Error - Speaker Deckでしゃべったけれど、 “signal 6” はアサーション(主にInnoDB)、 “signal 11” はバグな傾向がある。
“This could be because you hit a bug.” って書いてあるけどこれは単なるハードコード文なので真に受けてはいけない。
再起動しようとしてまた “signal 6” で落ちるなら、InnoDBのデータが破損している気がする。エラーログに含まれるスタックトレースの中にinnobaseな関数が並んでたりすると更に確率が上がる。
何度起動しようとしても “signal 11” で落ち 続けるならデータが壊れている && そのエラーハンドルにバグがある予感。datadirをまるっとコピーしてバグレポートできると吉。
どっちのシグナルにしろ起動したら上がっちゃったなら、ハードウェアの一過性の障害という可能性もある。 dmesgやハードウェアのログも見るのを忘れずに。あとは mysqlcheck --all-databasesとかしておくといいかも知れない。
……っていうのを某氏向けに書いていたんだけどすっかり時期を逸したので、ここでおとむらい。

日本MySQLユーザ会会(MyNA会) 2019年8月に参加してきました

$
0
0

とんぼさんのMySQL weeklyばなし

  • 「メリット: 普段、流れてくるブログエントリを全部とか読めないけど強制的に読むようになるから読めるようになる」
    • とてもわかる
  • 「デメリット: つらい。アップデートがリリース(8.0.17)された時は超つらかった」
    • とてもわかる
  • とても読みやすくてわたしもお世話になっているので、長続きしてほしい…!

yoku0825のCloneばなし



  • 「さすが変態」が “As expected” になったのはちょっと面白かったw
    • 実際には「変態」を何故か読み飛ばして「さすが」だけ翻訳してたっぽいけど

坂井さんのMySQL 5.7認定試験ばなし




  • この(日本語試験がリリースされた)時期に何故か観測範囲でこの試験を受けている人多かったな…と思ってたんですが、実はそんなに多くはなかった。。



  • 個人的にあの試験に思うところはこんな感じです。

杉山さんのInnoDB Clusterばなし


  • MySQL 8.0の「それ以外の部分」でハマるの大変だなと思うなど…。
  • そろそろGroup Replicationしたいので大変参考になりました。
    • やっぱりMySQL Router方面の基本的な考え方はMySQL Fabricの時と似てますね。

とみたさんのutf8mb4_0900_binのはなし





  • utf8mb4_0900_binutf8mb4_binNO_PAD_COLLATE版。yokuおぼえた。

db tech showcase Tokyo 2019 2日目に参加してきました

$
0
0
db tech showcase Tokyo 2019 2日目に参加してきました。
3日目(今日)も午後から参加するんですが、取り急ぎ2日目(9/26(木))の感想を。

受付

  • ふらっと昼前にスピーカー受付だけしたら「控室を使えるのは講演前の1時間だけなので13時になったらまた来てくれ…すまぬ…」的な感じだった
    • そして13時になると俺の枠で控室が使えるので、その机に続々と集まってもくもくを始めるOracle MySQLチームwww
    • この「控室を知り合い同士で融通する」システムを上手くやると面白い会話が生まれたりするのかなあ(適当)
  • 『ああ、スーツ来た人たちが商談っぽいのをしてたり、畑の違うDBの人たちがホワイトボードの前で議論を交わしていたり、「おお、一年ぶり!」な挨拶を交わすあの頃のdb tech showcaseとは変わってしまったんだな』という感じ
  • 去年までのコーヒースペースもなくなっていたので、これ特に聞くセッションがないからもくもく仕事してよーとかそういう人が生存できなくなっていた…(これは仕方ないといえば仕方ないけど、直前まで資料に手を入れたかった俺にはちょっとつらかった)
インサイトの 石川さんからこんなコメントをいただいたので、懲りずにまた来年を楽しみにしようと思います!






Shooting a trouble of MySQL

  • わたしこと yoku0825によるセッション
  • コンセプトは 日々の覚書: MySQLが刺さった時にざっと見るためのメモと同じで、「同僚に俺がやっているやり方を伝える」ようなコンセプトで進んでいきます。
  • 全部を全部やったりできたりする必要はないですが、「そういえば他の観点で見られるものってどっかで聞いたことあったような?」って感じになってもらえれば幸いです。






Galera Cluster 4の新機能

  • Presented by Colin Charles
  • 俺が真面目に Galera Cluster (ただし本家ではなく Percona XtraDB Clusterの方)をやっていたのはもう5~6年くらい前で、随分進化したんだなあと感じるなど
    • 俺が最初に試し始めたのはどうやらChiba.pm #2の頃だった 様子

Oracle Database 18c/19c 辛口新機能解説

  • Presented by 篠田さん
  • yoku0825にはOracleがわからぬ…けれども、篠田さんのセッションだったら絶対面白い ⇒ 大当たり
  • Oracle畑の人には「こんなことができるようになったのか」、俺には「こんな機能MySQLにもあったらいいな」「こんな変態機能Oracleにもあったの…」みたいなすごく楽しいセッションでした

Oracle Database バージョン選択における考察’19

  • Presented by 諸橋さん
  • これ! ホントこれ!!
  • ライフサイクルポリシーの話はMySQLとしても今後どうなるのか感あるしクラウドの台頭という点ではRDS for MySQLという巨人がいるので色々考えさせられる話であった…

メルカリにおけるMySQLの運用

  • Presented by いちりんちゃんさん
  • 数年前にはてな東京オフィスとはてな京都オフィスの間のビデオ通話で顔を合わせた(?)っきりで物理いちりんちゃんさんを認識できてなかった…
  • メルカリ、 orchestrator始めたってよ
  • 導入するまでに調査したorchestratorの内部動作とかを丁寧に教えてくれるという、ガチ方面のMySQLer垂涎のセッションでした
  • 資料が公開されるのを服を着て待ちます

スピーカーディナーちょっとだけ

  • ああ…顔ぶれ変わったなぁ…という感じ。。

binlog_format= ROW + 式インデックス + mysqldumpでレプリケーションに失敗する可能性がある

$
0
0

TL;DR

  • 以下の4つの条件を満たすと MySQL error code MY-013146 (ER_SERVER_SLAVE_CONVERSION_FAILED): Column %d of table '%-.192s.%-.192s' cannot be converted from type '%-.32s' to type '%-.32s'が発火してSQLスレッドが止まる
    1. binlog_format= ROWである
    2. 式インデックスを使っている
    3. 式インデックスを作って以降、そのテーブルにカラムを追加した
    4. 論理バックアップからリストアしてスレーブを作成
  • この記事を書いている現在、俺には「マスターのテーブルを再作成する」か、「スレーブの該当テーブルをマスターと同じ順序で構成してからデータをINSERTする」以外の回避策が思いつかない…

MySQL 8.0.13とそれ以降で使える式インデックスだが、どうもなんかバグを抱えているっぽい。。
式インデックスは内部的に「目には見えないgenerated column」を作ってそれにインデックスを貼っているようで、それが binlog_format = ROW、つまり「 n番目のカラムの値はこれ!」という形式のレプリケーションと相性が悪い(´・ω・`)
マスターに対して式インデックスを含む CREATE TABLEを流し、その後カラムを追加する。
master> CREATE DATABASE d1;
Query OK, 1 row affected (0.00 sec)

master> CREATE TABLE d1.t1 (num INT PRIMARY KEY, KEY((num < 100)));
Query OK, 0 rows affected (0.01 sec)

master> ALTER TABLE d1.t1 ADD COLUMN val varchar(32);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

master> SHOW CREATE TABLE d1.t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) DEFAULT NULL,
PRIMARY KEY (`num`),
KEY `functional_index` (((`num` < 100)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
この時、マスターの内部的には 「1番目は numカラム、2番目は (num < 100)を評価する仮想列、3番目が valカラム」になっている。
ibd2sdiでibdファイルからテーブルの定義情報をダンプするとを使うと簡単に見られた。
(もし、information_schemaとかでこれを確認しようと思うと、デバッグビルドのmysqldが必要になってめんどい…)
$ ibd2sdi t1.ibd
["ibd2sdi"
,
{
..
"columns": [
..
{
"name": "3bb8c14d415110ac3b3c55ce9108ae2d",
"type": 4,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": true,
"hidden": 3,
"ordinal_position": 2,
"char_length": 1,
"numeric_precision": 10,
"numeric_scale": 0,
"numeric_scale_null": false,
"datetime_precision": 0,
"datetime_precision_null": 1,
"has_no_default": false,
"default_value_null": false,
"srs_id_null": true,
"srs_id": 0,
"default_value": "AAAAAA==",
"default_value_utf8_null": false,
"default_value_utf8": "0",
"default_option": "",
"update_option": "",
"comment": "",
"generation_expression": "(`num` < 100)",
"generation_expression_utf8": "(`num` < 100)",
"options": "interval_count=0;",
"se_private_data": "table_id=1064;",
"column_key": 4,
"column_type_utf8": "int(1)",
"elements": [],
"collation_id": 8,
"is_explicit_collation": false
},
..
"indexes": [
..
{
"name": "functional_index",
"hidden": false,
"is_generated": false,
"ordinal_position": 2,
"comment": "",
"options": "flags=0;",
"se_private_data": "id=150;root=5;space_id=3;table_id=1064;trx_id=2077;",
"type": 3,
"algorithm": 2,
"is_algorithm_explicit": false,
"is_visible": true,
"engine": "InnoDB",
"elements": [
{
"ordinal_position": 1,
"length": 4,
"order": 2,
"hidden": false,
"column_opx": 1
},
{
"ordinal_position": 2,
"length": 4294967295,
"order": 2,
"hidden": true,
"column_opx": 0
}
],
"tablespace_ref": "d1/t1"
}
],
..
このマスターに対して INSERTステートメントを叩き込む。
ちゃんとカラム名までフル修飾しても変わらないはず。
master> INSERT INTO d1.t1 VALUES (1, 'one');
Query OK, 1 row affected (0.01 sec)

master> SELECT * FROM d1.t1;
+-----+------+
| num | val |
+-----+------+
| 1 | one |
+-----+------+
1 row in set (0.00 sec)
マスターの内部的に「numが1番目、valが3番目」なので、バイナリログは当然こうなる。
# at 833
#191007 11:56:23 server id 111 end_log_pos 904 CRC32 0x3f31c3bd Query thread_id=17 exec_time=0 error_code=0
SET TIMESTAMP=1570416983/*!*/;
BEGIN
/*!*/;
# at 904
#191007 11:56:23 server id 111 end_log_pos 959 CRC32 0x1659d7ad Table_map: `d1`.`t1` mapped to number 83
# at 959
#191007 11:56:23 server id 111 end_log_pos 1003 CRC32 0x00f71d7f Write_rows: table id 83 flags: STMT_END_F

BINLOG '
V6maXRNvAAAANwAAAL8DAAAAAFMAAAAAAAEAAmQxAAJ0MQADAwMPAoAABAEBAAID/P8ArddZFg==
V6maXR5vAAAALAAAAOsDAAAAAFMAAAAAAAEAAgAD/QABAAAAA29uZX8d9wA=
'/*!*/;
### INSERT INTO `d1`.`t1`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @3='one' /* VARSTRING(128) meta=128 nullable=1 is_null=0 */
# at 1003
#191007 11:56:23 server id 111 end_log_pos 1034 CRC32 0x610336ff Xid = 572
COMMIT/*!*/;
仮に最初の時点からスレーブが存在していたとしたら、「CREATE TABLEの時にnumと式インデックスが1, 2番になって後からADD COLUMNされたvalは3番」の順序が保たれるのでレプリケーションは壊れないが、このタイミングで論理バックアップを取ってリストアしてしまうと、
CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) DEFAULT NULL,
PRIMARY KEY (`num`),
KEY `functional_index` (((`num` < 100)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
論理バックアップをリストアしたスレーブでは内部的に「 numが1番目、 valが2番目、式インデックスの隠し生成列が3番目」になり、1番目と3番目のカラムに値を突っ込もうとするバイナリログのイベントと矛盾(というか、データ型のミスマッチ)を起こす。
slave> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
..
Slave_IO_Running: Yes
Slave_SQL_Running: No
..
Last_SQL_Errno: 13146
Last_SQL_Error: Column 1 of table 'd1.t1' cannot be converted from type 'int' to type 'varchar(128(bytes) utf8mb4)'

..
…あれ、エラーメッセージを見ると、 num (マスターで1番目、スレーブで1番目)を val(マスターで3番目、スレーブで2番目)に突っ込もうとしてる…?
それか、Columnの値が0オリジンで Column 1 = マスターの仮想生成列の値をスレーブの Column 1 = valに突っ込もうとしてる…?
エラーメッセージが悪いのか俺の認識がおかしいのかだけど、はてさてこれはなかなかエグい…本番に放り込んじゃったよ式インデックス…。
何とかなってほしいので、是非 Affects meでポチってあげてください…。

MySQL 8.0.17現在、PRIMARY KEYやUNIQUE KEYのCOLLATEを変更しても何故か再起動まで反映されない

$
0
0

TL;DR


まずは何も考えずに val varchar(32)にユニークキーを作る。
この時の collation_serverはデフォルトの utf8mb4_0900_ai_ciのままで、「おっとこれって kamipoのハハパパ問題が起こるやつじゃん、そういえばデフォルト変わったんだっけ」なイメージ。
mysql80 10> SELECT @@collation_server;
+--------------------+
| @@collation_server |
+--------------------+
| utf8mb4_0900_ai_ci |
+--------------------+
1 row in set (0.00 sec)

mysql80 10> CREATE TABLE t1 (num int NOT NULL PRIMARY KEY, val varchar(32) NOT NULL, UNIQUE KEY(val));
Query OK, 0 rows affected (0.03 sec)

mysql80 10> INSERT INTO t1 VALUES (1, 'ハハ');
Query OK, 1 row affected (0.00 sec)

mysql80 10> INSERT INTO t1 VALUES (2, 'パパ');
ERROR 1062 (23000): Duplicate entry 'パパ' for key 'val'

mysql80 10> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) NOT NULL,
PRIMARY KEY (`num`),
UNIQUE KEY `val` (`val`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
ハハパパ問題を避けるには utf8mb4_ja_0900_as_csって ~進研ゼミで~ 習ったので、華麗にCOLLATIONを変更して事なきを得ようとする。
mysql80 10> ALTER TABLE t1 MODIFY val varchar(32) NOT NULL COLLATE utf8mb4_ja_0900_as_cs;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 10> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
PRIMARY KEY (`num`),
UNIQUE KEY `val` (`val`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
よし問題なくなったので安心して「パパ」を追加できるz
mysql80 10> INSERT INTO t1 VALUES (2, 'パパ');
ERROR 1062 (23000): Duplicate entry 'パパ' for key 'val'
アイエエエエエエエエ
これ 元のバグレポートにも書いてあるんだけど、mysqldを再起動するか OPTIMIZE TABLE相当(テーブル再構築)のことをすると直る。
mysql80 10> OPTIMIZE TABLE t1;
+-------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+-------+----------+----------+-------------------------------------------------------------------+
| d1.t1 | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| d1.t1 | optimize | status | OK |
+-------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.15 sec)

mysql80 10> INSERT INTO t1 VALUES (2, 'パパ');
Query OK, 1 row affected (0.01 sec)

mysql80 10> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
| 2 | パパ |
+-----+--------+
2 rows in set (0.00 sec)
…これ、逆のことやったらどうなるんだろう。
mysql80 10> ALTER TABLE t1 MODIFY val varchar(32) NOT NULL COLLATE utf8mb4_0900_ai_ci;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql80 10> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`num` int(11) NOT NULL,
`val` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`num`),
UNIQUE KEY `val` (`val`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql80 10> INSERT INTO t1 VALUES (3, 'ババ');
Query OK, 1 row affected (0.00 sec)

mysql80 10> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
| 3 | ババ |
| 2 | パパ |
+-----+--------+
3 rows in set (0.00 sec)
:(;゙゚’ω゚’): ハハパパババを全部同じ文字に扱うはずなのに入りましたね…
mysql80 10> SELECT * FROM t1 WHERE val = 'ハハ';
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'パパ';
+-----+--------+
| num | val |
+-----+--------+
| 2 | パパ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'ババ';
+-----+--------+
| num | val |
+-----+--------+
| 3 | ババ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'ばば';
+-----+--------+
| num | val |
+-----+--------+
| 3 | ババ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'ハハ';
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
+-----+--------+
1 row in set (0.00 sec)
動きは変更前の utf8mb4_0900_ja_as_csに近いですね…:(;゙゚’ω゚’):
mysql80 10> RESTART;
Query OK, 0 rows affected (0.02 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'ハハ';
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'ばば';
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
+-----+--------+
1 row in set (0.00 sec)

mysql80 10> SELECT * FROM t1 WHERE val = 'パパ';
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
+-----+--------+
1 row in set (0.00 sec)
RESTARTしたら、今度は utf8mb4_0900_ai_ciっぽくなったけど、ユニーク制約だからか1レコードしか返ってきませんね…:(;゙゚’ω゚’):
これ UPDATEDELETEでもたぶんそうなるんですよね…。
mysql80 10> DELETE FROM t1 WHERE val = 'ババ';
Query OK, 1 row affected (0.13 sec)

mysql80 10> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 3 | ババ |
| 2 | パパ |
+-----+--------+
2 rows in set (0.00 sec)
ヒィ
ちなみに utf8mb4_0900_bin, utf8mb4_binへの変更はちゃんと即時反映された。
mysql80 10> ALTER TABLE t1 MODIFY val varchar(32) NOT NULL COLLATE utf8mb4_0900_bin;
Query OK, 2 rows affected (0.03 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql80 10> INSERT INTO t1 VALUES (1, 'ハハ');
Query OK, 1 row affected (0.00 sec)

mysql80 10> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | ハハ |
| 3 | ババ |
| 2 | パパ |
+-----+--------+
3 rows in set (0.00 sec)
RESTARTOPTIMIZE TABLEってことは、データディクショナリー上はちゃんと書き換えられてるけどオンメモリーの方が追随できてないんだろうなぁとかなんとか、 *ai_ciが悪いのかなどうなんだろうなぁとか。
取り敢えず Bug#97103はしばらくウォッチすることにします…

MySQL 8.0.18からCREATE USER, ALTER USER, SET PASSWORDで "RANDOM"を指定できるようになった

$
0
0

TL;DR


MySQL 8.0.18から、ランダムなパスワードを勝手に生成する RANDOM PASSWORD構文が使えるようになった。
外部のパスワードジェネレータでいいじゃnううんなんでもない。
CREATE USERALTER USERは、本来パスワード文字列を渡すところにそのまま RANDOM PASSWORDと置き換えると使える。
mysql80 18> CREATE USER b IDENTIFIED WITH mysql_native_password BY RANDOM PASSWORD;
+------+------+----------------------+
| user | host | generated password |
+------+------+----------------------+
| b | % | CYvTzZuEK</1JwfFrFkM |
+------+------+----------------------+
1 row in set (0.01 sec)

mysql80 18> SHOW CREATE USER b;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER for b@% |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER 'b'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*B463EC3F855577E3EC49BC205D66ECEAF7ABC106' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql80 18> ALTER USER b IDENTIFIED BY RANDOM PASSWORD;
+------+------+----------------------+
| user | host | generated password |
+------+------+----------------------+
| b | % | HnZwYKlnl]*b1lJ>JxQt |
+------+------+----------------------+
1 row in set (0.02 sec)

mysql80 18> SHOW CREATE USER b;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER for b@% |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER 'b'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*15B4BD0DD178CB3C258B8867A1BB570A7CF9E799' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
SET PASSWORDだけ、 = 'パスワード文字列'だったところが TO RANDOMになる。
こういうところ妙に英語っぽくするよねSQLって(偏見)
mysql80 18> SET PASSWORD FOR b TO RANDOM;
+------+------+----------------------+
| user | host | generated password |
+------+------+----------------------+
| b | % | 6jKNjZBhz2fJHLM@N13* |
+------+------+----------------------+
1 row in set (0.01 sec)

mysql80 18> SHOW CREATE USER b;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER for b@% |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER 'b'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*E0D266D8CA4F993339B63CB8C3D7AB103B6D08B5' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
ちなみにランダムパスワードの長さは generated_random_password_lengthで指定できるらしい。
完全な余談だけど、ドキュメントを見ずにテキトーな値を突っ込んだらワーニングになって、
mysql80 16> SET SESSION generated_random_password_length = 1024;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql80 16> SHOW WARNINGS;
+---------+------+--------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect generated_random_password_length value: '1024' |
+---------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)
こんな時MySQL 8.0の performance_schema.variables_infoがあれば一発で便利だなあと思いました。
mysql80 16> SELECT * FROM performance_schema.variables_info WHERE variable_name = 'generated_random_password_length';
+----------------------------------+-----------------+---------------+-----------+-----------+----------------------------+----------+-----------+
| VARIABLE_NAME | VARIABLE_SOURCE | VARIABLE_PATH | MIN_VALUE | MAX_VALUE | SET_TIME | SET_USER | SET_HOST |
+----------------------------------+-----------------+---------------+-----------+-----------+----------------------------+----------+-----------+
| generated_random_password_length | DYNAMIC | | 5 | 255 | 2019-11-05 15:56:14.070703 | root | localhost |
+----------------------------------+-----------------+---------------+-----------+-----------+----------------------------+----------+-----------+
1 row in set (0.01 sec)

MySQL Shellのdba.deploySandboxInstanceでサクッとmysqldを起動する

$
0
0

TL;DR

  • いつからできたのかは知らないけれど、気が付いたらMySQL Shellに dba.deploySandboxInstance()なんてものが出来ていてそれが感動的に楽なので熱が冷めないうちにメモ

如何に「MySQLとMySQL Shell以外他に何もいらない」かの感動を伝えるために、 ubuntu:latestのDockerコンテナを起動しただけの状態から始めます。
取り敢えず、 MySQLのaptリポジトリの依存に指定されてるやつらをインストール。
# apt update
# apt install wget lsb-release gnupg
MySQLのaptリポジトリをインストール。最近の .deb ファイルなら向き先が mysql-8.0になっていると思われる。
# wget https://dev.mysql.com/get/mysql-apt-config_0.8.14-1_all.deb
# dpkg -i mysql-apt-config_0.8.14-1_all.deb
..
1. MySQL Server & Cluster (Currently selected: mysql-8.0) 3. MySQL Preview Packages (Currently selected: Disabled)
2. MySQL Tools & Connectors (Currently selected: Enabled) 4. Ok
Which MySQL product do you wish to configure? 4

# apt update
MySQL ServerとMySQL Shellをインストールする。
# apt install mysql-community-server mysql-shell
準備おしまい。

ただ /bin/bashを呼び出したコンテナなので他には何も浮いていない。
root@b2fbd7b7148f:~# ps auxww
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 18504 3432 pts/0 Ss 09:21 0:00 bash
root 4103 0.0 0.2 34396 2912 pts/0 R+ 09:31 0:00 ps auxww
ここからおもむろに mysqlshを起動。接続先は指定しない(し、しようもないし)
root@b2fbd7b7148f:~# mysqlsh
Logger: Tried to log to an uninitialized logger.
MySQL Shell 8.0.18

Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
Other names may be trademarks of their respective owners.

Type '\help' or '\?' for help; '\quit' to exit.
MySQL JS >
接続先を指定せずに、サーバープロセスを起動させるためにクライアントを起動する、ってなんか sqlplusっぽさを感じます。
長いメソッドの名前とか憶えてられないので、 dba.まで打ち込んでからTabを2回で補完候補が現れる。
 MySQL  JS > dba.
checkInstanceConfiguration() deploySandboxInstance() rebootClusterFromCompleteOutage()
configureInstance() dropMetadataSchema() startSandboxInstance()
configureLocalInstance() getCluster() stopSandboxInstance()
createCluster() help() verbose
deleteSandboxInstance() killSandboxInstance()
たぶん deploySandboxInstance()だろ。実行。
 MySQL  JS > dba.deploySandboxInstance()
Dba.deploySandboxInstance: Invalid number of arguments, expected 1 to 2 but got 0 (ArgumentError)
エラった><
引数がおかしいのはわかるんだけど引数に何を指定すればいいのかわからないので、 dba.help('deploySandboxInstance')と実行。
 MySQL  JS > dba.help('deploySandboxInstance')
NAME
deploySandboxInstance - Creates a new MySQL Server instance on localhost.

SYNTAX
dba.deploySandboxInstance(port[, options])
..
ドバっと出てくる。使い方を引きやすい対話型クライアントは好きです。
取り敢えず、必須引数はportだということなので3306でいく。
 MySQL  JS > dba.deploySandboxInstance(3306)
A new MySQL sandbox instance will be created on this host in
/root/mysql-sandboxes/3306

Warning: Sandbox instances are only suitable for deploying and
running on your local machine for testing purposes and are not
accessible from external networks.

Please enter a MySQL root password for the new instance:

Deploying new MySQL instance...

Instance localhost:3306 successfully deployed and started.
Use shell.connect('root@localhost:3306'); to connect to the instance.
root@localhostのパスワードだけ対話入力するとおしまい。
root@b2fbd7b7148f:~# ps auxwww
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 18504 3432 pts/0 Ss 09:21 0:00 bash
root 4163 0.0 0.2 18372 3088 pts/0 S 09:36 0:00 /bin/bash /root/mysql-sandboxes/3306/start.sh --user=root
root 4164 2.0 32.8 2302340 418604 pts/0 Sl 09:36 0:00 /root/mysql-sandboxes/3306/bin/mysqld --defaults-file=/root/mysql-san
dboxes/3306/my.cnf --user=root
root 4208 0.0 0.2 34396 2864 pts/0 R+ 09:36 0:00 ps auxwww
勿論、ポート番号を変えればこのノリでザクザク mysqldを起動できる。
データとかmy.cnfとかは $HOME/mysql-sandboxesあたりに転がるので、サンドボックス用途ならもう公式のツールだけで作れる時代になってしまったのだ。令和ばんざい。
ちなみに、mysqlx-portにport * 10を指定しようとするので、ポート番号に 6554を超える値を渡そうとするとエラる。その場合は dba.deploySandboxInstance(13306, {"portx": 13307})とか明示してやればおk
それでは、楽しいMySQL Shellライフを。

MySQLにおけるNULLと演算の結果

$
0
0

NULLの四則演算はわかりやすいですよね。
mysql80 21> SELECT 1 + NULL, 1 - NULL, 1 * NULL, 1 / NULL;
+----------+----------+----------+----------+
| 1 + NULL | 1 - NULL | 1 * NULL | 1 / NULL |
+----------+----------+----------+----------+
| NULL | NULL | NULL | NULL |
+----------+----------+----------+----------+
1 row in set (0.00 sec)
NULLに対する四則演算はNULL。
「謎の数字に1を足したら何になる?」「(´・ω・`)知らんがな」
論理演算は知ってる人と知らない人に分かれそう。
mysql80 21> SELECT TRUE AND NULL, TRUE OR NULL, FALSE AND NULL, FALSE OR NULL;
+---------------+--------------+----------------+---------------+
| TRUE AND NULL | TRUE OR NULL | FALSE AND NULL | FALSE OR NULL |
+---------------+--------------+----------------+---------------+
| NULL | 1 | 0 | NULL |
+---------------+--------------+----------------+---------------+
1 row in set (0.00 sec)
MySQLの整数型において0以外は真、0は偽で、 TRUEは1のシノニム、 FALSEは0のシノニムです。
話はちょっと逸れますが、 TRUEは1のシノニムなので↓のようなことも可能(?)です。 = TRUEと等価比較すると値が真なのか偽なのかは判定できません。
mysql80 21> SELECT TRUE + TRUE /* 1 + 1 */, TRUE + TRUE = TRUE /* 1 + 1 = 1 */;
+-------------+--------------------+
| TRUE + TRUE | TRUE + TRUE = TRUE |
+-------------+--------------------+
| 2 | 0 |
+-------------+--------------------+
1 row in set (0.00 sec)
真偽値を判定するには IS演算子を使う必要があります。
mysql80 21> SELECT (TRUE + TRUE) IS TRUE, (TRUE + TRUE = TRUE) IS TRUE;
+-----------------------+------------------------------+
| (TRUE + TRUE) IS TRUE | (TRUE + TRUE = TRUE) IS TRUE |
+-----------------------+------------------------------+
| 1 | 0 |
+-----------------------+------------------------------+
1 row in set (0.00 sec)
単純な話、0ならFALSE(= 0)、それ以外ならTRUE(= 1)を返します。NULLセーフです。 IS演算子(またはIS NOT演算子)の右辺には TRUE, FALSE, UNKNOWNしか取れません。
IS NULLは別の演算子扱いでマニュアルも別れています。これ豆な。
で、戻ってきますが論理演算はこんな感じです(さっきと同じ出力)
mysql80 21> SELECT TRUE AND NULL, TRUE OR NULL, FALSE AND NULL, FALSE OR NULL;
+---------------+--------------+----------------+---------------+
| TRUE AND NULL | TRUE OR NULL | FALSE AND NULL | FALSE OR NULL |
+---------------+--------------+----------------+---------------+
| NULL | 1 | 0 | NULL |
+---------------+--------------+----------------+---------------+
1 row in set (0.00 sec)
AND演算子もOR演算子も左辺と右辺を交換しても結果が変わらない演算子なので、順番関わらずこうなります。
この順番で書くと、「短絡評価っぽいでしょ?」と説明しやすいのでよくそういうのですが、本当は短絡評価しているわけではなく、↓のような感じです。
  • ANDの場合、
    • 片方が偽なら式全体として偽が確定するので、偽 AND NULLは偽
    • 片方が真なら式全体の真偽はもう片方の値に依存するので、真 AND NULLはNULL
  • ORの場合、
    • 片方が偽なら式全体の真偽はもう片方の値に依存するので、偽 OR NULLはNULL
    • 片方が真なら式全体としての真が確定するので、真 OR NULLは真
十分短絡評価っぽかった。
プログラミングの短絡評価と違うのは「MySQLは飽くまで「ANDもORも両辺を交換可能」と見た上で」こうなっているということ。
「この論理演算がパッと思いつかないならNOT NULLしといた方が踏むリスクはなくなるよ」というユルいNOT NULL推奨のPoCとしてご利用いただければと思います。

ちなみにPostgreSQL 9.1.10。
(PostgreSQLのTRUE, FALSEはシノニムではなくBool型なので、足したり引いたりはできません)
postgres=# SELECT TRUE AND NULL, TRUE OR NULL, FALSE AND NULL, FALSE OR NULL;
?column? | ?column? | ?column? | ?column?
----------+----------+----------+----------
[NULL] | t | f | [NULL]
(1 row)
Oracle 11.2.0.4.0。
どうやらAND演算子がselect_listの中に入るのを許してくれない模様。
SQL> SELECT TRUE AND NULL, TRUE OR NULL, FALSE AND NULL, FALSE OR NULL FROM dual;
SELECT TRUE AND NULL, TRUE OR NULL, FALSE AND NULL, FALSE OR NULL FROM dual
*
行1でエラーが発生しました。:
ORA-00923: FROMキーワードが指定の位置にありません。

InnoDB Clusterの構築だけMySQL Shellでやって、運用はSQLインターフェイスでやるのはアリ?

$
0
0

TL;DR

  • 調べたいこと
    • MySQL InnoDB Clusterの構築(と、その後の運用フェーズでメンバーの追加削除)だけMySQL Shellでやって、フェイルオーバーその他の動作はSQLインターフェイス( mysqlコマンドまたはスクリプトなど) からだけできるか?
    • できた
  • …というちょっとした検証をしたい時にMySQL Shellは便利

日々の覚書: MySQL Shellのdba.deploySandboxInstanceでサクッとmysqldを起動するでやったのと同じ手順で、 ubuntu:latest (2019/11/12時点) なDockerコンテナに mysql-community-server, mysql-shell, mysql-router-communityをインストールした。
( ´-`).oO( mysql-community-servermysql-router-communityって “community” が付く位置が違って一瞬ハマった…そして mysql-shellは “community” がつかない…
# dpkg -l | grep -i mysql
ii mysql-apt-config 0.8.14-1 all Auto configuration for MySQL APT Repo.
ii mysql-client 8.0.18-1ubuntu18.04 amd64 MySQL Client meta package depending on latest version
ii mysql-common 8.0.18-1ubuntu18.04 amd64 Common files shared between packages
ii mysql-community-client 8.0.18-1ubuntu18.04 amd64 MySQL Client
ii mysql-community-client-core 8.0.18-1ubuntu18.04 amd64 MySQL Client Core Binaries
ii mysql-community-server 8.0.18-1ubuntu18.04 amd64 MySQL Server
ii mysql-community-server-core 8.0.18-1ubuntu18.04 amd64 MySQL Server Core Binaires
ii mysql-router-community 8.0.18-1ubuntu18.04 amd64 MySQL Router
ii mysql-shell:amd64 8.0.18-1ubuntu18.04 amd64 MySQL Shell (part of MySQL Server) 8.0
mysqlshを起動し、 dba.deploySandboxInstance()で2つばかりインスタンスを立ち上げる。
# mysqlsh
MySQL JS > dba.deploySandboxInstance(3306)
MySQL JS > dba.deploySandboxInstance(3307)
どっちかのインスタンス(最初のマスターにするやつ、どれでもいいと思う)にそのまま接続して、InnoDB Clusterを構築する。
 MySQL  JS > \c root@localhost:3306
reating a session to 'root@localhost:3306'
Please provide the password for 'root@localhost:3306':
Save password for 'root@localhost:3306'? [Y]es/[N]o/Ne[v]er (default No): Y

MySQL localhost:3306 ssl JS > dba.createCluster('sakila_cluster')
クラスターのメンバーを追加するには、一度変数に受けてClusterオブジェクトにしてから addInstance()っぽい。
 MySQL  localhost:3306 ssl  JS > var c = dba.getCluster()
MySQL localhost:3306 ssl JS > c.addInstance('localhost:3307')
Please provide the password for 'root@localhost:3307':
Save password for 'root@localhost:3307'? [Y]es/[N]o/Ne[v]er (default No): Y

NOTE: The target instance 'localhost:3307' has not been pre-provisioned (GTID set is
empty). The Shell is unable to decide whether incremental distributed state
recovery can correctly provision it.
The safest and most convenient way to provision a new instance is through
automatic clone provisioning, which will completely overwrite the state of
'localhost:3307' with a physical snapshot from an existing cluster member. To
use this method by default, set the 'recoveryMethod' option to 'clone'.

The incremental distributed state recovery may be safely used if you are sure
all updates ever executed in the cluster were done with GTIDs enabled, there
are no purged transactions and the new instance contains the same GTID set as
the cluster or a subset of it. To use this method by default, set the
'recoveryMethod' option to 'incremental'.

Please select a recovery method [C]lone/[I]ncremental recovery/[A]bort (default Clone): C
ここでMySQL 8.0.17とそれ以降ならCLONEプラグインを使って初期データのセットアップを済ませるモードが選べる。らくちん。
Monitoring recovery process of the new cluster member. Press ^C to stop monitoring and let it continue in background.
Clone based state recovery is now in progress.

NOTE: A server restart is expected to happen as part of the clone process. If the
server does not support the RESTART command or does not come back after a
while, you may need to manually start it back.

* Waiting for clone to finish...
NOTE: localhost:3307 is being cloned from 127.0.0.1:3306
** Stage DROP DATA: Completed
** Clone Transfer
FILE COPY ############################################################ 100% Completed
PAGE COPY ############################################################ 100% Completed
REDO COPY ############################################################ 100% Completed

NOTE: localhost:3307 is shutting down...

* Waiting for server restart... ready
* 127.0.0.1:3307 has restarted, waiting for clone to finish...
* Clone process has finished: 59.55 MB transferred in about 1 second (~inf TB/s)

State recovery already finished for 'localhost:3307'

The instance 'localhost:3307' was successfully added to the cluster.
なんか仕事をしている気になれそうな出力があったあと、クラスターのメンバーへの追加が終わっている。
 MySQL  localhost:3306 ssl  JS > c.status()
{
"clusterName": "sakila_cluster",
"defaultReplicaSet": {
"name": "default",
"primary": "127.0.0.1:3306",
"ssl": "REQUIRED",
"status": "OK_NO_TOLERANCE",
"statusText": "Cluster is NOT tolerant to any failures.",
"topology": {
"127.0.0.1:3306": {
"address": "127.0.0.1:3306",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.18"
},
"127.0.0.1:3307": {
"address": "127.0.0.1:3307",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.18"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "127.0.0.1:3306"
}
はい。取り敢えず2台構成ではあるけどMySQL InnoDB Clusterの形にはなったのでMySQL Shellはここまで。
次はMySQL Router。
初期設定は --bootstrapでInnoDB Clusterに参加しているmysqldを指定( username@hostname:port形式) してやるくらい。
# mysqlrouter --bootstrap root@localhost:3306 --user=mysqlrouter
Please enter MySQL password for root:
# Reconfiguring system MySQL Router instance...

- Checking for old Router accounts
- Found old Router accounts, removing
- Creating mysql account 'mysql_router1_c1ntzh4p43m6'@'%' for cluster management
- Storing account in keyring
- Adjusting permissions of generated files
- Creating configuration /etc/mysqlrouter/mysqlrouter.conf

# MySQL Router configured for the InnoDB cluster 'sakila_cluster'

After this MySQL Router has been started with the generated configuration

$ /etc/init.d/mysqlrouter restart
or
$ mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf

the cluster 'sakila_cluster' can be reached by connecting to:

## MySQL Classic protocol

- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447

## MySQL X protocol

- Read/Write Connections: localhost:64460
- Read/Only Connections: localhost:64470
これでコンフィグが書き込まれるので、その後フツーに起動してやれば息をし始める。
Dockerコンテナでsystemdを使えるような設定にはしてないので、 nohupで起動しちゃう。
# nohup  mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &
[1] 4673

# mysql -h127.0.0.1 -P6446 -uroot -sse "SELECT @@port" ### mysqlrouterのRead/Writeポート
3306

# mysql -h127.0.0.1 -P6447 -uroot -sse "SELECT @@port" ### mysqlrouterのRead/Onlyポート
3307
ちゃんとつながった。
じゃあコイツを mysqlコマンドラインクライアントでSQL使ってスイッチオーバーさせる。
mysql> SELECT * FROM performance_schema.replication_group_members; -- BEFORE
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 5442bcff-0506-11ea-b701-0242ac110003 | 127.0.0.1 | 3306 | ONLINE | PRIMARY | 8.0.18 |
| group_replication_applier | 88295d82-0506-11ea-8a34-0242ac110003 | 127.0.0.1 | 3307 | ONLINE | SECONDARY | 8.0.18 |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
2 rows in set (0.05 sec)

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 5442bcff-0506-11ea-b701-0242ac110003 | 127.0.0.1 | 3306 | ONLINE | PRIMARY | 8.0.18 |
| group_replication_applier | 88295d82-0506-11ea-8a34-0242ac110003 | 127.0.0.1 | 3307 | ONLINE | SECONDARY | 8.0.18 |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
2 rows in set (0.05 sec)

mysql> SELECT group_replication_set_as_primary('88295d82-0506-11ea-8a34-0242ac110003');
+--------------------------------------------------------------------------+
| group_replication_set_as_primary('88295d82-0506-11ea-8a34-0242ac110003') |
+--------------------------------------------------------------------------+
| Primary server switched to: 88295d82-0506-11ea-8a34-0242ac110003 |
+--------------------------------------------------------------------------+
1 row in set (0.15 sec)

mysql> SELECT * FROM performance_schema.replication_group_members; -- AFTER
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 5442bcff-0506-11ea-b701-0242ac110003 | 127.0.0.1 | 3306 | ONLINE | SECONDARY | 8.0.18 |
| group_replication_applier | 88295d82-0506-11ea-8a34-0242ac110003 | 127.0.0.1 | 3307 | ONLINE | PRIMARY | 8.0.18 |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
2 rows in set (0.00 sec)
ルーター側はどうなったかな。
# mysql -h127.0.0.1 -P6446 -uroot -sse "SELECT @@port" ### Read/Writeポート
3307

# mysql -h127.0.0.1 -P6447 -uroot -sse "SELECT @@port" ### Read/Onlyポート
3306
いけてますね。
MySQL Shellが苦手でも、一度構築(これは面倒なのでMySQL Shellに任せてしまいたい、個人的に)してしまえばあとはいつもの使い慣れたものから呼び出せるということでひとまず安心。

MyNA 望年LT大会2019に参加してきました

$
0
0
昨年に引き続き SCSKさんのご支援で nomunoさんでの開催。美味しいごはんとお酒をありがとうございましたm(_ _)m

終わってしまってからでなんなんですが「どんな雰囲気なの?」「フツーのLT大会と違うの?」みたいなのを(事前に)何回か聞かれたので紹介します。
  • 飲みながらしゃべりたい人がしゃべりたいことをしゃべります
    • そういう意味では MySQL Casual Talksにも近いですがLT枠onlyでした(ただし厳密に5分きっかりなスタイルではない)
  • 椅子に座って全員同じ方向を向いて始まるというスタイルでは ないです
    • 立食パーティーをしながら、一角ではLTやってるという感じ
    • 興味があれば聞いてもいいし、お酒を嗜みながら他の人と談笑するもいいし
      • これは珍しいスタイルかも知れません
  • 赤坂です
    • だいじなことなのでもう一度言います、赤坂です(少なくとも2018と2019は赤坂でした)

私はここ最近 ~(といってもそろそろ熱が冷め始めてきていて困っている)~ MySQL Shellを使って、5分で(間に合わなかった) InnoDB Clusterのサンドボックスを構築するのにチャレンジしました。
( ´-`).oO(記事を書いた当時はWSL1で、昨日の時点ではWSL2を使っていて、かかる時間の想定に若干のブレが(いいわけ
個人的には keny_lalaさんの「MySQLの気持ちになって答えるクイズ」が面白かったです! スライド公開期待 :)
しかし後で聞いてみたら「一応LTは準備してきてた(けど発表{しな|できな}かった)」みたいな人が多くて、そういう話こそ聞きたかった感もあり、そういうイベントしたいなって思った赤坂の夜でした!
あと、作って以来渡そう渡そうと思っていた マイナくんのウインドブレーカーをようやく堤井さんに渡せたので俺の2019年は無事に終わりそうです。
来年もよろしくお願いしますm(_ _)m
Viewing all 581 articles
Browse latest View live