mita2 database life

主にMySQLに関するメモです

MySQL 8.0 の新機能 デュアルパスワードでパスワード変更時のダウンタイムを回避する

MySQL 8.0で、「デュアルパスワード」がサポートされました。 1つのユーザに対して、新旧2つのパスワード(プライマリとセカンダリ)を設けることができます。

# どっちのパスワードでもログインできる
$ mysql -uapp -h$HOSTNAME -pPassw0rd@init -e 'SELECT CURRENT_USER()'
+----------------+
| CURRENT_USER() |
+----------------+
| app@%          |
+----------------+

$ mysql -uapp -h$HOSTNAME -pPassw0rd@2nd -e 'SELECT CURRENT_USER()'
+----------------+
| CURRENT_USER() |
+----------------+
| app@%          |
+----------------+

DB上でのパスワード変更のオペレーション と アプリケーション側に設定されているパスワードの変更 をまったく同時に行うことは困難です。 そのため、従来はパスワード変更の際は、メンテナンス時間を確保して対応する必要がありました。

もしくは、新しいパスワードを設定した別のユーザを追加し、アプリケーション(やスレーブ)を徐々に追加したユーザに切り替えていくことでダウンタイムを回避していました。 ユーザ名の変更が難しいケースもあると思います(SQL SECURITY DEFINER で指定しているとか、ユーザ名を利用してアクセス監査しているなど)。 デュアルパスワード機能で、ユーザ名を維持しつつ、ダウンタイムを回避してパスワード変更が可能になります。

試します

テスト用にユーザを作ります。

mysql> CREATE USER app@'%' IDENTIFIED BY 'Passw0rd@init';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT User, Host, plugin, authentication_string, User_attributes FROM mysql.user Where user = 'app' \G
*************************** 1. row ***************************
                 User: app
                 Host: %
               plugin: caching_sha2_password
