41から始めました

文字通り41歳から始めたブログです。DB(MySQL)を使ってお仕事してるので、DB周りの話を中心に最近始めたこととかをTwitterのノリで書いています。なお、本サイトにおいて示されている見解は私個人の見解であり、所属団体や組織を代表するものではありません。

シンキノウ×デ×シルキノウ (CREATE TABLE ... SELECT)

Myリノベ 8.0.21に参加

先日MySQL Release note でわいわい言う勉強会 8.0.21」に参加してまいりました。

今回も最新のMySQL8.0.21のリリースノートをみんなで見ながら、思いつくままに語る会で、その中で僕は相変わらずROM専ですが参加しておりました。

流れとしては坂井さんがオープニングトーク、次にまつさんがJSON_VALUE()のLTを語ってくださり、その他の機能についてはリリースノートを見ながら…という感じでした。

いずれyoutubeあたりに動画が上がると思いますので、全体が知りたい方はtwitterやslackでチェックしてるとよいかと。

8.0.21からはCREATE TABLE ... SELECT気軽に使えるぜ!

さて、その会の中でyoku0825さんがつぶやいたCREATE TABLE ... SELECTの以下の話、マイナーかもしれないけど結構驚きの変更だと思うんですよね。

On storage engines that support atomic DDL, the CREATE TABLE ... SELECT statement is now logged as one transaction in the binary log when row-based replication is in use.
Previously, it was logged as two transactions, one to create the table, and the other to insert data.
With this change, CREATE TABLE ... SELECT statements are now safe for row-based replication and permitted for use with GTID-based replication.
For more information, see Atomic Data Definition Statement Support.

見たことないですか?

ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT.

GTIDを使う人は頭に叩き込まれてるはずなんです。

  • 行ベースで
  • GTID使ってる場合
  • CREATE TABLE ... SELECTはできないって

だって、ドキュメントにもあるんだから。

みんな5.6以降、GTIDを使う人はCREATE TABLE ... SELECT使わないぞ!と心に誓ってたはずなんです。

でも、

MySQL8.0.21からはフツーにレプリカに反映されるようになったんです。

試してみた

作業概要

環境構築

僕はdbdeployer使ってます。とてもかんたんに環境作れますよ。

dbdeployerの詳しい説明については適当にググってもらうとかすればいいんですが、ざっくり書くと

  1. MySQLのtarファイルゲットしてきて
  2. dbdeployerインストールして
  3. dbdeployer unpack mysql-xxxxxxxx-8.0.xx-linux-x86_64-minimal.tar.xz みたいな感じで解凍して
  4. 以下のようなコマンドでレプリケーション環境を作れます。
dbdeployer deploy replication 8.0.xx \
    --custom-role-name=R_POWERFUL \
    --custom-role-privileges='ALL PRIVILEGES' \
    --custom-role-target='*.*' \
    --custom-role-extra='WITH GRANT OPTION' \
    --default-role=R_POWERFUL \
    --bind-address=0.0.0.0 \
    --remote-access='%' \
    --gtid \
    --db-user=admin \
    --db-password=xxxxx

こんな感じで2つのソースレプリカ形式のDBのペアを用意しました。

$ dbdeployer sandboxes
 rsandbox_8_0_20          :   master-slave           8.0.20   [21021 31021 21022 31022 21023 31023 ]
 rsandbox_8_0_21          :   master-slave           8.0.21   [21122 31122 21123 31123 21124 31124 ]

検証開始

そして、2つのバージョンで比較するんですが、やることはかんたん。

  1. テーブル(t1)を作って
  2. テーブルに適当にデータ入れて
  3. 一応テーブルにデータ入ったこと確認して
  4. そのテーブルからデータコピーをCREATE TABLE ...SELECT構文でやって
  5. レプリカ側のほうにあるか確認

というだけの作業。

GTIDの確認

こんな感じでGTIDモードはいずれもONです

$ mysql -uroot -pxxxxx -h127.0.0.1 -P21122 -e "show variables like 'gtid_mode'"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode     | ON    |
+---------------+-------+
$ mysql -uroot -pxxxxx -h127.0.0.1 -P21021 -e "show variables like 'gtid_mode'"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode     | ON    |
+---------------+-------+

MySQL8.0.20

ソース(マスター)側

$ mysql -uroot -pxxxxx -h127.0.0.1 -P21021 test
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 8.0.20-commercial MySQL Enterprise Server - Commercial

Copyright (c) 2000, 2020, 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 '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> CREATE TABLE `t1` (
    ->    `id` int(11) NOT NULL AUTO_INCREMENT,
    ->    PRIMARY KEY (`id`)
    ->  ) ENGINE=InnoDB;
Query OK, 0 rows affected, 1 warning (0.04 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.02 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.01 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.01 sec)

(root@127.0.0.1) [test]> select * from t1;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)

(root@127.0.0.1) [test]> CREATE TABLE t2 select * from t1;
ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT.
(root@127.0.0.1) [test]> select * from t2;
ERROR 1146 (42S02): Table 'test.t2' doesn't exist

ERROR 1786が出ますね。

当然ソース・レプリカいずれのインスタンスにもt2テーブルは作られていません。

これを8.0.21でもやります。

MySQL8.0.21

ソース(マスター)側

$ mysql -uroot -pxxxxx -h127.0.0.1 -P21122 test
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 8.0.21-commercial MySQL Enterprise Server - Commercial

Copyright (c) 2000, 2020, 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 '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> CREATE TABLE `t1` (
    ->    `id` int(11) NOT NULL AUTO_INCREMENT,
    ->    PRIMARY KEY (`id`)
    ->  ) ENGINE=InnoDB;
Query OK, 0 rows affected, 1 warning (0.02 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.02 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.00 sec)

(root@127.0.0.1) [test]> insert into t1 values(NULL);
Query OK, 1 row affected (0.00 sec)

(root@127.0.0.1) [test]> select * from t1;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.01 sec)

(root@127.0.0.1) [test]> CREATE TABLE t2 select * from t1;
Query OK, 3 rows affected (0.03 sec)
Records: 3  Duplicates: 0  Warnings: 0

(root@127.0.0.1) [test]> select * from t2;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)

エラーが出ない!

んじゃ、レプリカ側はどうなってるかな?

レプリカ(スレーブ)側

$ mysql -uroot -pxxxxx -h127.0.0.1 -P21123 test
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.0.21-commercial MySQL Enterprise Server - Commercial

Copyright (c) 2000, 2020, 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 '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> select * from t1;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)

(root@127.0.0.1) [test]> select * from t2;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)

できてるぅ!ヒャッホー!

binlogの比較

GITDがONだと比較できないので、OFFにしてmysqlbinlogで覗いてみる。

MySQL8.0.20

