今回はMySQLを利用するうえで、知っていると便利になるかもしれないちょっとした小技をいくつか紹介しようと思います。なお、利用するMySQLのバージョンは8.0.18、OSはCentOS 7を利用しています。
STRAIGHT_JOINの位置
第97回 JOIN_ORDERを使ってJOINの順番を決める にて、バージョン8.0以降ではJOIN_ORDERヒント句を用いてJOINの順番を決めるやり方と、バージョン5.7とそれ以前ではINNER JOINに限り、STRAIGHT_JOINを用いて駆動表を選択することができることを紹介しました。
みなさんはこのSTRAIGHT_JOINを記述するやり方が複数あるのはご存知でしょうか? 1つ目の記述は、INNER JOINの記述をSTRAIGHT_JOINに書き直すやり方です。たとえば、第97回で利用した下記クエリを参考にしてみましょう。
mysql> EXPLAIN SELECT Country.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.CountryCode=Country.Code;
+----+-------------+---------+------------+------+---------------+-------------+---------+--------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+-------------+---------+--------------------+------+----------+-------+
| 1 | SIMPLE | Country | NULL | ALL | PRIMARY | NULL | NULL | NULL | 239 | 100.00 | NULL |
| 1 | SIMPLE | City | NULL | ref | CountryCode | CountryCode | 3 | world.Country.Code | 18 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+-------------+---------+--------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
このクエリのINNER JOIN部分をSTRAIGHT_JOINに書き直して、下記のようになります。
mysql> EXPLAIN SELECT Country.Name as CountryName, City.Name AS City FROM City STRAIGHT_JOIN Country ON City.CountryCode=Country.Code;
2つ目はSELECTのすぐ後にSTRAIGHT_JOINを記述するやり方です。
mysql> EXPLAIN SELECT STRAIGHT_JOIN Country.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.CountryCode=Country.Code;
どちらのクエリも駆動表が固定されて実行計画が出力されています。ただし、どちらもSTRAIGHT_JOINの記述が間違うとsyntax errorとなりますので好みで好きな方を用いることになるかと思います。
mysql> SELECT STRAIGHT_JON Country.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.CountryCode=Country.Code;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.Cou' at line 1
mysql> SELECT Country.Name as CountryName, City.Name AS City FROM City STRAIGHT_JO Country ON City.CountryCode=Country.Code;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Country ON City.CountryCode=Country.Code' at line 1
3つ目のやり方はコメント構文に入れるやり方です。
EXPLAIN SELECT /*! STRAIGHT_JOIN */ Country.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.CountryCode=Country.Code;
なお、コメント構文に入れるやり方であっても、STRAIGHT_JOINの綴りが間違っているとsyntax errorになります。
mysql> EXPLAIN SELECT /*! TRAIGHT_JOIN */ Country.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.CountryCode=Country.Code;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.Name as CountryName, City.Name AS City FROM City INNER JOIN Country ON City.Cou' at line 1
MySQLのバージョンによってSQLを切り分ける
第54回 SQLコメントを使ってみよう にて、MySQLではコメントを利用して特定のバージョンより前のバージョンでは実行しないという制御が可能であることが紹介されています。
これを利用して、スクリプトを記述するときに特定のバージョン以上のみ実行するような処理が可能です。ただし、こちらをBashのスクリプト等で利用する際は注意が必要です。たとえば、MySQL 8.0以上の場合はmysql.role_edgesを出力するという想定で下記を実行してみます。
$ mysql -sse "/*!80000 SELECT distinct from_user, from_host FROM mysql.role_edges */"
-bash: !80000: event not found
Bash側のエラーでevent not foundと表示されてしまいました。これはBashのヒストリ展開が!80000に反応して、履歴の中の80000番目を実行しようとしているからです。
もしBashのスクリプトの中などで利用する場合は、スクリプトの中ではヒストリ展開をOFFにして実行してあげるとうまくいくかもしれません。
$ set +H
$ mysql -sse "/*!80000 SELECT distinct from_user, from_host FROM mysql.role_edges */
MySQLのbyte単位のファンクション
MySQL 8.0.16から、sysスキーマにあったformat_bytes()という関数が組み込み関数に変更になりました。この関数は与えられたbyteを適切な単位に変換して表示してくれます。
たとえば、innodb_buffer_pool_sizeなどにこの関数を通すことで、適切な単位接尾辞を付与して表示されます。
mysql> SELECT @@innodb_buffer_pool_size;
+---------------------------+
| @@innodb_buffer_pool_size |
+---------------------------+
| 536870912 |
+---------------------------+
1 row in set (0.00 sec)
mysql> SELECT format_bytes(@@innodb_buffer_pool_size);
+-----------------------------------------+
| format_bytes(@@innodb_buffer_pool_size) |
+-----------------------------------------+
| 512.00 MiB |
+-----------------------------------------+
1 row in set (0.00 sec)
SET GLOBALなどで設定するときは乗算が利用できるので、確認時などに利用すると良いかもしれません。
mysql> SET GLOBAL innodb_buffer_pool_size = 640 * 1024 * 1024;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT format_bytes(@@innodb_buffer_pool_size);
+-----------------------------------------+
| format_bytes(@@innodb_buffer_pool_size) |
+-----------------------------------------+
| 640.00 MiB |
+-----------------------------------------+
1 row in set (0.00 sec)
gtid_purgedに(+)を利用して設定する
gtid_modeを利用してレプリケーションを設定している場合、もしレプリケーションでエラーになった場合などで結果によってはskipさせたい場合、空のトランザクションをコミットして対応する必要がありました(詳しいやり方は第25回 GTIDを使用したレプリケーション構成を作成する[2] に特定のトランザクションをスキップするを参考にしてください) 。
これだと多くのトランザクションをスキップする場合に手間がかかるため、多くの方はMySQL Utilitiesのmysqlslavetrxを利用しているかも知れません。
MySQL 8.0では、gtid_purgedを設定するときに+
を用いることで、連続したトランザクションを追記してgtid_purgedに設定できるようになりました。たとえば下記のような状況において、1000000-1000010
のgtidを設定したい場合、下記のように+をつけることで既存のGTIDにプラスしてGTIDに設定することができます。
mysql> show variables like '%gtid_purged';
+----------------------------------+-----------------------------------------------+
| Variable_name | Value |
+----------------------------------+-----------------------------------------------+
| gtid_purged | 7d2772a5-8987-11e8-a6a6-0201858272ab:1-344976 |
+----------------------------------+-----------------------------------------------+
1 rows in set (0.03 sec)
mysql> SET GLOBAL gtid_purged = '+7d2772a5-8987-11e8-a6a6-0201858272ab:1000000-1000010';
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like '%gtid_purged';
+----------------------------------+---------------------------------------------------------------+
| Variable_name | Value |
+----------------------------------+---------------------------------------------------------------+
| gtid_purged | 7d2772a5-8987-11e8-a6a6-0201858272ab:1-344976:1000000-1000010 |
+----------------------------------+---------------------------------------------------------------+
1 rows in set (0.00 sec)
ただし、設定できる値は既存のgtid_executedと重複しない値のみになります。もし、gtid_executedとかぶるようであれば、下記のようなエラーが出力されます。
mysql> SET GLOBAL gtid_purged = '+7d2772a5-8987-11e8-a6a6-0201858272ab:100000-100010';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED
とはいえ、gtid_purgedを設定するということはトランザクションを飛ばすということなので、設定には十分注意が必要です。
より詳細な使い方については公式ドキュメントの17.1.6.5 Global Transaction ID System Variablesのgtid_purged の項目をご確認ください。
COMMIT、ROLLBACKのCHAINとRELEASE
COMMIT, ROLLBACKを実行する際に、AND CHAIN
, RELEASE
を付与することができるのはご存知でしょうか? AND CHAINの場合は現在のトランザクションが終了したときに、同じトランザクション分離レベルで新しいトランザクションを開始します。
RELEASEの場合はトランザクションが終了したときにクライアントセッションを切断します。この挙動はcompletion_typeというオプションによって通常のCOMMIT, ROLLBACKにも組み込むことができますが、デフォルト値で運用している場合は、CHAIN、RELEASEを使うことによってトランザクション分離レベルの検証や、threadを新規につなぎ直したいときに便利かもしれません。
より詳細な挙動については、公式ドキュメントの13.3.1 START TRANSACTION、COMMIT、および ROLLBACK 構文 のCHAINおよびRELEASE句の説明をご覧ください。
テーブル作成時のデータ型マッピング
MySQLにテーブルを作成するときに、型をint8
とするとbigint型が作成されます。 他にも、INT1でTINYINTなど、いくつかのマッピングが存在します。これは、他のデータベースでSQL実装用に作成されたコードを使用しやすくするための機能で、テーブル作成時のみ有効です。
mysql> create table t1 (id int8);
Query OK, 0 rows affected (0.03 sec)
mysql> show create table t1 \G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.01 sec)
その他のマッピングについては、公式ドキュメントの11.9 その他のデータベースエンジンのデータ型の使用 をご確認ください。
まとめ
今回はMySQLを利用するときに役に立つかもしれない小技を少しだけご紹介しました。
みなさんはすべて把握済みだったでしょうか?どれも、知らなくても運用に支障をきたすことはおそらくないですが、知っていると検証や運用を助けてくれるときがあるかもしれません。もし知らないものがあった場合は、小さなクリスマスプレゼントとして受け取っていただけると幸いです。