GA

2020/08/31

MySQL InnoDB Cluster/ReplicaSet 8.0.21で「mysqlrouterから参照されないように」設定する

TL;DR


まずはフツーにMySQL Shellでサンドボックスを3つばかり作る。

$ mysqlsh -- dba deploySandboxInstance 3306 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3307 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3308 { --password="" }

3306のインスタンスで dba.createaReplicaSet('myRs')

$ mysqlsh --uri=root:""@localhost:3306 -- dba createReplicaSet myRs

--replicasetrs に受けてからの rs.addInstance
(このコマンドラインオプションの奇妙な書き方は 日々の覚書: CentOS 7のAMIでEC2を起動してGroup Replicationを組むところまでを何も考えずに をやってる時に見つけた)

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3307 { --recoveryMethod=clone }
$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3308 { --recoveryMethod=clone }

以前にInnoDB Clusterとかを組んだことがあると mysqlrouter.conf に書かれていたりするので一度消してから mysqlrouter --bootstrap

$ sudo rm /etc/mysqlrouter/mysqlrouter.conf
$ sudo mysqlrouter --bootstrap=root@localhost:3306 --user=mysqlrouter
Please enter MySQL password for root:
$ sudo systemctl start mysqlrouter

この状態でRead-Onlyな6447は3307と3308にラウンドロビンされている。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308

rs.setInstanceOption(‘127.0.0.1:3308’, ‘tag:_hidden’, true) とやると

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs setInstanceOption '127.0.0.1:3308' 'tag:_hidden' 'true'
WARNING: Using a password on the command line interface can be insecure.
You are connected to a member of replicaset 'myRs'.

ルーターを再起動とかしなくても読み取り分散から外れる。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307

これはうまく使えば便利そう。


ただし、_hidden: trueのままでもsetPrimaryInstanceできてしまうし、_hiddenは効いたままなのでその辺はどうなのか感ある。

2020/08/20

MySQL 8.0 vs 外部キー制約 vs ALTER TABLEでメタデータロック待ちになったら疑うこと

【2025/07/08 14:58】

子テーブルにSELECTすると親テーブルにもMDLを置くんではなく、ALTER TABLEが子テーブルにもMDLを置くんだって教えてもらいました。

TL;DR


論より証拠。
サンプルスキーマはこんなかんじ。