# at 9418
#200814 18:50:04 server id 100  end_log_pos 9543 CRC32 0xccceb3e3   Query   thread_id=12    exec_time=0 error_code=0
SET TIMESTAMP=1597398604/*!*/;
/*!80013 SET @@session.sql_require_primary_key=0*//*!*/;
CREATE TABLE `t2` (
  `id` int NOT NULL DEFAULT '0'
)
/*!*/;
# at 9543
#200814 18:50:04 server id 100  end_log_pos 9622 CRC32 0xf3e5f383   Anonymous_GTID  last_committed=38   sequence_number=39  rbr_only=yes    original_committed_timestamp=1597398604099299   immediate_commit_timestamp=1597398604099299 transaction_length=283
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1597398604099299 (2020-08-14 18:50:04.099299 JST)
# immediate_commit_timestamp=1597398604099299 (2020-08-14 18:50:04.099299 JST)
/*!80001 SET @@session.original_commit_timestamp=1597398604099299*//*!*/;
/*!80014 SET @@session.original_server_version=80020*//*!*/;
/*!80014 SET @@session.immediate_server_version=80020*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 9622
#200814 18:50:04 server id 100  end_log_pos 9697 CRC32 0x796b6528   Query   thread_id=12    exec_time=0 error_code=0
SET TIMESTAMP=1597398604/*!*/;
BEGIN
/*!*/;
# at 9697
#200814 18:50:04 server id 100  end_log_pos 9745 CRC32 0x1fde1b11   Table_map: `test`.`t2` mapped to number 112
# at 9745
#200814 18:50:04 server id 100  end_log_pos 9795 CRC32 0xc4bf3987   Write_rows: table id 112 flags: STMT_END_F

BINLOG '
TF42XxNkAAAAMAAAABEmAAAAAHAAAAAAAAEABHRlc3QAAnQyAAEDAAABAQARG94f
TF42Xx5kAAAAMgAAAEMmAAAAAHAAAAAAAAEAAgAB/wABAAAAAAIAAAAAAwAAAIc5v8Q=
'/*!*/;
# at 9795
#200814 18:50:04 server id 100  end_log_pos 9826 CRC32 0xbc8b4b0a   Xid = 71
COMMIT/*!*/;
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*/;

MySQL8.0.21

# at 9496
#200814 18:47:28 server id 100  end_log_pos 9639 CRC32 0xe24b1dc8   Query   thread_id=12    exec_time=0 error_code=0
SET TIMESTAMP=1597398448/*!*/;
/*!80013 SET @@session.sql_require_primary_key=0*//*!*/;
CREATE TABLE `t2` (
  `id` int NOT NULL DEFAULT '0'
) START TRANSACTION
/*!*/;
# at 9639
#200814 18:47:28 server id 100  end_log_pos 9687 CRC32 0x4972ee4a   Table_map: `test`.`t2` mapped to number 112
# at 9687
#200814 18:47:28 server id 100  end_log_pos 9737 CRC32 0x56fa2558   Write_rows: table id 112 flags: STMT_END_F

BINLOG '
sF02XxNkAAAAMAAAANclAAAAAHAAAAAAAAEABHRlc3QAAnQyAAEDAAABAQBK7nJJ
sF02Xx5kAAAAMgAAAAkmAAAAAHAAAAAAAAEAAgAB/wABAAAAAAIAAAAAAwAAAFgl+lY=
'/*!*/;
# at 9737
#200814 18:47:28 server id 100  end_log_pos 9768 CRC32 0xa09930d6   Xid = 71
COMMIT/*!*/;
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*/;

明らかに8.0.21のほうが短いね。

読み解いたわけじゃないけど、雰囲気は感じる。

所感

リリースノートではサラッと一行で書かれてるけど、結構な変更だと思いません?

GTIDが無効な場合でもクラッシュ時とかにレプリカにはテーブルはあるけどデータが無いとか無くなるわけだし、

GTIDが有効な場合でもエラーにならずにCREATE TABLE ... SELECTが使えるようになったわけです。

リリースノートをみんなで眺めることで、そういう知見が多くの人の目に止まったんです。

素敵な会ですね!

この話以外にも気になる機能について色々語られてました。

非公式の何かがちょろっと配布されたりして、意外と録画されていないところも楽しい勉強会w

みなさんも次回は参加してみては?

(今日はHunter×Hunter アニメ版のようなタイトルの付け方してみました)

クエリーキャッシュを有効にしたけりゃサーバー再起動

え?何当たり前のこと言ってるの?

そう思われた貴方、クエリキャッシュをちゃんと使うか使わないか最初に考えている偉い人ですね。

query_cache_typeをON(1)にするとクエリーキャッシュが有効になるんですが、これ、ドキュメントを読むと少なくともMySQL5.6以降は動的に変更(SET GLOBAL)できそうに見えます。

f:id:next4us-ti:20200719174422p:plain

5.5でもできそうに見える)

ところが、有効なのを無効にはできるんですが、無効な状態を有効にはできないんです。

試してみた

my.cnfでまず有効にした状態で起動してみます。

$ grep query_cache_type ./msb_5_7_30/my.sandbox.cnf
query_cache_type=1
$ ./msb_5_7_30/restart
stop /home/takiida/sandboxes/msb_5_7_30
. sandbox server started
$ mysql -uroot -pxxxxx -h127.0.0.1 -P5730
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.30-enterprise-commercial-advanced MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2020, 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 '\h' for help. Type '\c' to clear the current input statement.

有効な状態から無効に変更することはSET GLOBAL句で可能です。

(root@127.0.0.1) [(none)]> show global variables like 'query_cache_type';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| query_cache_type | ON    |
+------------------+-------+
1 row in set (0.01 sec)

(root@127.0.0.1) [(none)]> set global query_cache_type = 0;
Query OK, 0 rows affected, 1 warning (0.00 sec)

(root@127.0.0.1) [(none)]> show global variables like 'query_cache_type';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| query_cache_type | OFF   |
+------------------+-------+
1 row in set (0.01 sec)

(root@127.0.0.1) [(none)]> ^DBye

ところが

クエリキャッシュを無効にして再起動します。

$ grep query_cache_type ./msb_5_7_30/my.sandbox.cnf
query_cache_type=0
$ ./msb_5_7_30/restart
stop /home/takiida/sandboxes/msb_5_7_30
. sandbox server started
$ mysql -uroot -pxxxxx -h127.0.0.1 -P5730
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.30-enterprise-commercial-advanced MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2020, 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 '\h' for help. Type '\c' to clear the current input statement.

無効な状態から有効にしようとすると怒られる!

(root@127.0.0.1) [(none)]> show global variables like 'query_cache_type';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| query_cache_type | OFF   |
+------------------+-------+
1 row in set (0.00 sec)

(root@127.0.0.1) [(none)]> set global query_cache_type = 1;
ERROR 1651 (HY000): Query cache is disabled; restart the server with query_cache_type=1 to enable it

query_cache_typeを1にするんならサーバー再起動しろや!と。

クエリキャッシュ、最初から有効か、無効の環境しか使ったこと無いから知らんかった。

ドキュメントが微妙ですね…

ドキュメント的に優しくないので直るといいなぁ、と思うのでちょっとプッシュしておこうかと思います。 (まあ、MySQL8.0で廃止されたクエリーキャッシュをあとから有効にしたいとかそんなにニーズが無いから放置されてたのかもしれませんが…)

MySQL5.xではデッドロックだけど8.0では死なないよ

はじめに

タイトル通り、MySQL5.x系だとデッドロックになるんだけど、 MySQL8.0だとロック機構が変わってデッドロックにならないよ という組み合わせのお話。

僕はこの話をどっかで見た記憶が無かった(忘れた?)ので 教えてもらったとき結構驚いたんだけど、GA(8.0.11)では 既にこれがあったんで、まあ古い話なんだと思うし、 何をイマサラなのかもしれないので、ご存じの方は笑ってやってください。

ちなみに、REPEATABLE READでのお話なので、 READ COMMITTEDの場合は5系でもデッドロックにはなりまへん。

検証

今回やるのはこういうこと

  1. 最初のトランザクション(以下Tx1)でトランザクション開始、共有(S)ロックで全レコードを参照。
  2. 別のトランザクション(以下Tx2)でトランザクション開始、INSERTによる排他(X)ロック
  3. Tx1でINSERTによる排他(X)ロック