authentication_string: $A$005$#]
7J.Y=5F`mN[hZn/xX6nyS2Bmb2m0Vr6WnCH6aQz.kUFO2AdtIuh/.9
      User_attributes: NULL
1 row in set (0.00 sec)

パスワード変更時に RETAIN CURRENT PASSWORD を指定することで、旧パスワードを保持した上で、パスワード変更が可能です。 旧パスワード(セカンダリ)は mysql.user テーブルの User_attributes に記録されてました。

mysql> ALTER USER 'app'@'%' IDENTIFIED BY 'Passw0rd@2nd' RETAIN CURRENT PASSWORD;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT User, Host, plugin, authentication_string, User_attributes FROM mysql.user Where user = 'app' \G
*************************** 1. row ***************************
                 User: app
                 Host: %
               plugin: caching_sha2_password
authentication_string: $A$005$F2.("3Cli9Uq{9
TLFbCcuja.VoDO8FrvhR3Zi92cp.v5Mec/Zjcezww9eq0
      User_attributes: {"additional_password": "$A$005$#]\n\u00077J\u0003\u0002.\u0013Y=5F`m/\bN[hZn/xX6nyS2Bmb2m0Vr6WnCH6aQz.kUFO2AdtIuh/.9"}
1 row in set (0.00 sec)

新旧両方のパスワードでログインできます。

$ mysql -uapp -h$HOSTNAME -pPassw0rd@init -e 'SELECT CURRENT_USER()'
+----------------+
| CURRENT_USER() |
+----------------+
| app@%          |
+----------------+

$ mysql -uapp -h$HOSTNAME -pPassw0rd@2nd -e 'SELECT CURRENT_USER()'
+----------------+
| CURRENT_USER() |
+----------------+
| app@%          |
+----------------+

最終的に、アプリケーション側のパスワード設定を変更し終わったら、DISCARD OLD PASSWORD で旧パスワードを破棄します。

mysql> ALTER USER 'app'@'%' DISCARD OLD PASSWORD;
Query OK, 0 rows affected (0.00 sec)

権限まわり

RETAIN CURRENT PASSWORD を実行するには、CREATE USER 権限 or APPLICATION_PASSWORD_ADMIN 権限 が必要。

mysql> ALTER USER 'app'@'%' IDENTIFIED BY 'Password@10th' RETAIN CURRENT PASSWORD;
ERROR 1227 (42000): Access denied; you need (at least one of) the CREATE USER or APPLICATION_PASSWORD_ADMIN privilege(s) for this operation

出来ないこと

  • セカンダリパスワードだけを変更する。一方、セカンダリを維持しつつ、プライマリのパスワードだけを変更することは可能。
  • ユーザがどっちのパスワードを利用して接続しているかの判別

(余談) 僕が本当に探しているもの

MySQL 8.0 移行時に、強制的に・短期間で、パスワードを mysql_native_password から caching_sha2_passwordマイグレーションする方法。 いろいろ調べてるけど、初期パスワードを発行しなおして、アプリケーション開発者に再設定してもらう方法に落ち着きそう。

MySQL 8.0.20 でHASH JOINが効くケースが拡大した

TLDR;

  • MySQL 8.0.20 から INNER JOIN だけじゃなくて、Semi joinや Left/Right outer join でも HASH JOIN が使えるようになったよ
  • MySQL 8.0.20 では (EXPLAIN ANALYZEではなく)EXPLAIN でも、HASH JOINが使われているか、表示されるようになったよ
  • MySQL 8.0.19 から、optimizer_switch で HASH JOIN の OFF/ON がコントロールできなくなったよ

MySQL 8.0.20 Release Note

Hash joins are now used any time a nested block loop would be employed. This means that hash joins can be used for the following types of queries:
* Inner non-equi-joins
* Semijoins
* Antijoins
* Left outer joins
* Right outer joins
This builds on work done for MySQL 8.0.18, and removes a limitation in the implementation such that a hash join could be used only with a query having at least one equi-join condition.

MySQL 8.0.20 で HASH JOIN が INNER JOIN 以外にも利用できるようになった。

MySQL 8.0.17

比較のため、HASH JOINが入る前の8.0.17 で試す。 HASH JOINが効きそうな、インデックスを一切貼ってないテーブルどうしをJOINさせる。

mysql>  SELECT * FROM t1 JOIN t2 ON t1.a = t2.a;
Empty set (32.01 sec)
mysql> SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a;
16305 rows in set (34.92 sec)

INNER JOIN, LEFT JOIN ともに、30秒ほど実行にかかった。

mysql> EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.a;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 32538 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 32926 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 32538 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 32926 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

実行計画でも、HASH JOIN ではなく、Block Nested Loop が利用されていることがわかる。

MySQL 8.0.18

HASH JOINがINNER JOINに対してのみサポートされた、8.0.18。

mysql>  SELECT * FROM t1  JOIN t2 ON t1.a = t2.a;
Empty set (0.16 sec)
mysql>  SELECT * FROM t1  LEFT JOIN t2 ON t1.a = t2.a;
16305 rows in set (34.65 sec)

INNER JOINは8.0.17で30秒だったものが、0.16秒とHASH JOINにより高速化されている。 LEFT JOINは変わらず。

mysql> EXPLAIN  SELECT * FROM t1  JOIN t2 ON t1.a = t2.a;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
mysql> EXPLAIN  SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

素のEXPLAINではHASH JOINかどうかが、区別できない。。。 EXPLAIN ANALYZE を利用する必要がある。

mysql> EXPLAIN ANALYZE  SELECT * FROM t1  JOIN t2 ON t1.a = t2.a;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                            |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t2.a = t1.a)  (cost=26268477.04 rows=26266685) (actual time=174.783..174.783 rows=0 loops=1)
    -> Table scan on t2  (cost=0.02 rows=16207) (actual time=0.026..38.354 rows=16305 loops=1)
    -> Hash
        -> Table scan on t1  (cost=1644.95 rows=16207) (actual time=0.053..37.792 rows=16305 loops=1)
 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.18 sec)