CREATE TABLE `item` (
  `item_id` int NOT NULL,
  `registered` datetime NOT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `user` (
  `user_id` int NOT NULL,
  `registered` datetime NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `item_box` (
  `user_id` int NOT NULL,
  `item_id` int NOT NULL,
  `item_amount` int NOT NULL,
  `last_updated` datetime NOT NULL,
  PRIMARY KEY (`user_id`,`item_id`),
  KEY `idx_itemid` (`item_id`),
  CONSTRAINT `item_box_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`),
  CONSTRAINT `item_box_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `item` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `item_log` (
  `item_log_seq` bigint unsigned NOT NULL,
  `user_id` int NOT NULL,
  `item_id` int NOT NULL,
  `changed_amount` int NOT NULL,
  `recorded` datetime NOT NULL,
  PRIMARY KEY (`item_log_seq`),
  KEY `idx_userid` (`user_id`),
  KEY `idx_itemid` (`item_id`),
  CONSTRAINT `item_log_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `item_box` (`user_id`),
  CONSTRAINT `item_log_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `item_box` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

item, user テーブルがあって、 item_box がそれぞれに外部キー制約を持ち、 item_box の履歴用に item_log テーブルがある、みたいなテーブル。
2段外部キー制約を張って試したかったので、 item_log の親は item_box になっている(本筋とは関係ないけど、こういうアレにするということは item_box には残数がゼロになったアイテムもDELETEされずに行としては残す感じになるね)

この時、

  • item, user テーブルに対するDDLは自分自身に対するMDLだけを欲しがる。これは従前からの動作といっしょ
mysql80 31> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql80 31> SELECT * FROM item; -- 行ロックは取らないけど共有MDLは取る
Empty set (0.00 sec)

mysql80 34> SELECT * FROM performance_schema.metadata_locks\G
..
*************************** 2. row ***************************
          OBJECT_TYPE: TABLE
        OBJECT_SCHEMA: d1
          OBJECT_NAME: item
          COLUMN_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140136396228576
            LOCK_TYPE: SHARED_READ
        LOCK_DURATION: TRANSACTION
          LOCK_STATUS: GRANTED
               SOURCE: sql_parse.cc:6161
      OWNER_THREAD_ID: 66
       OWNER_EVENT_ID: 5
2 rows in set (0.00 sec)

mysql80 34> SET SESSION lock_wait_timeout = 1;
Query OK, 0 rows affected (0.00 sec)

mysql80 34> ALTER TABLE item Engine = InnoDB; -- MDL待ちがタイムアウト
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • item_box は自分自身とその親である item, user に対してMDLを置こうとする。
mysql80 31> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql80 31> SELECT * FROM item_box;
Empty set (0.00 sec)

mysql80 34> ALTER TABLE item_box Engine = InnoDB;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql80 34> ALTER TABLE item Engine = InnoDB;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql80 34> ALTER TABLE user Engine = InnoDB;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql80 34> ALTER TABLE item_log Engine = InnoDB; -- 子であるitem_logには関係ない
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0
  • item_log は自信と親である item_box には共有MDLを置くが、祖父母の代(?)である item, user には置かない
mysql80 31> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql80 31> SELECT * FROM item_log;
Empty set (0.00 sec)

mysql80 34> ALTER TABLE item_log Engine = InnoDB; -- 自分自身
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql80 34> ALTER TABLE item_box Engine = InnoDB; -- 親テーブル
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql80 34> ALTER TABLE item Engine = InnoDB;  -- 祖父母世代(?)
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql80 34> ALTER TABLE user Engine = InnoDB; -- 祖父母世代(?)
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

実際食らったのは単なる親子関係のテーブルだけど、「あんまり更新されないsbtest1にインデックス追加したい」(都道府県マスタ的な役割だと思いねぇ)「子テーブルのsbtest2はバンバン参照される」みたいな時にハマった。

mysql80 42> ALTER TABLE sbtest.sbtest1 Engine = InnoDB; -- MDL待ちでブロックされなければこれくらいかかるALTER TABLEがあるじゃろ?
Query OK, 0 rows affected (8.96 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql80 42> ALTER TABLE sbtest.sbtest1 Engine = InnoDB; -- ALTER TABLEを開始してから

mysql80 31> BEGIN; SELECT * FROM sbtest.sbtest2 LIMIT 1; -- 他のコネクションで共有MDLを置くじゃろ?
Query OK, 0 rows affected (0.00 sec)

+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
| id | k      | c                                                                                                                       | pad                                                         |
+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
|  1 | 499284 | 83868641912-28773972837-60736120486-75162659906-27563526494-20381887404-41576422241-93426793964-56405065102-33518432330 | 67847967377-48000963322-62604785301-91415491898-96926520291 |
+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
1 row in set (0.00 sec)

mysql80 31> SHOW PROCESSLIST; -- State: altering table でフツーに進むんだけど
+----+-----------------+-----------------+--------+---------+------+------------------------+--------------------------------------------+
| Id | User            | Host            | db     | Command | Time | State                  | Info                                       |
+----+-----------------+-----------------+--------+---------+------+------------------------+--------------------------------------------+
| 42 | root            | localhost       | NULL   | Query   |    7 | altering table         | ALTER TABLE sbtest.sbtest1 Engine = InnoDB |
+----+-----------------+-----------------+--------+---------+------+------------------------+--------------------------------------------+

mysql80 31> SHOW PROCESSLIST; -- しばらくするとMDL待ちで詰まる
+----+-----------------+-----------------+--------+---------+------+---------------------------------+--------------------------------------------+
| Id | User            | Host            | db     | Command | Time | State                           | Info                                       |
+----+-----------------+-----------------+--------+---------+------+---------------------------------+--------------------------------------------+
| 42 | root            | localhost       | NULL   | Query   |   13 | Waiting for table metadata lock | ALTER TABLE sbtest.sbtest1 Engine = InnoDB |
+----+-----------------+-----------------+--------+---------+------+---------------------------------+--------------------------------------------+

mysql80 31> COMMIT; -- COMMITして共有MDLをリリースしたタイミングで
Query OK, 0 rows affected (0.00 sec)

mysql80 42> ALTER TABLE sbtest.sbtest1 Engine = InnoDB; -- MDL取れたのでALTER TABLEが終われる
Query OK, 0 rows affected (2 min 23.94 sec)
Records: 0  Duplicates: 0  Warnings: 0

オンラインALTER TABLEは終了時にMDLを取るのでこんな風になった。

更に、sbtest1へのALTER TABLEが排他MDL待ちになってしまうので、sbtest1への全てのDMLもMDL待ちのキューを追い越せなくて共有MDLが取れなくなって待たされる。
もしもっと早くこれを知っていたら、「sbtest2を掴んでるトランザクションをKILLすればいい」と、ALTER TABLEを完走させることができたかもしれない。

俺基本的に「ヘビーなMySQLでもちゃんと正規化してれば外部キー制約は張っても大丈夫」って主張してたけど、これを食らってから危なっかしいかなと思うようになりました。
ちなみに回避策はレプリカにALTER TABLEしてからマスター切り替え。これで取り敢えず問題は出ていない。

2020/08/07

MyISAMで第2カラムのAUTO_INCREMENTを使ってるテーブルを洗い出すSQL

TL;DR

  • SELECT table_schema, table_name, column_name, seq_in_index FROM information_schema.statistics WHERE (table_schema, table_name, column_name) IN (SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE extra LIKE '%auto_increment%') AND (table_schema, table_name, column_name) NOT IN (SELECT DISTINCT table_schema, table_name, column_name FROM information_schema.statistics WHERE seq_in_index = 1);
  • 無理にSQLでどうにかしなくても、mysqldump --no-data で引っこ抜いて sed 's/MyISAM/InnoDB/' してテキトーなところにリストアしてみればいいと思うよ

MyISAMストレージエンジンとInnoDBストレージエンジンの非互換(というか、他のストレージエンジンの中でもMyISAMだけに特有な機能)な点に「MyISAMは複合キーの2つ目以降のカラムにAUTO_INCREMENT属性を指定できる」というのがある。

サンプルは↓の4つ。

mysql56> SHOW CREATE TABLE t1\G -- 第1カラムのAUTO_INCREMENT。InnoDBでも有効。
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) DEFAULT NULL,
  PRIMARY KEY (`one`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql56> SHOW CREATE TABLE t2\G -- 第2カラムのAUTO_INCREMENT。InnoDBでは使えない
*************************** 1. row ***************************
       Table: t2
Create Table: CREATE TABLE `t2` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`one`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql56> SHOW CREATE TABLE t3\G -- 第3カラムのAUTO_INCREMENT。これも使えない。
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  `three` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`three`,`one`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql56> SHOW CREATE TABLE t4\G -- 第3カラムのAUTO_INCREMENTだけど第1カラムのAUTO_INCREMENTでもあるのでInnoDBでも有効
*************************** 1. row ***************************
       Table: t4
Create Table: CREATE TABLE `t4` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  `three` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`three`,`one`),
  KEY `one` (`one`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

このうちt2とt3だけが引っ掛けられれば正解。
まずはAUTO_INCREMENT属性を持ったカラムを引っこ抜くか。

mysql56> SELECT table_schema, table_name, column_name, extra FROM information_schema.columns WHERE extra LIKE '%auto_increment%';
+--------------+------------+--------------+----------------+
| table_schema | table_name | column_name  | extra          |
+--------------+------------+--------------+----------------+
| d1           | t1         | one          | auto_increment |
| d1           | t2         | one          | auto_increment |
| d1           | t3         | one          | auto_increment |
| d1           | t4         | one          | auto_increment |
| mysql        | time_zone  | Time_zone_id | auto_increment |
+--------------+------------+--------------+----------------+
5 rows in set (0.02 sec)

ここから「第1カラムにインデックスを持っているものを除外」すればいいはず。
「インデックスの第1カラムに指定されているカラム」は seq_in_index = 1 で表現できる。

mysql56> SELECT table_schema, table_name, column_name, index_name, seq_in_index FROM information_schema.statistics WHERE table_schema = 'd1';
+--------------+------------+-------------+------------+--------------+
| table_schema | table_name | column_name | index_name | seq_in_index |
+--------------+------------+-------------+------------+--------------+
| d1           | t1         | one         | PRIMARY    |            1 |
| d1           | t2         | two         | PRIMARY    |            1 |
| d1           | t2         | one         | PRIMARY    |            2 |
| d1           | t3         | two         | PRIMARY    |            1 |
| d1           | t3         | three       | PRIMARY    |            2 |
| d1           | t3         | one         | PRIMARY    |            3 |
| d1           | t4         | two         | PRIMARY    |            1 |
| d1           | t4         | three       | PRIMARY    |            2 |
| d1           | t4         | one         | PRIMARY    |            3 |
| d1           | t4         | one         | one        |            1 |
+--------------+------------+-------------+------------+--------------+
10 rows in set (0.00 sec)

mysql56> SELECT DISTINCT table_schema, table_name, column_name FROM information_schema.statistics WHERE table_schema = 'd1' AND seq_in_index = 1;
+--------------+------------+-------------+
| table_schema | table_name | column_name |
+--------------+------------+-------------+
| d1           | t1         | one         |
| d1           | t2         | two         |
| d1           | t3         | two         |
| d1           | t4         | two         |
| d1           | t4         | one         |
+--------------+------------+-------------+
5 rows in set (0.00 sec)

という訳でサブクエリーを使ってこんな感じになるかしらん。

SELECT 
  table_schema, 
  table_name, 
  column_name
FROM 
  information_schema.statistics 
WHERE 
  (table_schema, table_name, column_name) IN 
    (SELECT table_schema, table_name, column_name 
     FROM information_schema.columns 
     WHERE extra LIKE '%auto_increment%') /* ←AUTO_INCREMENTなカラムの一覧サブクエリ */ AND 
  (table_schema, table_name, column_name) NOT IN 
    (SELECT DISTINCT table_schema, table_name, column_name 
     FROM information_schema.statistics 
     WHERE seq_in_index = 1) /* ←第1カラムなインデックス一覧サブクエリ */;
+--------------+------------+-------------+
| table_schema | table_name | column_name |
+--------------+------------+-------------+
| d1           | t2         | one         |
| d1           | t3         | one         |
+--------------+------------+-------------+
2 rows in set (0.03 sec)

まあこんなことしなくても、 mysqldump --no-data でテーブル定義だけ引っこ抜いて、MyISAMをInnoDBに書き換えてやればエラーになるんでわかるんですけどね。

$ mysqldump56 --set-gtid-purged=OFF --no-data d1  > tables.sql
$ sed -i 's/MyISAM/InnoDB/' tables.sql
$ mysql56 -v -f d1 < tables.sql
..
--------------
CREATE TABLE `t1` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) DEFAULT NULL,
  PRIMARY KEY (`one`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
--------------
..
--------------
CREATE TABLE `t2` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`one`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
--------------

ERROR 1075 (42000) at line 39: Incorrect table definition; there can be only one auto column and it must be defined as a key
..
--------------
CREATE TABLE `t3` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  `three` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`three`,`one`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
--------------

ERROR 1075 (42000) at line 53: Incorrect table definition; there can be only one auto column and it must be defined as a key
..
--------------
CREATE TABLE `t4` (
  `one` int(11) NOT NULL AUTO_INCREMENT,
  `two` int(11) NOT NULL DEFAULT '0',
  `three` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`two`,`three`,`one`),
  KEY `one` (`one`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4
--------------
..

2020/07/30

MySQL 8.0.17とそれ以前では、CREATE USER .. DEFAULT ROLE ..構文を使うと、ロールの情報が正しくレプリケーションされない

TL;DR

  • mysql.role_edges テーブルと mysql.default_roles テーブルがマスターとスレーブでズレる
    • マスターでは登録されるけどスレーブでは登録されない
    • つまりスレーブでは「そのロールを使う権限もそのロールがデフォルトロールである情報も失われる」
  • See MySQL Bugs: #93252: Default role is not logged into the binary log
    • Fixed in 8.0.18

8.0.15を使ってレプリケーションを組んでいたらハマった。
バイナリログへの記録がそもそもおかしいことになる。

mysql> SELECT @@version;
+-----------+
| @@version |
+-----------+
| 8.0.17    |
+-----------+
1 row in set (0.00 sec)

mysql> CREATE ROLE myrole;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE USER yoku0825 DEFAULT ROLE myrole;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW BINLOG EVENTS IN 'binlog.000002';
+---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------+
| Log_name      | Pos | Event_type     | Server_id | End_log_pos | Info                                                                           |
+---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------+
| binlog.000002 |   4 | Format_desc    |         1 |         124 | Server ver: 8.0.17, Binlog ver: 4                                              |
| binlog.000002 | 124 | Previous_gtids |         1 |         155 |                                                                                |
| binlog.000002 | 155 | Anonymous_Gtid |         1 |         232 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                           |
| binlog.000002 | 232 | Query          |         1 |         333 | CREATE ROLE myrole /* xid=7 */                                                 |
| binlog.000002 | 333 | Anonymous_Gtid |         1 |         410 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                           |
| binlog.000002 | 410 | Query          |         1 |         559 | CREATE USER 'yoku0825'@'%' IDENTIFIED WITH 'caching_sha2_password' /* xid=8 */ |
+---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------+
6 rows in set (0.00 sec)

そもそも、 binlog_format によらずSQLがそのまま転記されるタイプの CREATE USER ステートメントだが「俺が打ったものと若干違う(そしてそこには DEFAULT ROLE 句はない)」ことがお分かりいただけるだろうか。

この CREATE USER ステートメントの書き換え自体は ずっと昔からやっていてIDENTIFIED BY で平文パスワードを指定しても「平文はバイナリログに載せず、ハッシュ後の値でバイナリログに載せる」ために構文を書き換えたりしていた。あとは認証プラグインの情報とかも追記している(これはいつからだったか…)

で、その書き換え部分にバグがあったっぽい。
修正コミットはこれ。

Bug#28948915 DEFAULT ROLE IS NOT LOGGED INTO THE BINARY LOG · mysql/mysql-server@d278a87

CREATE USER .. DEFAULT ROLE .. は「ユーザー作成」「ロールの許可」「デフォルトロールの指定」がいっぺんに指定できて便利なんだけど、MySQL 8.0.17とそれ以前を使っている場合は注意。

日々の覚書: CREATE USER .. DEFAULT ROLE .. で指定すると一発でROLEも許可される

( ´-`).oO(↑の時点では気が付いてなくて、最近スレーブをスイッチオーバーさせようとして初めて気が付いた…つらい…

pt-table-checksum で mysql.role_edges とmysql.default_roles だけがズレてたらこれかも知れないので気を付けて!

2020/07/21

DEBUG_SYNCことはじめ

TL;DR


DEBUG_SYNCとは、デバッグビルドのmysqldの中だけでブレークポイントみたいなものを設定して、ブレークポイントに差し掛かったら他のコネクションを使って操作を試してみられる…と思ってもらえれば多分大丈夫。

ただし任意の箇所でブレークできるわけではなく、コードの中に埋め込まれた “synchronization points” の場所にだけ仕掛けられる。以下、わかりやすさ優先で ブレークポイント と呼ぶことにする。

基本的な使い方はこんな感じ。

  1. mysqld --debug-sync-timeout=n で起動する (デフォルトは0でDEBUG_SYNC無効)
  2. ブレークポイントを通らせたいスレッドで SET DEBUG_SYNC = 'synchronization_pointsの名前 SIGNAL 送信シグナル名 WAIT_FOR テキトーなシグナル名'
  3. 2. のスレッドがブレークポイントを通った時に操作をしたいスレッドで SET DEBUG_SYNC = 'now WAIT_FOR 送信シグナル名
  4. 3. のスレッドはその時点で debug_sync_timeout 秒間の待機に入るので、その間に 2. のスレッドでブレークポイントを通るような操作をする。
  5. 2. のスレッドがブレークポイントを通ると SIGNAL 句で設定した 送信シグナル名 がブロードキャストされて、 3. のスレッドがそれを受け取って復帰する。 2. のスレッドはそのまま WAIT_FOR 句で テキトーなシグナル名 がブロードキャストされるのを待つ
  6. 3. のスレッドなりシェルなりで好きな操作をする
  7. 3. のスレッドで SET DEBUG_SYNC = 'now SIGNAL テキトーなシグナル名'テキトーなシグナル名 がブロードキャストされて 2. のスレッドが復帰して残りの処理をする

3. のスレッドは本質ではない(別にこれがなくてもブレークポイントを通ったあと待ちに入る)けれど、これがないと「ブレークポイントを通るまでの処理をしているのか、通ったあとに来ないシグナルを待っているのか」の区別がつかないのでこうするのが吉だと思う。


デバッグビルドしたmysqldを --debug-sync-timeout=n (n > 0) で起動。

$ ./bin/mysqld --max-allowed-packet=1G --gtid_mode=ON --enforce_gtid_consistency=ON --debug-sync-timeout=600 --daemonize

autocommit環境下でストレージエンジンへのINSERT後COMMITになる前の状態、とかを観測してみたりできる。

conn1> SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL here WAIT_FOR yoku0825';
Query OK, 0 rows affected (0.01 sec)

conn2> SET DEBUG_SYNC = 'now WAIT_FOR here'; -- nowは特殊なキーワードで常にブレークする。nowにヒットしたのでhereシグナル待ちに入る

conn1> INSERT INTO d1.t1 SET lo = REPEAT('a', 128 * 1024 * 1024); -- ib_after_row_insertに到達したあと hereシグナルを送ってyoku0825シグナルを待つ

conn2> SET DEBUG_SYNC = 'now WAIT_FOR here'; -- hereシグナルを受け取って叩き起こされる
Query OK, 0 rows affected (7.64 sec)

conn2> SHOW PROCESSLIST;
+----+-----------------+-----------+------+---------+------+---------------------------------------+-----------------------------------------------------------+
| Id | User            | Host      | db   | Command | Time | State                                 | Info                                                      |
+----+-----------------+-----------+------+---------+------+---------------------------------------+-----------------------------------------------------------+
|  1 | event_scheduler | localhost | NULL | Daemon  |   87 | Waiting on empty queue                | NULL                                                      |
|  3 | root            | localhost | NULL | Query   |    0 | starting                              | SHOW PROCESSLIST                                          |
|  4 | root            | localhost | NULL | Query   |   20 | debug sync point: ib_after_row_insert | INSERT INTO d1.t1 SET lo = REPEAT('a', 128 * 1024 * 1024) |
+----+-----------------+-----------+------+---------+------+---------------------------------------+-----------------------------------------------------------+
3 rows in set (0.03 sec)

conn2> SELECT COUNT(*) FROM d1.t1; -- ib_after_row_insertはCOMMITの手前なのでautocommitでも行は見えない
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

conn2> quit -- 1回抜けてシェルに
Bye

### バイナリログにも書かれていない
$ mysqlbinlog /usr/mysql/5.7.23/data/bin.000011 | tail 
mysqlbinlog: [Warning] unknown variable 'loose-default-character-set=utf8mb4'.
# at 134218325
#200721 15:34:00 server id 1  end_log_pos 134218402 CRC32 0x8f60827e    Query   thread_id=4     exec_time=1     error_code=0
SET TIMESTAMP=1595313240/*!*/;
TRUNCATE d1.t1
/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

$ mysql -S /tmp/mysql.sock -uroot
mysql> SET DEBUG_SYNC = 'now SIGNAL yoku0825';  -- yoku0825シグナルを送ってやる
Query OK, 0 rows affected (0.00 sec)

conn1> INSERT INTO d1.t1 SET lo = REPEAT('a', 128 * 1024 * 1024); -- yoku0825シグナルに叩き起こされてINSERTが完了する
Query OK, 1 row affected (2 min 28.38 sec)

楽しい、いろいろ捗りそう(要出典: 何が)