結論を先にいうと、3による結果がMySQL5.xではデッドロック発生(Tx1のINSERT死亡)、 MySQL8.0ではすんなりTx1のINSERTが通る(Tx2は待ちのまま)。

以下は自分で試してみたい人・どういうふうになるのか知りたい人のため。

準備

MySQL5.7(今回は.29を使いました)とMySQL8.0.11で以下のようなテーブルを作成する。

データベース作成

とりあえずいつものtestスキーマ

(root@127.0.0.1) [(none)]> create database test;
Query OK, 1 row affected (0.01 sec)

testにログイン

(root@127.0.0.1) [(none)]> use test;
Database changed

テーブル作成

とりあえずのtテーブル(とりあえずすぎるネーミング)

(root@127.0.0.1) [test]> CREATE TABLE `t` (
    ->   `id` int NOT NULL AUTO_INCREMENT,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.08 sec)

1行インサート

(root@127.0.0.1) [test]> insert t select null;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

(root@127.0.0.1) [test]> select * from t;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

ここまでで準備はOK

MySQL5.x系(デッドロックになるパターン)

Tx1で共有ロック

$ mysql -uroot -pxxxxxx -h127.0.0.1 -P5729 test
mysql: [Warning] Using a password on the command line interface can be insecure.
…
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> begin; select * from t where id >=1 lock in share mode;
Query OK, 0 rows affected (0.00 sec)

+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

Tx2で排他ロック

$ mysql -uroot -pxxxxxx -h127.0.0.1 -P5729 test
mysql: [Warning] Using a password on the command line interface can be insecure.
…
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> begin; insert into t select null;
Query OK, 0 rows affected (0.00 sec)

beginは通ったが、insertは待たされている。

Tx1でINSERTするとデッドロック発生

違和感はあるものの、MySQLのRRでのロックから考えると納得はする。(MySQLにマヒしてる?)

(root@127.0.0.1) [test]> insert into t select null;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

とはいえ、別トランザクションでロックかけられて、自分のトランザクションで知らずにデッドロックとかやはり悲しいものがある。

それがMySQL8.0では改善していた!

MySQL8.0系(デッドロックにならないパターン)

Tx1で共有ロック

$ mysql -uroot -pxxxxx -h127.0.0.1 -P8011 test
mysql: [Warning] Using a password on the command line interface can be insecure.
…
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> begin; select * from t where id >=1 lock in share mode;
Query OK, 0 rows affected (0.00 sec)

+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

Tx2で排他ロック

$ mysql -uroot -pxxxxx -h127.0.0.1 -P8011 test
mysql: [Warning] Using a password on the command line interface can be insecure.
…
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

(root@127.0.0.1) [test]> begin; insert into t select null;
Query OK, 0 rows affected (0.00 sec)

beginは通ったが、insertは待たされている。 ここまで全く同じだが、次が違う

Tx1ではTx2の排他ロックを気にしない

(root@127.0.0.1) [test]> insert t select null;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

(root@127.0.0.1) [test]> select * from t;
+----+
| id |
+----+
|  1 |
|  3 |
+----+
2 rows in set (0.00 sec)

でも、ちゃんと「id=2」はTx2が抑えているのを考慮している!

Tx2は待ち疲れでタイムアウト

でも、Tx1がいつまでもコミットしてくれないと、Tx2はタイムアウトしちゃいますw

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

MySQL8.0のロック、だいぶ賢い!

逆に変わりすぎてて、こことかに明記されててもおかしくないと思ったんだが…。

追記(変わった理由)

この記事書いたとき気づいてなかったんだけど、innodb_autoinc_lock_modeがデフォルト値1から2に変わったことが理由ですね。

パラメータの差異確認して、かつtom__boさんのWeeklyのコメントも見ておそらくこれやなと思ってたけど、検証するのサボってましたw

innodb_autoinc_lock_modeについては第49回のMySQL道普請を参考にすると良いです。

MySQL5.7の段階でbinlog-formatがROWに変わってるのでinnodb_autoinc_lock_modeのデフォルトも変わってても良かった気がする。

パーティションほとんどわからんので触ってみた

パーティショニングしたテーブルの確認方法

MySQL触ってて、実は仕事上では一度も触ったことがなかったパーティショニング。

(だって、色んな人が使えないって言ってたんだもん)

だけど、意外と世の中使ってる人は多いので今後のためにもパーティショニングしたテーブルの確認方法を色々見てみました。

まずはパーティションテーブル作る

ドキュメントからテーブルを拝借しました。

パーティションタイプはレンジ、リスト、ハッシュ、キーの4つがあるが、今回はハッシュにしました。 (特に意味はないです。適当に選んだだけです)

mysql [localhost:8020] {admin} (test) > CREATE TABLE ti (
    ->     id INT,
    ->     amount DECIMAL(7 , 2 ),
    ->     tr_date DATE
    -> )  ENGINE=INNODB PARTITION BY HASH (MONTH(tr_date)) PARTITIONS 6;
Query OK, 0 rows affected (0.09 sec)

適当に何件か入れてみる

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (1,
    -> 100,
    -> '2020-01-01');