Inner hash join と表示され、HASH JOIN が使われたことが、わかる。

MySQL 8.0.20

OUTER JOIN にも HASH JOINが適用されるようになった、8.0.20で試す。

mysql> SELECT * FROM t1 JOIN t2 ON t1.a = t2.a;
Empty set (0.14 sec)
mysql> SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a;
16305 rows in set (0.14 sec)

LEFT JOINも早くなった!

mysql> EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+--------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |   100.00 | NULL                                       |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 16207 |   100.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

8.0.20 では、EXPLAIN ANALYZEでなくても、HASH JOINかどうか区別できるようになってる(Using join buffer (hash join))。

HASH JOINをOFFにできなくなってた

optimizer_switch の hash_join を OFF にしても、、、

HASH JOINが使われてしまう!

Beginning with MySQL 8.0.19, the hash_join optimizer switch and the HASH_JOIN and NO_HASH_JOIN optimizer hints no longer have any effect.

8.0.19 から、optimizer_switch や ヒント句でコントロールできなくなってしまったようです。

良く使う pt-query-digest のコマンド メモ

ただのメモです。

pt-query-digest

percona tookit に含まれるツールの1つ。 スロークエリログやtcpdump からクエリを抽出して、クエリを集計するツール。 負荷をかけているクエリを洗い出すのに便利なヤツ。

https://www.percona.com/doc/percona-toolkit/LATEST/pt-query-digest.html

いつものコマンド

  • InnoDB の IO の多い順でソート

InnoDB_IO_r_bytes は Vanilla MySQLでは出力されない。Percona でしか動かない。

$ pt-query-digest  --group-by fingerprint --order-by InnoDB_IO_r_bytes:sum --since 'YYYY-MM-DD HH:MM:SS' --until 'YYYY-MM-DD HH:MM:SS' mysqld-slow.log 
  • 実行時間の合計でソート

Query_time の合計でソート

$ pt-query-digest  --group-by fingerprint --order-by Query_time:sum  --since 'YYYY-MM-DD HH:MM:SS' --until 'YYYY-MM-DD HH:MM:SS' mysqld-slow.log 
  • たくさん行を読み取っているヤツ

Rows_examined の合計でソート

pt-query-digest  --group-by fingerprint --order-by Rows_examined:sum  --since 'YYYY-MM-DD HH:MM:SS' --until 'YYYY-MM-DD HH:MM:SS' mysqld-slow.log 
  • 回数でソート

〜:cnt とする。Query_time の部分は何でも良い。

pt-query-digest  --group-by fingerprint --order-by Query_time:cnt  --since 'YYYY-MM-DD HH:MM:SS' --until 'YYYY-MM-DD HH:MM:SS' mysqld-slow.log 

おまけ

MySQL 8 で FLUSH STATUS したら、Bytes_received やら Read_first がありえ無い大きな値でログが出力された。

# Time: 2020-04-18T13:38:45.798080+09:00
# User@Host: root[root] @ localhost []  Id:   162
# Query_time: 0.002119  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0 Thread_id: 162 Errno: 0 Killed: 0 Bytes_received: 18446744073709551112 Bytes_sent: 18446744073709548672
 Read_first: 18446744073709551612 Read_last: 0 Read_key: 18446744073709551597 Read_next: 18446744073709551611 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 18446744073709551609 Sort_me
rge_passes: 0 Sort_range_count: 0 Sort_rows: 18446744073709551608 Sort_scan_count: 18446744073709551613 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2020-04-18T13:38:4
5.795961+09:00 End: 2020-04-18T13:38:45.798080+09:00
SET timestamp=1587184725;
flush status;

MySQL の feature request を眺める

feature request

MySQL のバグは bugs.mysql.com で管理されています。 このサイトにはバグだけでなく、機能改善(feature request) の要望も投稿できます。

bugs.mysql.com

眺めてみる

普段はバグ(不具合)に注目してみているのですが、今日は、FRをみてみようと思います。

Advanced Search から StatusがVerified、Severity が S4(Feature request ) を条件として検索してみます。 ID順でソートし、最近投稿されたものを見てみます。

f:id:mita2db:20200418141732p:plain
bugs.mysql.com

これ確かに欲しい!と思ったら

Affects me というボタンがあります。「このバグやリクエストは自分にも関係がある」ことを示すものです。投票機能のようなものですね。 Affects me の数が多ければ、MySQLの開発者の人たちは、「このリクエストはニーズが高い」とわかるわけです。もしかしたら、優先して実装してくれるかもしれません。

f:id:mita2db:20200418150836p:plain
affectsme

ということで、「これ良さそう!」というFeature request があったら、Affects Me しましょう。

when log_slow_extra is on,add some new status

スロークエリに出力されるステータスを増やして欲しい。具体的には、InnoDB_pages_distinct, InnoDB_IO_r_wait, InnoDB_rec_lock_wait, InnoDB_queue_wait があるとすごい便利!というFR。

MySQL Bugs: #99261: when log_slow_extra is on,add some new status

実は、Perconaサーバにはこの機能があります。Percona ではこのようなスロークエリログが出ます。 InnoDB_IO_r_bytes が大きいものを見れば、DISK IOに負荷をかけているSQLがすぐ特定できます。実際、使っていて、非常に便利です。

# User@Host: mailboxer[mailboxer] @  [192.168.10.165]
# Thread_id: 11167745  Schema: board
# Query_time: 1.009400  Lock_time: 0.000190  Rows_sent: 4  Rows_examined: 1543719  Rows_affected: 0  Rows_read: 4
# Bytes_sent: 278  Tmp_tables: 0  Tmp_disk_tables: 0  Tmp_table_sizes: 0
# InnoDB_trx_id: 1500
# QC_Hit: No  Full_scan: Yes  Full_join: No  Tmp_table: No  Tmp_table_on_disk: No
# Filesort: No  Filesort_on_disk: No  Merge_passes: 0
#   InnoDB_IO_r_ops: 6415  InnoDB_IO_r_bytes: 105103360  InnoDB_IO_r_wait: 0.001279
#   InnoDB_rec_lock_wait: 0.000000  InnoDB_queue_wait: 0.000000
#   InnoDB_pages_distinct: 6430
SET timestamp=1346844943;
SELECT id,title,production_year FROM title WHERE title = 'Bambi';

これは絶対便利なので、みんなで Affects Me しましょう。

JSON Path implement negative offset

MySQL Bugs: #99238: JSON Path implement negative offset

JSONで配列から値を取り出すときに、「後ろから数えて何番目」という指定方法をサポートしてほしい。というFR。

多くのプログラミング言語では、配列の要素を指定するときにマイナスで「後ろから数えて何番目」と指定できますね。

mysql> SELECT JSON_EXTRACT(JSON_ARRAY(1, 2, 3), '$[-1]');

Insufficient information in explain

MySQL Bugs: #99107: Insufficient information in explain

EXPLAINしたときに、エイリアスではなくスキーマ名とテーブル名を出してほしい。というFR。 テーブルが多いと、エイリアスとテーブルのマッピングが覚えきれずに、苦労する。。。 昔からこの仕様なので、過去にも同様のFRは出ていそうですが、、、

-- EXPLAINにはテーブル名が表示されずにエイリアスである「x」が表示される
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: x
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 986400
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

これも、ぜひ対応してほしい。Affects Me!

Manage buf blocks for InnoDB temp tablespace separately in buffer pool

MySQL Bugs: #99020: Manage buf blocks for InnoDB temp tablespace separately in buffer pool