Query OK, 1 row affected (0.02 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (1,
    -> 101,
    -> '2020-01-02');
Query OK, 1 row affected (0.01 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (2,
    -> 200,
    -> '2020-02-02');
Query OK, 1 row affected (0.01 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (2,
    -> 201,
    -> '2020-02-01');
Query OK, 1 row affected (0.00 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (3,
    -> 301,
    -> '2020-03-01');
Query OK, 1 row affected (0.00 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (4,
    -> 400,
    -> '2020-04-01');
Query OK, 1 row affected (0.00 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (5,
    -> 500,
    -> '2020-05-01');
Query OK, 1 row affected (0.00 sec)

mysql [localhost:8020] {admin} (test) > INSERT INTO `test`.`ti`
    -> (`id`,
    -> `amount`,
    -> `tr_date`)
    -> VALUES
    -> (6,
    -> 600,
    -> '2020-06-01');
Query OK, 1 row affected (0.01 sec)

6件ほど入れてみました。

パーティションテーブルの確認

SHOW CREATE TABLE

SHOW CREATE TABLEではパーティション化されたテーブルの作成に使用されたパーティショニング句を表示します。

mysql [localhost:8020] {admin} (test) > show create table `test`.`ti`\G
*************************** 1. row ***************************
       Table: ti
Create Table: CREATE TABLE `ti` (
  `id` int DEFAULT NULL,
  `amount` decimal(7,2) DEFAULT NULL,
  `tr_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY HASH (month(`tr_date`))
PARTITIONS 6 */
1 row in set (0.00 sec)

SHOW TABLE STATUS

SHOW TABLE STATUSではテーブルがパーティション化されているかどうかを判別します。

mysql [localhost:8020] {admin} (test) > show table status like 'ti'\G
*************************** 1. row ***************************
           Name: ti
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 8
 Avg_row_length: 12288
    Data_length: 98304
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2020-06-19 21:31:01
    Update_time: 2020-06-19 21:34:08
     Check_time: NULL
      Collation: utf8mb4_0900_ai_ci
       Checksum: NULL
 Create_options: partitioned
        Comment: 
1 row in set (0.01 sec)

Create_optionsがpartitionedとなっているので、パーティション化されているテーブルです、と。

まあ、ここではただそれだけなんですねw

information_schema.partitions

I_Sのpartitionsビューでも確認できます。 ここではパーティションごとに行が作られますので、先のshow create tableよりも詳細な情報が得られます。

mysql [localhost:8020] {admin} (test) > select * from information_schema.partitions where table_schema = 'test' and table_name = 'ti'\G
*************************** 1. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p0
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 1
               AVG_ROW_LENGTH: 16384
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:34:08
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 2. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p1
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 2
               AVG_ROW_LENGTH: 8192
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:32:58
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 3. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p2
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 3
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 2
               AVG_ROW_LENGTH: 8192
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:33:21
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 4. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p3
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 4
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 1
               AVG_ROW_LENGTH: 16384
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:33:31
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 5. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p4
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 5
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 1
               AVG_ROW_LENGTH: 16384
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:33:45
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 6. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p5
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 6
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 1
               AVG_ROW_LENGTH: 16384
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 21:31:01
                  UPDATE_TIME: 2020-06-19 21:33:55
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
6 rows in set (0.00 sec)

パーティションのタイプもわかりますし、最後にデータが更新された時間やどのパーティションに何件入ってるかなどもわかりますね。

EXPLAIN

EXPLAINで指定された SELECT によってどのパーティションが使用されているかを確認することができます。

mysql [localhost:8020] {admin} (test) > EXPLAIN select * from `test`.`ti`\G

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ti
   partitions: p0,p1,p2,p3,p4,p5
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 8
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.02 sec)

partitions列に作られたパーティションが表示されていますね。

mysql [localhost:8020] {admin} (test) > EXPLAIN select * from `test`.`ti` where tr_date  = '2020-01-01';
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ti
   partitions: p1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 50.00
        Extra: Using where
1 row in set, 1 warning (0.01 sec)

WHERE句に条件を入れると、使用されたパーティションだけが表示されました。

ALTER TABLE ...COALESCE PARTITION

昔レンジパーティションの記事を読んだときにalter table drop partitionってあったな、と思ったらドキュメント

DROP PARTITION can be used to drop one or more RANGE or LIST partitions. This statement cannot be used with HASH or KEY partitions; instead, use COALESCE PARTITION (see later in this section).

(DROP PARTITIONはRANGE または LIST パーティションでのみ使え、HASH または KEY パーティションに使用することはできません。HASH または KEY パーティションの場合は代わりに COALESCE PARTITION を使用します。)

とあったのでALTER TABLE ...COALESCE PARTITIONでパーティションを減らしました。

ちなみにCOALESCE PARTITION で指定する数字は減らすパーティション数なのでご注意を。

試しにやってみます

mysql [localhost:8020] {admin} (test) > alter table ti drop partition p0;
ERROR 1512 (HY000): DROP PARTITION can only be used on RANGE/LIST partitions

mysql [localhost:8020] {admin} (test) > ALTER TABLE ti COALESCE PARTITION 4;
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

さて、確認してみましょう。

SHOW CREATE TABLE

mysql [localhost:8020] {admin} (test) > show create table `test`.`ti`\G
*************************** 1. row ***************************
       Table: ti
Create Table: CREATE TABLE `ti` (
  `id` int DEFAULT NULL,
  `amount` decimal(7,2) DEFAULT NULL,
  `tr_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY HASH (month(`tr_date`))
PARTITIONS 2 */
1 row in set (0.00 sec)

4つ減らしたので2になってます。

SHOW TABLE STATUS

まあ、これは変わりませんねw

mysql [localhost:8020] {admin} (test) > show table status like 'ti'\G
*************************** 1. row ***************************
           Name: ti
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 8
 Avg_row_length: 12288
    Data_length: 98304
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2020-06-19 22:27:58
    Update_time: 2020-06-19 21:34:08
     Check_time: NULL
      Collation: utf8mb4_0900_ai_ci
       Checksum: NULL
 Create_options: partitioned
        Comment: 
1 row in set (0.00 sec)

information_schema.partitions

mysql [localhost:8020] {admin} (test) >  select * from information_schema.partitions where table_schema = 'test' and table_name = 'ti'\G
*************************** 1. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p0
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 22:27:58
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 2. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p1
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-19 22:27:58
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
2 rows in set (0.00 sec)

TABLE_ROWSが0になってますね・・・

ドキュメントには

The data contained in the last number partitions will be merged into the remaining partitions. In this case, partitions 4 and 5 will be merged into the first 4 partitions (the partitions numbered 0, 1, 2, and 3).

とあるんで、これらに入ると思ったんですがふっとんだんでしょうか?

mysql [localhost:8020] {admin} (test) > select * from ti;
+------+--------+------------+
| id   | amount | tr_date    |
+------+--------+------------+
|    6 | 600.00 | 2020-06-01 |
|    2 | 200.00 | 2020-02-02 |
|    2 | 201.00 | 2020-02-01 |
|    4 | 400.00 | 2020-04-01 |
|    1 | 100.00 | 2020-01-01 |
|    1 | 101.00 | 2020-01-02 |
|    3 | 301.00 | 2020-03-01 |
|    5 | 500.00 | 2020-05-01 |
+------+--------+------------+
8 rows in set (0.00 sec)

ありますねえ…。

この後、再起動したり、データ入れてみたりしましたが、今まで入ってた分はTABLE_ROWSに戻らず。

EXPLAIN

(root@localhost) [test]> EXPLAIN select * from `test`.`ti`\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ti
   partitions: p0,p1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.02 sec)

partitionsはp0,p1 だけになってますね。

こちらもrowsが1(試しに後から入れた1件)のみ…。

どうしても行数を復活させたい

というわけで試すことにしました。

ALTER TABLE ... REBUILD PARTITION;

ALTER TABLE ... REBUILD PARTITIONでパーティションを再構築してみます。

ドキュメントでは

Rebuilds the partition; this has the same effect as dropping all records stored in the partition, then reinserting them. This can be useful for purposes of defragmentation.

パーティションに格納されているすべてのレコードを削除してからそれらを再度挿入することと同じ効果があります。これはデフラグに役立つことがあります。)

とあるので、再構築すれば読めるようになるのでは?

(root@localhost) [test]> alter table ti rebuild partition p0,p1;
Query OK, 0 rows affected (0.10 sec)
Records: 0  Duplicates: 0  Warnings: 0
(root@localhost) [test]> select * from information_schema.partitions where table_schema = 'test' and table_name = 'ti'\G
*************************** 1. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p1
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-20 08:47:54
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 2. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p0
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-20 08:47:54
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
2 rows in set (0.01 sec)

残念、再構築では入りませんでした。

ALTER TABLE ... OPTIMIZE PARTITION

次にパーティションの最適化をやってみます。

ドキュメントでは

ALTER TABLE ... OPTIMIZE PARTITION を使用して、未使用領域を解放したりパーティションデータファイルをデフラグしたりできます。 (パーティションから多数の行を削除した場合、または可変長行を持つ (つまり、VARCHAR、BLOB、または TEXT カラムを持つ) パーティション化されたテーブルに多くの変更を行なった場合は、 ALTER TABLE ... OPTIMIZE PARTITION を使用して、未使用領域を解放したりパーティションデータファイルをデフラグしたりできます。)

とあるので、少しイメージと違うような気持ちもしますが試してみましょう。

(root@localhost) [test]> ALTER TABLE ti OPTIMIZE PARTITION p0, p1;
+---------+----------+----------+---------------------------------------------------------------------------------------------+
| Table   | Op       | Msg_type | Msg_text                                                                                    |
+---------+----------+----------+---------------------------------------------------------------------------------------------+
| test.ti | optimize | note     | Table does not support optimize on partitions. All partitions will be rebuilt and analyzed. |
| test.ti | optimize | status   | OK                                                                                          |
+---------+----------+----------+---------------------------------------------------------------------------------------------+
2 rows in set (0.08 sec)

1パーティションに対してはできないよ!という注意が見えますが、ちゃんと全パーティションを選択しているのでOKとなってます。

(root@localhost) [test]> select * from information_schema.partitions where table_schema = 'test' and table_name = 'ti'\G
*************************** 1. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p1
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 5
               AVG_ROW_LENGTH: 3276
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-20 08:48:16
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
*************************** 2. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test
                   TABLE_NAME: ti
               PARTITION_NAME: p0
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: HASH
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: month(`tr_date`)
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: NULL
                   TABLE_ROWS: 4
               AVG_ROW_LENGTH: 4096
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: 0
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2020-06-20 08:48:16
                  UPDATE_TIME: NULL
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT: 
                    NODEGROUP: default
              TABLESPACE_NAME: NULL
2 rows in set (0.00 sec)

おお、TABLE_ROWSが見事に見えるようになりました。

正解はOPTIMIZE PARTITIONだったようです。

(root@localhost) [test]> EXPLAIN select * from `test`.`ti`\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ti
   partitions: p0,p1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 9
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

EXPLAINでもrowsが 9に見えてます。

最後に

今日はパーティションと少し戯れてみました。

パーティションの数変更した後に確認しようとしたときに行数が狂っちゃう(0になっちゃう)のはイケてないと思うんだけど、直し方はありました。

(バグ登録されてるのかな?あとで見てみよう)

まだ触りたてなので、他の3つのパーティションタイプやそれぞれでのALTER TABLE ... ANALYZE PARTITION、CHECK PARTITION、REPAIR PARTITION、REMOVE PARTITIONINGあたりも触ってないのでそのへんも気が向いたら手を出していきます。

(でも、そもそも使うとしたらみなさんレンジやリストのタイプなのかしら?)

あ、あと、MySQL5.6ではEXPLAINではなくて、EXPLAIN PARTITIONSなのでお間違えなく

(root@127.0.0.1) [test]> EXPLAIN PARTITIONS select * from ti\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: ti
   partitions: p0,p1,p2,p3,p4,p5
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: NULL
1 row in set (0.00 sec)

参考

dev.mysql.com

dev.mysql.com

dev.mysql.com

【ポエムというか愚痴】おれはソネットをやめるぞ!ジョジョーーーーーッ‼

全くMySQLに関係ない、でも、きっかけは家で仕事するのに不都合だったから起こった身の上の話

光を超越する!

オトコは悩んでいた。

家は東京のとある集合住宅、150以上の世帯が住むマンション。

その家の光回線が遅いのである。

回線速度を測ると、1Gbpsをうたっているはずの光回線がその100分の1も出ないのである。

ひどいときには1000分の1になるときもあった。

オトコの仕事はサポート業務。

お客さんがトラブルを解消したい場合や、知見を得たいというときにそれを補助する仕事だ。

トラブルの時はログファイル等を添付していることもある。

あるバージョンのアプリケーションで問題が発生しているといったときはそのバージョンのアプリケーションをダウンロードして検証しなければならない。

オトコはそれまで家で仕事をしたことが無かった。

仕事は会社でするもの。

家でするのはあくまで緊急時、特に深夜のみだった。

しかし、コロナウイルスが家で仕事を強要した。

もちろん、それはオトコだけでなく、オトコの妻も、マンションに住む全ての社会人がそうなった。

そして、子供たちも学校や幼稚園・保育園に行けず、家にいることになった。

彼らは何をしただろう。

恐らく、オトコの家と同じく子供はインターネット経由で動画を楽しみ、親はリモートワークでインターネットを使ったのだろう。

同じ時間帯にそれらがマンションの半分、いや3分の1の世帯で起こったとしたら…。

マンションは築40年を超える物件だ。

今流行りの1Gbpsの光回線を入れることはできないらしい。

光と言っても100Mbpsが最大の貧弱な回線だ。

そこに一斉にインターネットを使う状況ができれば、それはそうなるのは明らかである。

オトコは決断した。

光をやめて、無線にしよう!

携帯のほうが速度も出ている。

速度の出ない光を使い続けるくらいなら無線でその数倍~数十倍の速度でやったほうが仕事がはかどるハズだ!

プロバイダーはそのままに、光コラボからWiMAX

無駄にダラダラと文章を書いたけど、要するにコロナのせいでマンションの有線がクッソ遅すぎて仕事にならないから無線に切り替えようと考えたんです。

そしてその際、プロバイダーとして使ってたSo-netWiMAXの取り扱いがあったので、光回線を解約してWiMAXの契約をポチリました。

光回線は月末で廃線になる予定なので、それまでは使い、WiMAXとの並行運用で速度検証を行ったりしました。

WiMAXは3日後くらいに来たので、1週間くらい並行稼働してましたが、光が1Mbps~10Mbpsなのに対し、WiMAXが10Mbps~80Mbpsと約10倍の速度が出ていて、これなら自宅でも多少快適になるだろうと喜んでいました。

解約料が7500円取られるらしいけど、それくらいなら我慢しよう、そう思ってました。

ところが、

突然WiMAXがつながらなくなる

子供が動画を見ていたところ、急にインターネットが使えなくなりました。

時間は19時頃。

「何事だ?」

電源を入れなおしても、リセットしても通信ができません。

機器の故障か?と思いましたが、直接その機器にはアクセスできるようなのでそうではない模様。

「まさか!?」

So-netの契約を見に行きました。

退会処理が完了しました(5月末日付け退会)

いやいやいや、どういうこと???

確認を取ろうと思いましたが、コロナの状況下でプロバイダーのサポート業務も18時までとなっていました。

過去のメールを見ても解約になってるのは光回線のみ。WiMAXの文字はありません。

ただ、光回線のメールに不穏な文字が。

「退会した場合にもこのメールは送られます」

至急確認したいし、明日仕事どうすれば?

いや、日中ならまだしも、そっちの定時後にやられたら確認のしようが無いじゃないか…。

自分一人ならテザリングでなんとかしのげますが、嫁も家にいる日、時間帯があるので至急復旧が必要です。

会社には無理やり1日休みをもらい、朝から確認と、最悪別契約を取りに行く準備を前日に行いました。

翌朝9時からプロバイダーに確認

翌朝、朝9時にPCの前に座り、So-netのサポートに問い合わせました。

サポートの人と話すことで、以下のようなことが分かりました。

  • WiMAXの契約前に光回線を解約している。
  • 自分は光回線の「オプション」としてWiMAXを契約していた。(本人は単にマイページがあるので、「サービスを利用中の場合」のところから契約をした)
  • 光回線が解約された時、退会も同時に行われた。
  • WiMAXはオプション扱いなので同時に解約された。
  • コース変更というものがあったが、それに気づいてなかった
  • 退会されたのでWiMAXの復旧は不可(少なくとも1日以内では)。
  • WiMAXの解約料も当然発生。

光回線を主回線とし、WiMAXはオプションで従という関係なのは100歩譲ってわかるとしても、

主回線の解約後に従のオプション回線が契約できて、主が解約・退会になったから従も併せて解約…

って常識で考えたらおかしいって思わないかな?

せめてコース変更を自動で切り替えてくれればこんなこと起こらなかったのに。

まあ、自分もサポート業務やってるので、サポートの人に言ってもかわいそうなんで、もめずに解約料の話も許容して引き下がりましたよ。

(でも、納得は行ってないから書くけどね!)

1週間程度しか使ってないもの解約料と登録手数料に22000円、そして解約した光の7500円かかって、その他諸々で3万超えてます。

そして、急遽回線引かなければいけなくなったんで、お店が開いてて今日SIMもらえるってなると限られてて、結局WiMAXしか選択肢なかったし。

かつ、SIMだけっていうのが無くてルーターも買わなきゃで、また出費(約2万)…。

合計5万程度余計に払うことに。

復旧作業に4万強使いました。

それでもポジティブに考えてみる

無駄にお金をドブに捨ててるので、何を考えてみても焼け石に水なのですが、それでも無理やりひねり出すと、

  • 公式で契約したことで携帯代がこれから毎月300円安くなる(手続きもやってもらった)
  • ルーターをHOME用と持ち歩き用両方手に入れた
  • 明日は仕事ができる!(急遽休んでごめんなさい)
  • 体調悪かったので、時間も十分にあるので病院に行ける(これから行く)
  • So-netは契約のシステムがザルだったのを忘れてた自分が悪い(そういえばそうだった)
  • 子供が大きくなったら、このマンションは貸して、田舎に引っ越そうという気持ちが固まった
  • 全てコロナのせい

全てコロナが悪いんですよ!

早く正常な世の中に戻ってほしいですね。

MySQL8.0.20で追加・変更されたパラメータを眺める

はじめに

MySQL8.0.20で追加・変更されたパラメータをなんとなく眺めてみました。

パラメータの説明については基本的にはドキュメントからの引用です。

変更されたパラメータ一覧

例によってとみたさんのパラメータ比較ツールをお借りしました。

Parameter 8.0.20 説明
binlog-transaction-compression FALSE バイナリログファイルに書き込まれるトランザクションの圧縮を有効にする
binlog-transaction_compression_level-zstd 3 バイナリログファイルの圧縮レベルを設定
innodb_doublewrite_batch_size 0 バッチで書き込むダブルライトバッファページの数
innodb_doublewrite_dir (No default value) ダブルライトバッファのファイルディレクト
innodb_doublewrite_files 0 ダブルライトファイルの数
innodb_doublewrite_pages 0 バッチ書き込みのスレッドあたりの二重書き込みページの最大数
mysqlx_deflate_default_compression_level 3 サーバが Xプロトコル接続で Deflate アルゴリズムに使用する既定の圧縮レベル
mysqlx_deflate_max_client_compression_level 5 サーバが X プロトコル接続上の Deflate アルゴリズムに対して許可する最大の圧縮レベル
mysqlx_lz4_default_compression_level 2 サーバが X プロトコル接続で LZ4 アルゴリズムに使用する既定の圧縮レベル
mysqlx_lz4_max_client_compression_level 8 サーバが X プロトコル接続上の LZ4 アルゴリズムに対して許可する最大の圧縮レベル
mysqlx_zstd_default_compression_level 3 X プロトコル接続でサーバーが zstd アルゴリズムに使用するデフォルトの圧縮レベル
mysqlx_zstd_max_client_compression_level 11 サーバが X プロトコル接続上の zstd アルゴリズムに対して許可する最大の圧縮レベル
performance_schema_error_size (number of server error codes) インスツルメント化されたサーバーエラーコードの数

バイナリログトランザクション圧縮

MySQL8.0.20からbinlogが圧縮できます。

これについてはけんつさんが検証してくれてますので、非常に参考になります。

パラメータについては以下の通り

binlog_transaction_compression

もしCPUはヒマしてるけど、大量にデータ更新してて、ネットワークがネックなMySQL環境があればONにして、次の圧縮レベルを調整すると良いかもしれませんね。

binlog_transaction_compression_level_zstd

  • このサーバのバイナリログトランザクション圧縮の圧縮レベルを設定します。
  • 値は、圧縮努力を決定する整数で、1 (最も低い) から 22 (最も高い) までの値を指定します。
  • このシステム変数を指定しない場合、圧縮レベルは3に設定されます。
  • 圧縮レベルが高くなると、データ圧縮率が高くなり、トランザクションペイロードに必要なストレージ・スペースとネットワーク帯域幅が削減されます。
  • しかし、データ圧縮に必要な労力も増加し、時間と発信元サーバのCPUおよびメモリリソースが必要となる。圧縮努力の増加は、データ圧縮率の増加と直線的な関係はありません。
  • このシステム変数は、トランザクションのコンテキスト内では変更できません。
  • このシステム変数のセッション値の設定は制限された操作です。
  • セッションユーザは、制限されたセッション変数を設定するのに十分な権限を持っていなければならない。
  • セクション5.1.9.1 「システム変数の特権」を参照してください。

けんつさんの記事から読むに、デフォルトの3でも半分くらいになるみたいなので、22とかどうなっちゃうの?とw

ダブルライトバッファの改良

  • 8.0.20以降、doublewriteバッファストレージ領域をInnoDBシステムテーブルスペースではなく、別途専用ファイルに分けることができるようになりました。
  • これにより書き込みレイテンシが減少するため、スループットを向上させることができ、ダブルライトバッファページの配置に関して柔軟性が提供されます。
  • 8.0.20では、ダブルライトバッファの構成用にパラメータが追加されました。

ドキュメントを読む限りはinnodb_doublewrite_dir以外はあまり変更する必要は無さそうですが、書き込み量やファイルサイズからデフォルト値だと…という場合に触ってみる可能性があるかも、でしょうか。

ダブルライトバッファについて

念のため、ダブルライトバッファについて軽く触れておくと、

  • InnoDBではデータファイル更新前にダブルライトバッファと呼ばれる領域(ファイル)にデータを書き込む。
  • マシンクラッシュでデータページが壊れていても、ダブルライトバッファからリカバリされる。
  • ダブルライトバッファが壊れていた場合は、そのデータは破棄。
  • ダブルライトバッファは連続した領域なので、書き込みのオーバーヘッドはデータファイルより少ない
    • とはいえ、書き込み量、利用領域が増えるので不要な場合は無効にできる

innodb_doublewrite_batch_size

  • バッチで書き込むダブルライトバッファページの数を定義します。
  • 基本的にはデフォルト値のままで問題ないはずですが、パフォーマンスチューニングしたい場合には調整するパラメータ。

innodb_doublewrite_dir

innodb_doublewrite_files

  • ダブルライトファイルの数を定義します。
  • デフォルトでは、バッファープールインスタンスごとに2つの二重書き込みファイルが作成されます。
  • 少なくとも、2つの二重書き込みファイルがあります。
  • doublewriteファイルの最大数は、バッファープールインスタンスの数の2倍です。(バッファー・プール・インスタンスの数はinnodb_buffer_pool_instances 変数によって制御されます。)
  • 二重書き込みファイル名の形式は次のとおりです 。
    • たとえば、次のdoublewriteファイルは、ページサイズが16KBで単一のバッファープールを持つMySQLインスタンス用に作成されたものです。
    • ib_page_size_file_number.dblwrInnoDB

#ib_16384_0.dblwr
#ib_16384_1.dblwr
  • 基本的にはデフォルト値のままで問題ないはずですが、パフォーマンスチューニングしたい場合には調整するパラメータ。

innodb_doublewrite_pages

  • バッチ書き込みのスレッドあたりの二重書き込みページの最大数を定義します。
  • 値が指定されていない場合は、innodb_doublewrite_pagesはinnodb_write_io_threadsの値に設定されます。
  • 基本的にはデフォルト値のままで問題ないはずですが、パフォーマンスチューニングしたい場合には調整するパラメータ。

Xプロトコル接続を介して送信されるメッセージの圧縮

MySQL 8.0.19以降、Xプロトコル接続を介して送信されるメッセージの圧縮がサポートされました。

サーバーとクライアントが使用する圧縮アルゴリズムに同意した場合、接続を圧縮でき、デフォルトでは、Deflate、LZ4、およびzstd圧縮アルゴリズムを許可します。

ところが、MySQL 8.0.19では、サーバー側で圧縮レベルがデフォルト設定化されていて、クライアントはこれを調整することができません。

それがMySQL 8.0.20以降、クライアントはXプロトコル接続のケイパビリティ ネゴシエーション(※)中に特定の圧縮レベルを要求できるようになったとのこと。

一方でアルゴリズムごとに最大圧縮レベルを設定できるようになっていて、サーバーが忙しい時に、クライアントが高圧縮レベルを要求しても、最大圧縮レベル以上の負荷をかけることを防ぎます。

ちなみに、この圧縮レベルの要求はMySQL Shell経由でのみ行われるため、現時点ではMySQLクライアントやその他のConnector系はサポートされていないようです。(20.5.5項 「X プラグインによる接続圧縮」より)

ちなみに、ケイパビリティ ネゴシエーション(capability negotiation)っていうのがよくわからんな、と思って調べたらドキュメントにありました。

ケイパビリティ ネゴシエーション(capability negotiation)

14.2.1.3 Capability Negotiation

要するに、サーバーとクライアントでやり取りするのに色んなもの使うし、使えますよ、と。(例えば、サーバーのバージョンや認証情報(認証方式)とか)

mysqlx_deflate_default_compression_level

  • サーバが X プロトコル接続で Deflate アルゴリズムに使用する既定の圧縮レベル。
  • レベルは、1 (最も低い圧縮率) から 9 (最も高い圧縮率) までの整数で指定。
  • このレベルは、クライアントがケイパビリティ ネゴシエーション中に圧縮レベルを要求しない場合に使用されます。
  • このシステム変数を指定しない場合、サーバーはレベル3を既定値として使用します。
  • 詳細については、20.5.5項 「X プラグインによる接続圧縮」を参照してください。

mysqlx_deflate_max_client_compression_level

  • サーバが X プロトコル接続上の Deflate アルゴリズムに対して許可する最大の圧縮レベル。
  • 範囲は、このアルゴリズムの既定の圧縮レベルと同じ(1から9)です。
  • クライアントがこれより高い圧縮レベルを要求した場合、サーバはここで設定したレベルを使用します。
  • このシステム変数を指定しない場合、サーバは最大圧縮レベルを 5 に設定します。

Deflateとは

Deflate(デフレート)とはLZ77とハフマン符号化を組み合わせた可逆データ圧縮アルゴリズム。フィル・カッツが開発した圧縮ツールPKZIPのバージョン2で使われていた。ZIPやgzipなどで使われている。1996年5月に RFC 1951 としてドキュメント化された。ヘッダーやフッターをつけた zlib (RFC 1950) 形式や gzip (RFC 1952) 形式とともに使われる事が多い。 Deflateアルゴリズムが利用されているソフトウェアの一例を挙げる。

zlib ZIP GZIP 7z Portable Network Graphics (PNG) また、ほとんどのプログラミング言語で利用できる。以下はその一例。

Java - Deflater クラスで nowrap を有効にすることにより素の deflate が扱え、別途 zlib 形式や gzip 形式のヘッターやフッターの付いた物も扱える。 Perl PHP Python Ruby C#、VB.NET等の.NET Framework 2.0以降対応言語 - DeflateStream クラスで素の deflate もしくは GZipStream クラスで gzip 形式。 Apache HTTP ServerなどのWebサーバでも圧縮通信を zlib 形式で Deflate を使って実装している。 (Wikipediaより)

というわけで普段からよくお世話になってる可逆圧縮方式のあいつらはDeflateだったわけでした。

mysqlx_lz4_default_compression_level

  • サーバが X プロトコル接続で LZ4 アルゴリズムに使用する既定の圧縮レベル。
  • レベルは、0 (最も低い圧縮率) から 16 (最も高い圧縮率) までの整数で指定します。
  • このレベルは、クライアントがケイパビリティ ネゴシエーション中に圧縮レベルを要求しない場合に使用されます。
  • このシステム変数を指定しない場合、サーバーはレベル2を既定値として使用します。
  • 詳細については、20.5.5項 「X プラグインによる接続圧縮」を参照してください。

LZ4とは

LZ4 は圧縮と展開の速さに焦点を当てた可逆圧縮アルゴリズムである。バイト指向の圧縮方法であるLZ77ファミリーに属する。 (Wikipediaより)

mysqlx_lz4_max_client_compression_level

  • サーバが X プロトコル接続上の LZ4 アルゴリズムに対して許可する最大の圧縮レベル。
  • 範囲は、このアルゴリズムの既定の圧縮レベルと同じです。
  • クライアントがこれより高い圧縮レベルを要求した場合、サーバはここで設定したレベルを使用します。
  • このシステム変数を指定しない場合、サーバは最大圧縮レベルを 8 に設定します。

mysqlx_zstd_default_compression_level

  • Xプロトコル接続でサーバーが zstd アルゴリズムに使用するデフォルトの圧縮レベル。
  • 1.4.0 以降の zstd ライブラリのバージョンでは、1 から 22 までの正の値 (最高の圧縮率)、または負の値 (圧縮率が徐々に低くなることを表す) を設定できます。
  • 0 の値は 1 の値に変換されます。
  • zstd ライブラリの以前のバージョンでは、3 の値しか指定できません。
  • このレベルは、ケイパビリティ・ネゴシエーション中にクライアントが圧縮レベルを要求しない場合に使用されます。
  • このシステム変数を指定しない場合、サーバーはレベル3をデフォルトとして使用します。
  • 詳細については、20.5.5項 「X プラグインによる接続圧縮」を参照してください。

Zstandard (Zstd)

Zstandard (Zstd) は、2015年からFacebookに所属しているYann Colletによって開発された可逆圧縮アルゴリズムである。またCで書かれた前述のアルゴリズムのリファレンス実装の名前でもある。 Zstandardは現在主流であるDeflate (ZIP, gzip) アルゴリズムによるものと遜色ない圧縮を、より高速に行えるように設計されている。 Zstandardは大きな検索窓の辞書式圧縮アルゴリズム (LZ77) とエントロピー符号化を併用しており、エントロピー符号化ステージで有限状態エントロピー(FSE)のtANS(英語版) あるいはハフマン符号化を使用している。Zstandardの実装で特徴的なのはエントロピー復号化時に逆方向から読み取ることである。 (Wikipediaより)

mysqlx_zstd_max_client_compression_level

  • サーバが X プロトコル接続上の zstd アルゴリズムに対して許可する最大の圧縮レベル。
  • 範囲は、このアルゴリズムの既定の圧縮レベルと同じです。
  • クライアントがこれより高い圧縮レベルを要求した場合、サーバはここで設定したレベルを使用します。
  • このシステム変数を指定しない場合、サーバは最大圧縮レベルを 11 に設定します。

performance_schema_error_size

  • インスツルメント化されたサーバーエラーコードの数。
  • デフォルト値はサーバーエラーコードの実際の数なので、サーバーエラーコードが増えたことで変更となりました。
  • 値は0から最大値まで設定できますが、使用目的は、デフォルト(すべてのエラーを計測する)または0(エラーなしを計測する)に設定することです。
  • エラー情報は要約テーブルに集約されます。セクション26.12.18.11「エラーサマリーテーブル」を参照してください。

まとめ

  • MySQL8.0.20では圧縮形の機能追加が二つあったことがパラメータから見て取れました。
    • binlogの圧縮はZstdのみ。
    • Xプロトコルの圧縮はDeflate、LZ4、Zstdの三つの圧縮レベルのデフォルト値と最大値がパラメータとして追加
      • クライアントが要求した圧縮レベル>サーバーのmax値だったときはサーバのmax値のほうが使われる
      • Xプロトコルの圧縮はMySQL Shell専用なので注意
    • 圧縮形はCPU使用率が上がり、CPU使用率+圧縮時間とネットワーク転送時間とのトレードオフとなります。
  • ダブルライトバッファ系パラメータはinnodb_doublewrite_dir以外はあまり変更する必要は無さそうです。
    • innodb_doublewrite_dirを高速なディスクに設定すると良さそうです。

さいごに

  • 和訳部分についてはドキュメントをDeepLやGoogle翻訳を使って訳したものを一部引用してます。
  • 日本語がおかしい部分は自分で訳しましたが、合ってなかったらごめんなさい。

MySQLでのテーブルコピー(InnoDBとMyISAM)

MySQLでのテーブルコピーを考える

MySQLでテーブルコピー(同一インスタンス内とか別インスタンスとかあるけど)って意外とテストとかちょっとしたリカバリ作業用にしたくなりますよね。

というわけで、InnoDBMyISAM関連だけですが書き出してみました。

コピーパターン

僕がパッと思いつく限りだとこの辺ですが、後から追記する可能性あり〼。

  • Mysql Enterprise Backup(MEB)
  • mysqldump/mysqlpump
  • INSERT...SELECT
  • CREATE TABLE...SELECT
  • OSファイルコピー
  • トランスポータブルテーブルスペース(TTS)
  • MEB + TTS
  • IMPORT TABLE
  • その他

MEB(InnoDB,MyISAM

MySQL Enterprise Backup と呼ばれる商用の製品です。

以下、ドキュメントからの転記です

  • 実行中の MySQL データベース (InnoDB および MyISAM テーブルを含む) を、データベースの整合性のあるスナップショットを生成しながら、操作の中断を最小限に抑えてバックアップできます。
  • MySQL Enterprise Backup が InnoDB テーブルをコピーしている間は、InnoDB テーブルと MyISAM テーブルの両方に対する読み取りと書き込みを続行できます。
  • MyISAM およびその他の InnoDB 以外のテーブルのコピー中は、これらのテーブルに対する (書き込みではなく) 読み取りが許可されます。
  • さらに、MySQL Enterprise Backup では、圧縮バックアップファイルを作成したり、InnoDB テーブルのサブセットをバックアップしたりすることもできます。
  • MySQL のバイナリログと組み合わせると、ポイントインタイムリカバリを実行できます。

ユーザーガイドはこちらですが、EE版を契約した人には使わない手は無い代物だと思います。

mysqldump/mysqlpump(InnoDB,MyISAM

皆さんご存じmysqldumpとあまりご存じじゃないmysqlpumpですw

mysqlpumpだけの機能だとか、mysqldumpとの違いなんかは既に僕ら大好きyoku0825さんがまとめてくださってるので多くは語らず。

あえて言うなら、mysqlpumpはMySQL5.7以降で使いましょう。

そして、mysqldumpと比べると新しい機能なのでまだまだ枯れてない部分もありそうです。

こういう話もありますので、最新を使いましょうね!

INSERT...SELECT(InnoDB,MyISAM

create tableして、INSERT...SELECTする、こちらもおなじみのやり方ですね。

create tableして、csv,tsv形式のファイルを作ってload dataコマンドやmysqlimportコマンドという方法なら並列化できていいかもしれません。

CREATE TABLE...SELECT(InnoDB,MyISAM

CREATE TABLE...SELECTもちょっとしたコピーテーブル作るのには便利ですよね。

でも、インデックスや制約を自動で作らないので、以下のような書き方でインデックスや制約は追加してあげましょう。

mysql> CREATE TABLE t2 (PRIMARY KEY (a),INDEX (b),foreign key f1 (a) references t1(a)) SELECT * FROM t1;

AUTO_INCREMENTあたりも保持しないので気を付けましょう。

OSファイルコピー(InnoDB,MyISAM

これは2パターンある。MySQLを停止するパターンと停止しないで行うパターンです。

停止パターン

ドキュメントに例があります。

Not停止パターン

停止パターンとほぼ同じですがshutdownをしないので、メモリやらバイナリログにデータが残ってる可能性があるのでフラッシュやsync作業が必要かも。

https://dev.mysql.com/doc/refman/8.0/en/flush.html

トランスポータブルテーブルスペース(InnoDB

InnoDBだとTTSが便利かつ高速。

ドキュメントの手順だと別のサーバーへのコピーとなっているが、同じインスタンス内の別スキーマでもOK。

注意点としては外部キーは持てないので、必要なら後から適用する必要があるところ。

MEB + TTS(InnoDB

MEBのオプションとして先ほど挙げたTTSがバックアップとして使えるというものです。

詳しく知りたい方はドキュメントを参照してください。

IMPORT TABLE(MyISAM

ここまではMySQL5.6以降であれば使えました。(え?5.5?もう僕は知らない子ですねぇ…)

そして、MySQL8.0では"IMPORT TABLE"というのができました。

まあ、MySQL8.0ではMyISAM自体非推奨であり、Perconaのブログでは

”MySQL 8.0: The end of MyISAM”

なんてタイトルの話があったりして、今更じゃね?という気もしないではないんですが…。

IMPORT TABLEはドキュメントを読むと以下の通りです。

  • MyISAMのテーブルをシリアル化された辞書情報(.sdi)メタデータファイルに含まれる情報に基づいてインポートします。
  • .sdiを読むためのIMPORT TABLEコマンドにはFILE権限とテーブルを作成するためのCREATE権限が必要です。
  • mysqldumpでエクスポートして、mysqlでインポートする方法よりも高速です。

参照:13.2.5 IMPORT TABLE Statement

作業手順としてはOSファイルコピーと似たような感じですが、んーーーー、使うメリットが今のところわかってません(苦笑)

その他

サードパーティ製のツールを使う方法です。

GUICUI色々ありますが、有名なのはPercona社のPercona XtraBackupですね。

xtrabackupのオプションの--tablesオプションを使えば対象テーブルのみをコピーしてくれるようです。

Percona XtraBackupの使い方については北川さんの記事がとても分かりやすいですが、他にも色々記事があるので使う場合はググって読んでみるといいです。

まとめ

MySQLでテーブルコピーに使えそうなものを雑に挙げてみました。

速度で行くとOSファイルコピーは最強でしょうね。ただ、権限的なものが強くなりますし、基本的には同一データベース名にコピーするものなので、開発者が気軽にできないところが難点でしょうか。

開発者が使うなら時間はかかりますが、mysqldumpやSQLでできるようなものがいいでしょう。

あとはTTSはそこまで有名じゃないですが、非常に便利だと思います。気になった方は試してみてはいかがでしょうか。

あ、そういえばロックの話全然書いてなかった…(;^_^A