InnoDB をテンポラリテーブルとして利用した場合、パフォーマンス劣化してしまうケースがある。internal_tmp_disk_storage_engine = MYISAM を指定して回避できていたが、8.0.16 から internal_tmp_disk_storage_engine が削除され、InnoDBしか選択できなくなってしまた。InnoDB はテンポラリテーブルとして利用するには、まだまだオーバーヘッドがあるので、改善してほしい。というFR。

MySQL 各インデックスごとの容量を確認する

お題

  • 不要そうなインデックスを削除して、どれぐらい容量が減るのか確認したい
  • information_schema.tablesINDEX_LENGTH では合計容量しか見えない
mysql> SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH 
  FROM information_schema.tables WHERE TABLE_NAME = 'sbtest1' \G
*************************** 1. row ***************************
TABLE_NAME< TABLE_ROWS: 1
           DATA_LENGTH: 225132544
          INDEX_LENGTH: 85295104
1 row in set, 1 warning (0.00 sec)
  • 複数のインデックスの合計サイズではなく、個別のインデックスのサイズが知りたい

innodb_index_stats テーブルを見る

v5.6 で試しています。innodb_index_stats テーブル でインデックスのページ数が確認できます。 InnoDBのページサイズ x ページ数 でだいたいの容量がわかりそうです。

mysql > SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb 
FROM innodb_index_stats WHERE index_name = 'pad_1' and stat_name = 'size';
+------------+------------+-------------+
| stat_value | index_name | size_mb     |
+------------+------------+-------------+
|       4341 | pad_1      | 67.82812500 |
+------------+------------+-------------+
1 row in set (0.00 sec)

このpad_1 インデックスの論理容量は67MBぐらいでした。インデックスが断片化している場合は、実際の容量より大きな容量が表示される可能性があります。 今回は断片化してないデータで試したため、正確な値が出ているはずです。

インデックスを削除して減る容量を確認する

innodb_index_stats で見積もった容量が正しいかどうか、実際に削除してみます。

削除前:300MB です。

$  ls -alh ./data/sbtest/sbtest1.ibd
-rw-rw---- 1 samitani users 300M Mar 29 16:18 ./data/sbtest/sbtest1.ibd

DROPし、ALTER TABLE で空いた物理領域を開放します。

mysql > DROP INDEX pad_1 ON sbtest.sbtest1;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql > ALTER TABLE sbtest.sbtest1 ENGINE=InnoDB;
Query OK, 0 rows affected (7.58 sec)
Records: 0  Duplicates: 0  Warnings: 0

削除後:232MBです。だいたい、見積もり通りに小さくなりました!

$  ls -alh ./data/sbtest/sbtest1.ibd
-rw-rw---- 1 samitani users 232M Mar 29 16:23 ./data/sbtest/sbtest1.ibd

MySQL Clone Plugin を利用してスレーブが簡単に作れた

お題

  • MySQL 8.0 で導入された、Clone Plugin を利用してスレーブを作ってみる dev.mysql.com

やってみる

Clone プラグインをマスターとスレーブ、両方に設定しておきます。

MySQL 8.0 から plugin-load-add という書き方がサポートされました。従来のplugin-loadと比較して設定ファイルが見やすく書けますね。

$ sudo vi /etc/my.cnf
plugin_load_add                     = mysql_clone.so

SHOW PLUGINSプラグインがインストールできたことが確認できる。

mysql> pager grep -i clone
PAGER set to 'grep -i clone'

mysql> SHOW PLUGINS;
| clone                           | ACTIVE   | CLONE              | mysql_clone.so     | GPL     |
47 rows in set (0.01 sec)

clone用のユーザを作ります。BACKUP_ADMIN権限が必要です。

mysql> CREATE USER clone_user IDENTIFIED BY 'Password123!';
Query OK, 0 rows affected (0.05 sec)

mysql> GRANT BACKUP_ADMIN ON *.* TO clone_user;
Query OK, 0 rows affected (0.03 sec)

マスターの gtid_executed は以下です。CLONE INSTANCE でデータを正しくコピーできれば、スレーブの gtid_executedも以下になるはずです。

mysql> SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
+---------------+------------------------------------------+
| Variable_name | Value                                    |
+---------------+------------------------------------------+
| gtid_executed | e9ec3499-62a3-11ea-8abd-901b0e97e768:1-3 |
+---------------+------------------------------------------+
1 row in set (0.00 sec)

スレーブでCLONE INSTANCEを実行しマスターのデータをコピーします。

mysql> SET GLOBAL clone_valid_donor_list = '192.168.10.3:3306';
Query OK, 0 rows affected (0.00 sec)

mysql> CLONE INSTANCE FROM clone_user@192.168.10.3:3306 IDENTIFIED BY 'Password123!';
Query OK, 0 rows affected (9.60 sec)

データがコピーされ、gtid_executed も一致しました。

mysql> SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
+---------------+------------------------------------------+
| Variable_name | Value                                    |
+---------------+------------------------------------------+
| gtid_executed | e9ec3499-62a3-11ea-8abd-901b0e97e768:1-3 |
+---------------+------------------------------------------+
1 row in set (0.00 sec)

あとは、いつもどおり、CHANGE MASTER するだけ。

mysql> CHANGE MASTER TO MASTER_HOST='master', MASTER_PORT=3306,
MASTER_USER='repl', MASTER_PASSWORD='replpassword', MASTER_AUTO_POSITION=1, 
MASTER_HEARTBEAT_PERIOD=2, 
MASTER_CONNECT_RETRY=2, 
MASTER_RETRY_COUNT=86400,
MASTER_SSL=1;

mysqldump したり、手で物理コピーするのに比べて遥かに簡単にスレーブが作れてしまいました。ぱちぱちぱち。

ROW_FORMATとCHECKSUM TABLEの結果の違い

お題

  • CHECKSUM TABLEROW_FORMAT が異なっても同じ値を出すときがある。
    • どういう場合に、ROW_FORMAT が異なっていても、チェックサムが一致するのか?

CHECKSUM TABLE(おさらい)

CHECKSUM TABLE はテーブルの中身のチェックサムを取得するコマンドです。 比較対象のデータベースサーバで、静止点を設けられないとデータの比較ができないわけですが、 pt-table-checksum と違って、コマンド1つでデータを比較できるので手軽ですね。

データは一致しているのにCHECKSUMの結果が違うパターン

MySQLのバージョンによる違い

こちらはマニュアルにも書いてあります。5.6で TIME, DATETIME, TIMESTAMP 型の実装が変わりました。 5.5 と 5.6以降で時刻を含むテーブルのCHECKSUMの値は一致しなくなりました。

The checksum value depends on the table row format. If the row format changes, the checksum also changes.

For example, the storage format for temporal types such as TIME, DATETIME, and TIMESTAMP changed in MySQL 5.6 prior to MySQL 5.6.5, so if a 5.5 table is upgraded to MySQL 5.6, the checksum value may change.

サーバにインストールされているMySQLのバージョンでなく、データファイルが作られた時のバージョンに依存する点に注意です。 5.5をアップグレードし5.5の時に作成されたデータファイルを引き継いでいる場合は、CHECKSUMの値は、5.5のままです。

ROW_FORMATの違い

The checksum value depends on the table row format. If the row format changes, the checksum also changes.

と記載のあるように、ROW_FORMATが違う場合は、データが同じでも異なるチェックサムになる可能性があります。

実際に試します

COMPACTDYNAMICで比較してみます。 FLOAT, DOUBLE型の場合、ROW_FORMATの違いによりチェックサムが異なりました。

mysql> CREATE TABLE t_float_compact (syousu FLOAT) ENGINE=InnoDB ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t_float_dynamic (syousu FLOAT) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO t_float_compact VALUES(1.11111);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t_float_dynamic VALUES(1.11111);
Query OK, 1 row affected (0.00 sec)
mysql> CHECKSUM TABLE t_float_compact EXTENDED;
+-------------------+-----------+
| Table             | Checksum  |
+-------------------+-----------+
| t.t_float_compact | 108074716 |
+-------------------+-----------+
1 row in set (0.00 sec)

mysql> CHECKSUM TABLE t_float_dynamic EXTENDED;
+-------------------+------------+
| Table             | Checksum   |
+-------------------+------------+
| t.t_float_dynamic | 1104243724 |
+-------------------+------------+
1 row in set (0.00 sec)

しかし、VARCHAR は問題ないようです。

mysql> CHECKSUM TABLE t_varchar_compact EXTENDED;
+---------------------+------------+
| Table               | Checksum   |
+---------------------+------------+
| t.t_varchar_compact | 4048785154 |
+---------------------+------------+
1 row in set (0.00 sec)

mysql> CHECKSUM TABLE t_varchar_dynamic EXTENDED;
+---------------------+------------+
| Table               | Checksum   |
+---------------------+------------+
| t.t_varchar_dynamic | 4048785154 |
+---------------------+------------+
1 row in set (0.00 sec)

一通りの型を試すと以下のようになりました。

ROW_FORMAT=COMPACT/DYNAMIC による CHECKSUM 値の違い
CHAR あり
VARCHAR なし
TEXT なし
INT なし
DATETIME あり
TIMESTAMP あり
FLOAT あり
DOUBLE あり

複雑な条件がありそう

上記のように型に依存してCHECKSUMへの影響の有無が判断できるかというと、そうでもなさそうです。 以下は、VARCHARDATETIME を組み合わせた場合です。CHECKSUMの値は一致しています。

mysql> CREATE TABLE t_datetime_compact (c1 varchar(250) NOT NULL, hiduke DATETIME) ENGINE=InnoDB ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t_datetime_dynamic (c1 varchar(250) NOT NULL, hiduke DATETIME) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO t_datetime_compact VALUES('aaaa', '2020-01-01 10:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t_datetime_dynamic VALUES('aaaa', '2020-01-01 10:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> CHECKSUM TABLE t_datetime_compact EXTENDED;
+----------------------+------------+
| Table                | Checksum   |
+----------------------+------------+
| t.t_datetime_compact | 1594517058 |
+----------------------+------------+
1 row in set (0.00 sec)

mysql> CHECKSUM TABLE t_datetime_dynamic EXTENDED;
+----------------------+------------+
| Table                | Checksum   |
+----------------------+------------+
| t.t_datetime_dynamic | 1594517058 |
+----------------------+------------+
1 row in set (0.00 sec)

しかし、DATETIME型、単体だと、CHECKSUMが異なります・・・

mysql> CREATE TABLE t_datetime_compact (hiduke DATETIME) ENGINE=InnoDB ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE t_datetime_dynamic (hiduke DATETIME) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t_datetime_compact VALUES('2020-01-01 10:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO t_datetime_dynamic VALUES('2020-01-01 10:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> CHECKSUM TABLE t_datetime_compact EXTENDED;
+----------------------+------------+
| Table                | Checksum   |
+----------------------+------------+
| t.t_datetime_compact | 1668604533 |
+----------------------+------------+
1 row in set (0.00 sec)

mysql> CHECKSUM TABLE t_datetime_dynamic EXTENDED;
+----------------------+------------+
| Table                | Checksum   |
+----------------------+------------+
| t.t_datetime_dynamic | 3856699611 |
+----------------------+------------+
1 row in set (0.00 sec)

FLOAT型も同様でした。

まとめ

  • ROW_FORMATが違う場合は 型によってCHECKSUM TABLEの結果が異なる場合がある
  • しかし、型の組み合わせによっては、同じチェックサムになる場合がある