ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2018-05-07

MySQL 8.0登場!立ち止まることを知らない進化はこれからも続く。

ゴールデンウィークはいかがお過ごしされただろうか。今年は天気も良く、行楽日和が続いたように思う。

さて、先日MySQL 8.0が正式にリリースされた。少し時間が経ってしまったが、今回はMySQL 8.0の新機能について紹介したい。コミュニティ版のダウンロードはこちらから可能だ。

ひとつ前の正式バージョンはMySQL 5.7だったのだが、MySQL 8.0は非常に大きなリファクタリングが含まれており、5.x台のバージョン番号を捨て去ろうという話があった。そこで、次のメジャーバージョンは最初の桁を増やすということになったのだが、MySQL 6.0は過去に既に存在し、買収などの騒ぎで開発が頓挫してしまった経緯がある。7.xはMySQL NDB Clusterと被っている。というわけで、5.7の7の部分の次という意味合いもあって、8.0というバージョン番号を引っさげ、満を持しての登場となった。そのバージョン番号が象徴するように、今回のアップデートは非常に大きいものとなっている。

以前の投稿では、開発段階の「マイルストーンリリース」を元に新機能を紹介したが、今回は改めて正式版をベースに新機能の概要について紹介しようと思う。

データディクショナリ

MySQLと言えば、テーブルごとに.FRMファイルが作成されるのが風物詩であったが、これは完全に過去のものとなった。

さらば.FRMファイル!!君のことは忘れない!!

.FRMファイルはテーブル定義を構造化した形式で記録するファイルである。テーブル定義は.FRMファイルから取得されるようになっていた。(過去形!)MySQL 8.0では、.FRMファイルの代わりに、システムテーブル内にデータが格納されるようになっている。データディクショナリ用のテーブルは、通常はユーザーから直接アクセスすることはできない。アクセスするにはデバッグ版のバイナリを用いる必要がある。

テーブル定義を含んだ.FRMファイル以外にも、トリガーの定義を含んだ.TRN/.TRGファイル、パーティションの定義を含んだ.parファイル、InnoDBのシンボリックリンク情報を含んだ.islファイル、データベースの属性を含んだdb.optファイルなどが用済みとなる。

これらのメタデータ用のファイルが不要になったことで、以下の様なメリットがある。
  • メタデータがデータベースと完全に同期できるようになった。(ファイルとデータベースは同期できないので、.FRMではクラッシュ時にデータベースと時差が生じる恐れがある。)
  • 情報スキーマのアクセスが高速化する。(いちいち.FRMファイルをopenしなくて済むようになった。)
データディクショナリ用のストレージエンジンはもちろんInnoDBである。そのため、クラッシュ耐性が大幅に強化されており、DDLを実行しているときにクラッシュが生じても、中途半端な状態にならず、そのDDLはCOMMITされるかROLLBACKされるかのいずれかの状態になること(原子性)が保証されるようになった。

データディクショナリがInnoDBになったとは言え、DDLがトランザクショナルになったワケではない。明示的にトランザクションの一部としてDDLを含めることはできないし、そもそもDDLはロールバックすることができないからだ。DDLがトランザクショナルになるかどうかについては、今後の進化に期待して欲しい。

新しいデータディクショナリの詳細については、マニュアルを参照して欲しい。

システムテーブルのInnoDB化

古来、MySQLのシステムテーブル(mysqlデータベース内のテーブル)には、MyISAMストレージエンジンが用いられていた。徐々にInnoDB化が進められており、MySQL 5.7ではかなりの部分がInnoDB化したが、それでもMyISAMのテーブルが一部(権限テーブル回りで)残っていた。だが、それはようやく過去の話となった。

さらば、MyISAM!!君のことは忘れない!!

InnoDBになったことで、クラッシュ耐性は格段に向上することになる。権限テーブルがMyISAMだと、GRANTコマンドを実行中にクラッシュしてしまった場合などに、最悪の場合テーブルが破損する恐れがあった。もちろんInnoDBはクラッシュリカバリが可能なので、そのような危険性は無い。少しでも安全性が高まることは良いことである。

ロール(ROLE)

他のRDBMSではお馴染みの機能なので敢えて説明するまでもないかも知れないが、ロール(ROLE/役割)とは権限のセットに名前をつけたものである。複数のユーザーに同じ権限の組み合わせを付与したい場合、その権限の組み合わせでロールを作成しておけば、同じロールを付与するだけで実現が可能となる。複数のGRANTコマンドを毎回叩く必要はない。ロールの基本的な使い方については、以前の投稿を見て欲しい。

ロールを適用するにはSET ROLEコマンドでロールを有効化する必要があり、少し面倒である。これは、デフォルトのロールを設定するか、あるいはactivate_all_roles_on_loginというオプションをONにしておくことで対処できる。また、全てのユーザーに対してデフォルトで付与するロールを設定する、mandatory_rolesというオプションもあるので活用して欲しい。

また、ロール名はユーザー名と同様に「role_name_part@host_name_part」という風に、@マークを使って表現する。@マーク以降は省略可能で、その場合は「全てのホスト」と同じ意味になる。だが、ロールにおいてはホスト部は単なる文字列リテラルであり、ホストとマッチングさせるという意味合いはない。ややこしくなるだけなので、ロール名では@マーク以下は省略した方が良いだろう。

文字コードの改良

MySQL 8.0では、デフォルトの文字コードがutf8mb4になっている。これは絵文字なども含め、Unicodeで定義された文字をすべて扱うことのできる仕様となっている。(UTF-8の符号化自体は6バイトまで扱えるようになっているが、5バイト目以降は文字が割り当てられていない。)

また、照合順序についても見直しがされており、Unicode照合アルゴリズム(UCA)に沿った照合順序が利用可能だ。日本語用の照合順序としては、utf8mb4_ja_0900_as_csとutf8mb4_ja_0900_as_cs_ksがある。照合順序の名称に含まれるjaは日本語、0900はUCA 9.0.0に基づいたものであること、asはアクセントセンシティブ、csはケースセンシティブ、ksはカナセンシティブの略である。カナセンシティブでない照合順序では、平仮名と片仮名の区別がなく、読みでソートする場合などは便利である。

なお、デフォルトの照合順序はutf8mb4_0900_ai_ciとなっており、これはカナを区別しないものであるため日本語を扱う際には注意が必要である。utf8mb4_0900_ai_ciは絵文字(寿司とビールのアレ)を区別するが、平仮名と片仮名は区別しない。従来からあるutf8mb4_general_ciは、平仮名と片仮名は区別できるが、絵文字は区別しない。両方共に何とも悩ましい照合順序である。日本語を扱いたい場合には、utf8mb4_ja_0900_as_cs_ksを利用すると良いだろう。ただしこちらはケースセンシティブなので、英語の大文字小文字を区別する点には注意して欲しい。

CTE

以前の投稿の段階ではまだラボ版という位置づけであったが、Common Table Expression(CTE)は正式にMySQL 8.0の機能として取り込まれている。CTEはWITH句を用い、サブクエリをSELECTの前に書くものである。以下はCTEの例だ。

WITH cl AS (
  SELECT CountryCode, Language FROM
  CountryLanguage WHERE IsOfficial = 'T' AND Percentage > 0.5
)
SELECT * FROM Country c JOIN cl ON c.Code = cl.CountryCode
WHERE c.Name LIKE 'J%';

RECURSIVE句とUNION ALLを使用することで、前の行に基づいた新たな行を次々と生成することができる。RECURSIVEを用いない場合はFROM句にサブクエリを書くのと実質的には変わらず、単なるシンタックスシュガーであると言える。クエリの見通しを良くしたい人はRECURSIVE無しでもCTEを用いると良いだろう。だが、やはりCTEの本領はRECURSIVEを用いる点だと言える。マニュアルの例では、フィボナッチ数を生成したり、階層型データの探索を行ったりというものが紹介されている。以下は階層型データ探索の例の抜粋である。

CREATE TABLE employees (
  id         INT PRIMARY KEY NOT NULL,
  name       VARCHAR(100) NOT NULL,
  manager_id INT NULL,
  INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES EMPLOYEES (id)
);
INSERT INTO employees VALUES
(333, "Yasmina", NULL),  # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333),      # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);

mysql> SELECT * FROM employees ORDER BY id;
+------+---------+------------+
| id   | name    | manager_id |
+------+---------+------------+
|   29 | Pedro   |        198 |
|   72 | Pierre  |         29 |
|  123 | Adil    |        692 |
|  198 | John    |        333 |
|  333 | Yasmina |       NULL |
|  692 | Tarek   |        333 |
| 4610 | Sarah   |         29 |
+------+---------+------------+
マニュアルに記載されている例では階層型データの探索だが、CTEは階層型だけでなく、様々なグラフの探索にも利用可能である。グラフの探索はリレーショナルモデルでは解決できない課題なので、CTEを使ってうまくクエリを書くと良いだろう。

なお、再帰的なCTEではデータやクエリ次第では永遠にループが終わらない可能性がある。そのため、CTEを使う場合には、cte_max_recursion_depthオプションで再帰の深さを指定したり、max_execution_timeオプションでクエリの実行時間に制限をつけるなどをして、対策をしておこう。なお、cte_max_recursion_depthのデフォルト値は1000となっている。

ウィンドウ関数

ウィンドウ関数は、長年MySQLにとって待望の機能であった。ウィンドウ関数はよく集約関数と比較されるが、集約関数と違って結果がグループごとにまとめられることはなく、各行ごとに表示される。また、まとめる結果の範囲を指定できるなど、集約関数よりも柔軟な使い方が可能となっている。とはいえ、集約関数を完全に置き換えるものではない。巨大なデータを分析する場合などは、膨大な結果セットを生成するよりも、結果を集約したほうが便利なケースも多いだろう。互いに補完するものとして使用して欲しい。

以下は、おなじみworldデータベースを使ったクエリの例である。大陸ごとに合計の人口を算出し、なおかつ各国が大陸の中でどれだけの人口比率があるか、人口の多さのランキングを表示するものである。応用次第でいくらでも用途は広がるので、色々と活用して欲しい。
SELECT
    Name,
    Continent,
    SUM(Population) OVER win AS continent_population,
    Population,
    Population / (SUM(Population) OVER win) * 100 AS pct_population_in_continent,
    RANK() OVER win AS population_rank
  FROM
    Country
  WINDOW win AS (PARTITION BY Continent ORDER BY Population DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
とりあえず、細かい部分についてはリファレンスマニュアルを参照して欲しい。

ウィンドウ関数を追加するには、MySQLの持つ構文にも大きく手を加える必要があり、なかなか手が入らなかったという事情がある。一つ前のバージョンであるMySQL 5.7ではパーサーのリファクタリングが行われた。その結果として、構文に手を入れやすくなり、CTEやウィンドウ関数の導入も可能となったのである。

テーブル統計情報のヒストグラム

MySQL 8.0では、オプティマイザがテーブル統計情報のヒストグラムを活用できるようになった。WHERE句の条件にマッチする行が、テーブルスキャンによってどの程度に絞り込まれるかということを見積もることによって、敢えてスキャンを選ぶべきかどうかが分かる。リアルタイム性が高い処理ではインデックスをバッチリ使用するように最適化することが多いのでヒストグラムの出番は無いだろうが、分析系の処理では重宝することもあるだろう。

なお、ヒストグラムは自動では更新されない点に注意して欲しい。初期状態ではヒストグラムは存在せず、以下のようにANALYZE TABLEを実行することで初めて生成される。ヒストグラムの更新も同じコマンドで行う。バッチ処理で大量のデータを更新した後や、定期的にANALYZE TABLEを実行することで、ヒストグラムの鮮度を保つようにしておこう。
ANALYZE TABLE City UPDATE HISTOGRAM ON Population WITH 100 BUCKETS;
バケットのサイズは1〜1024の間で指定する。WITH〜の部分は省略することも可能であり、その場合はデフォルトの100が適用される。

以下はヒストグラムが適用された場合の実行計画の例である。filteredの項目に、統計情報に基づいた行数の見積もりが表示されている。

mysql> EXPLAIN SELECT * FROM City JOIN CountryLanguage ON City.CountryCode = CountryLanguage.CountryCode WHERE City.Population < 10000\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: City
   partitions: NULL
         type: ALL
possible_keys: CountryCode
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4188
     filtered: 1.02
        Extra: Using where
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: CountryLanguage
   partitions: NULL
         type: ref
possible_keys: PRIMARY,CountryCode
          key: PRIMARY
      key_len: 3
          ref: world.City.CountryCode
         rows: 4
     filtered: 100.00
        Extra: NULL
2 rows in set, 1 warning (0.00 sec)

なお、ヒストグラムの情報はデータディクショナリ内に格納されており、ユーザーが直接アクセスすることはできない。内容を表示したい場合には、information_schema.COLUMN_STATISTICSに対して問い合わせを行おう。

ヒストグラムが不要になったらANALYZE TABLE ... DROP HISTOGRAM ON ...で削除しよう。

JSONの改良

JSONに関しても様々な改良が加えられており、JSON型データの扱いが容易になっている。

1つめの改良点は、->>という演算子が追加されたというものである。MySQL 5.7では->という演算子が追加されたが、これはJSONデータから指定されたパスの要素を取り出すJSON_EXTRACT()関数のショートカットとなっている。今回追加された->>は、取り出されたJSONの要素に対して、さらにJSON_UNQUOTE()関数を適用するというものである。->で取り出された要素が文字列だった場合、ダブルクォートで囲われた状態になっているため、文字列だけが必要な場合はいちいちJSON_UNQUOTE()を呼ぶ必要があった。今後は->>でいっぱつである。

JSONデータ型用の関数もいくつか追加されている。JSON_ARRAYAGG()およびJSON_OBJECTAGG()は、それぞれカラムをJSON配列およびJSONオブジェクトとして集約する関数である。JSONでないカラムのデータをJSONへ変換したい場合に便利だろう。JSON_PRETTY()関数は、JSONを人が見やすい形で整形するためのものである。以下はworldデータベースを用いたサンプルである。
mysql> SELECT JSON_PRETTY(JSON_OBJECTAGG(Name, Population)) FROM City WHERE CountryCode='JPN'\G
*************************** 1. row ***************************
JSON_PRETTY(JSON_OBJECTAGG(Name, Population)): {
  "Ise": 101732,
  "Ome": 139216,
  "Ota": 145317,
...中略...
  "Higashihiroshima": 119166,
  "Higashimatsuyama": 93342,
  "Jokohama [Yokohama]": 3339594
}
1 row in set (0.00 sec)

JSON_SET()、JSON_REPLACE()、JSON_REMOVE()は、JSONデータの要素に対して部分的な変更を加えるためのものであり、これらの関数を使うとバイナリログに記録されるデータがコンパクトになるので効率的である。部分的な更新を有効化するには、binlog_row_value_options=JSONを設定する必要がある。レプリケーション環境でJSONデータを利用している場合には、積極的にこれらの関数を使うようにしよう。詳しくはリファレンスマニュアルを参照して欲しい。

また、RFC 7396に対応したJSON_MERGE_PATCH()関数が追加されたり、JSON型データをテーブルのように扱うためのJSON_TABLE()関数が追加されている点も見逃せない。JSON_TABLE()関数は、配列の1要素を行として扱うようにできている。配列が入れ子になっている場合はネストも可能である。ネストした場合は、親の要素が複数行に渡って登場することになる。以下はマニュアルからの抜粋であるが、配列内のbというオブジェクトがネストした配列になっているので、NESTED PATH句を使ってさらに要素を抽出している。
mysql> SELECT *
    -> FROM
    ->   JSON_TABLE(
    ->     '[ {"a": 1, "b": [11,111]}, {"a": 2, "b": [22,222]}, {"a":3}]',
    ->     '$[*]' COLUMNS(
    ->             a INT PATH '$.a',
    ->             NESTED PATH '$.b[*]' COLUMNS (b INT PATH '$')
    ->            )
    ->    ) AS jt
    -> WHERE b IS NOT NULL;

+------+------+
| a    | b    |
+------+------+
|    1 |   11 |
|    1 |  111 |
|    2 |   22 |
|    2 |  222 |
+------+------+
これらの関数はJSON型データを加工する際に大いに役立つことだろう。

正規表現の改良

これまでのバージョンのMySQLでは、マルチバイト文字において正規表現をうまく扱うことができなかった。MySQL 8.0では正規表現系の関数が再実装され、そのような問題は解消された。正規表現ライブラリがICU(International Components for Unicode)のものへと変更されており、より広く知られたものであるという点で、より安心して使えるようになったと言える。

SET PERSIST

MySQL 8.0では、SETコマンドで変更したオプションをそのまま永続するための仕組みが追加されている。SET PERSIST構文である。SETにはこれまでSET LOCALか、SET GLOBALというスコープの異なる2つのバリエーションがあった。SET PERSISTは、SET GLOBALのようにサーバー全体をスコープとしたものであり、設定をmysqld-auto.cnfにも記録することで、設定が永続化されるようになっている。例えば、SET PERSIST max_connections=1000などとすると、現在稼働中のMySQLサーバーの最大接続数が1000となり、なおかつmysqld-auto.cnfにも設定が記録される。mysqld-auto.cnfはdatadirに配置される。当然ながら、オンラインで変更できない設定(例えばdatadir)などはSET PERSISTで変更することはできない。また、現在稼働中のサーバーに設定を反映させず、mysqld-auto.cnfだけに設定を書き込むSET PERSIST_ONLYという変種もあるので、ケースバイケースで使いこなして欲しい。

それぞれのオプションが、どのファイルの設定値が採用されたものなのかを知るには、performance_schema.variables_infoテーブルを参照する。このテーブルの定義は以下のようになっており、設定値の範囲や、何時誰が設定したかなどが分かるようになっており、大変便利である。
mysql> DESC variables_info;
+-----------------+-----------------------------------------------------------------------------------------------------------+------+-----+----------+-------+
| Field           | Type                                                                                                      | Null | Key | Default  | Extra |
+-----------------+-----------------------------------------------------------------------------------------------------------+------+-----+----------+-------+
| VARIABLE_NAME   | varchar(64)                                                                                               | NO   |     | NULL     |       |
| VARIABLE_SOURCE | enum('COMPILED','GLOBAL','SERVER','EXPLICIT','EXTRA','USER','LOGIN','COMMAND_LINE','PERSISTED','DYNAMIC') | YES  |     | COMPILED |       |
| VARIABLE_PATH   | varchar(1024)                                                                                             | YES  |     | NULL     |       |
| MIN_VALUE       | varchar(64)                                                                                               | YES  |     | NULL     |       |
| MAX_VALUE       | varchar(64)                                                                                               | YES  |     | NULL     |       |
| SET_TIME        | timestamp(6)                                                                                              | YES  |     | NULL     |       |
| SET_USER        | char(32)                                                                                                  | YES  |     | NULL     |       |
| SET_HOST        | char(60)                                                                                                  | YES  |     | NULL     |       |
+-----------------+-----------------------------------------------------------------------------------------------------------+------+-----+----------+-------+
8 rows in set (0.00 sec)

なお、設定を解除するにはRESET PERSISTを使う。RESET PERSIST max_connectionsを実行すると、mysqld-auto.cnfからmax_connectionsの設定は消えることになる。RESET PERSISTにおいてオプション名を忘れてしまうと、すべての設定がmysqld-auto.cnfから消え去ってしまうことになるので注意して欲しい。

リソース管理

MySQL 8.0では、スレッドの優先度を設定したり、CPUへのバインディングを行うことができるようになっている。そのようなリソース管理を行うには、リソースグループを作成する必要がある。リソースグループの作成は、CREATE RESOURCE GROUPコマンドで行う。
mysql> CREATE RESOURCE GROUP rg1 TYPE=USER VCPU=2-3;
VCPUは、OSが認識しているCPUのIDである。使用中のシステムにいくつのCPUがあるかは、OSのコマンド等(Linuxならばlscpu等)を使って調べるか、あるいは情報スキーマのRESOURCE_GROUPテーブルから、デフォルトで定義されているリソースグループを使用すると良いだろう。以下はrg1リソースグループを作成した時点での、RESOURCE_GROUPテーブルの内容である。このシステムには0-31のVCPUが存在することが分かる。
mysql> SELECT * FROM information_schema.RESOURCE_GROUPS\G                
*************************** 1. row ***************************
   RESOURCE_GROUP_NAME: USR_default
   RESOURCE_GROUP_TYPE: USER
RESOURCE_GROUP_ENABLED: 1
              VCPU_IDS: 0-31
       THREAD_PRIORITY: 0
*************************** 2. row ***************************
   RESOURCE_GROUP_NAME: SYS_default
   RESOURCE_GROUP_TYPE: SYSTEM
RESOURCE_GROUP_ENABLED: 1
              VCPU_IDS: 0-31
       THREAD_PRIORITY: 0
*************************** 3. row ***************************
   RESOURCE_GROUP_NAME: rg1
   RESOURCE_GROUP_TYPE: USER
RESOURCE_GROUP_ENABLED: 1
              VCPU_IDS: 0-8
       THREAD_PRIORITY: 0
3 rows in set (0.01 sec)
リソースグループを作成したら、それをセッションに割り当てよう。セッションにリソースグループを適用するには、SET RESOURCE GROUPコマンドを使う。
mysql> SET RESOURCE GROUP rg1;
このコマンドを実行すると、以降の処理はリソースグループrg1で実行されることになる。また、現在のセッション以外のスレッドに対してリソースグループを設定するには、SET RESOURCE GROUP rg_name FOR thread_idというコマンドを使う。スレッドIDはperformance_schema.threadsで調べることが可能である。SET RESOURCE GROUPコマンド以外にも、オプティマイザヒントをつかってリソースグループを指定することもできる。以下はその例である。
mysql> SELECT /*+ RESOURCE_GROUP(rg1) */ * FROM world.City;
TYPEはリソースグループの種別である。設定可能な値はUSERあるいはSYSTEMであり、スレッドの優先度の設定と、適用可能なスレッドの種類に違いがある。SYSTEMタイプのリソースグループは、バックグラウンドスレッドにも適用が可能である。(フォアグラウンドのスレッドにはUSERタイプのみが適用可能である。)そのため、パージやフラッシュと言った処理を特定のスレッドにバインドするといった管理が可能である。上記のCREATE RESOURCE GROUPコマンドの例では省略しているが、-20〜19の範囲でスレッドの優先度を指定することも可能である。USERタイプのリソースグループは0〜19が、SYSTEMタイプのリソースグループには-20〜19の範囲で優先度を指定可能となっている。省略した場合はデフォルトの0が適用される。

リソースグループを活用すると、例えばバッチ処理の優先度を落とす、特定のバックグラウンドスレッドの優先度を上げる、ユーザーごとに使用するVCPUを分けるといった使い方が可能である。やや面倒臭いが、使いこなすとシステムのリソースをより一層有効活用できるだろう。

InnoDBの改良

みんなが大好きなInnoDBストレージエンジンにも様々な改良が加えられている。改良点は多いので、主要なものを以下に列挙したい。

  • パフォーマンスの向上。特にR/Wトランザクションの実行性能や、特定の行にアクセスが集中するケースでの実行性能、ディスクI/Oのスループットなどが向上している。
  • バッファプールなどのいくつかのオプションを自動設定するinnodb_dedicated_serverオプションが追加された。サーバーマシンが持つリソースを全て割り当てる前提であれば、お手軽に設定ができるので便利だろう。
  • AUTO_INCREMENTカラムの値が、トランザクションをロールバックしてもREDOログに書き込まれるようになった。(不意に再起動しても逆戻りしない。)
  • インデックスの破壊を検知した場合、そのことを示すフラグがREDOログに書き込まれるようになった。
  • デッドロック検知を無効化するための、innodb_deadlock_detectオプションが追加された。無効化することで検知のためのオーバーヘッドが減少するが、実際にデッドロックが生じてしまうとロック・ウェイト・タイムアウトまで待ってしまうので注意しよう。
  • テーブルスペースだけでなく、REDOログとUNDOログの暗号化が可能になった。
  • SELECT ... FOR UPDATEに、追加でNOWAITあるいはSKIP LOCKEDを指定できるようになった。非常に高い頻度でロックが競合するようなケースにおいて、アプリケーションの要件次第では緩和策として使えるかも知れない。
  • UNDOログのトランケーションがデフォルトで有効になった。
  • パーティショニングされたテーブルの、パーティションの追加や削除、再構築などがインプレース方式(オンライン)でできるようになった。従来はテーブル全体をロックする必要があり、特に再構築などでは更新がブロックされる要因となっていたが、MySQL 8.0ではこれが解消されることになる。
などなど。変更点の中には、ユーザーが意識しなくても恩恵に預かれるものが多いという点も、InnoDBにおける改良点の魅力である。

TempTableストレージエンジン

MySQLでは、クエリの実行過程でテンポラリテーブルが必要になった際、条件が揃えば最初はメモリ上にテンポラリテーブルを作成し、データが大きくなってメモリに収まらなくなると、ディスクへ中身をコピーするという動作をするようになっている。MySQL 5.7では、ディスク上のテンポラリテーブルとしてInnoDBが利用できるようになった。MySQL 5.6ではMyISAMだけが利用可能だった。InnoDBとはいっても、テンポラリテーブルのために最適化されたものであり、REDOログの記録などテンポラリテーブルとして必要のないものを削ぎ落とし、実行速度を向上させてある。(詳しくは拙著、詳解MySQL 5.7などを参照してほしい。)

MySQL 8.0ではメモリ上のテンポラリテーブルのストレージエンジンが、従来のHEAPからTempTableストレージエンジンへと切り替えられた。TempTableストレージエンジンは、内部的なテンポラリテーブルのための専用のストレージエンジンであり、ユーザーテーブル用のストレージエンジンとしては使えない。HEAPは基本的に固定長のデータしか扱えないため、VARCHARなどの可変長データを格納する際の空間効率が良くなかった。TempTableストレージエンジンは可変長データにも対応しており、より効率的にメモリを活用することができるようになっている。詳細については、リファレンスマニュアルを参照して欲しい。

オプティマイザヒント

オプティマイザヒントにも改良が加えられている。前述したリソースグループの指定以外に、JOINの順序を指定することができるようになっている。これまで、JOINの順序を意図的に変えようと思った場合、STRAIGHT_JOINなどを活用して、SQLの構文自体をいじくる必要があった。オプティマイザヒントを使用すれば、SQLの構文はそのままで、コメント内の記述だけでJOINの順序を思いのままに変更することが可能である。以下はJOIN_ORDERオプティマイザヒントの使用例である。
mysql> SELECT /*+ JOIN_ORDER(City, Country) */ Country.Name, City.Name
    FROM City JOIN Country ON City.CountryCode = Country.Code
    WHERE Country.Name LIKE 'J%' AND City.Name LIKE 'T%';

エラーログのリファクタリング

MySQL 8.0ではエラーログ回りが大幅にリファクタリングされ、ログの出力先のバリエーションが増え、なおかつフィルタリング機能が充実した。ログの出力先としては、デフォルトのファイル形式のログだけでなく、従来から可能であったsyslog(UNIX系)やEvent Log(Windows)への出力だけでなく、JSON形式のログファイルへの出力が可能となっている。JSON形式のログファイルでは、若干参照できるメタデータの内容が多い。また、従来は「どのログに出力するか」を選択する形式になっていたが、複数のログへ同時に出力することも可能となった。

また、ログレベルによるフィルタリングに加え、ルールベースのフィルタリングも可能となっている。状況に合わせた、より柔軟なログの運用ができるだろう。

エラーログは、MySQL 8.0から新たに導入された「コンポーネント」という仕組みを使って実装されている。コンポーネントはプラグインに置き換わるものであり、コンポーネント間でAPIを相互に呼び出しあったり、あるいは依存関係を定義することができるようになっている。ただし、GA時点でコンポーネントとして実装されているのは、エラーログ関係のものと、Validate Passwordコンポーネント(プラグインからコンポーネントへと移植された)にとどまっている。

ルールベースのフィルタリングを行うには、log_filter_dragnetコンポーネントをインストールする必要がある。プラグインではSONAMEを指定し、ファイル名を入力してインストールする必要があったが、コンポーネントではファイル名の入力は不要である。以下はlog_filter_dragnetコンポーネントのインストール例である。
mysql> INSTALL COMPONENT 'file://component_log_filter_dragnet';
インストールが完了すると、mysql.componentテーブルにエントリが追加される。現在インストールされているコンポーネントについてはこのテーブルで確認しよう。ちなみに、アンインストールは上記のコマンドのINSTALLをUNINSTALLに変えて実行する。インストールが完了したら、エラーログを記録するのに用いるコンポーネントを変更する。
mysql> SET PERSIST log_error_services = 'log_filter_dragnet; log_sink_internal';
次にルールの設定を行う。ルールは、dragnet.log_error_filter_rulesオプションで行う。(余談であるが、MySQL 8.0からドット入りのオプション名が登場するようになった。コンポーネントごとのオプションは、「コンポーネント名.オプション名」という表示になる。)フィルタリングルールの形式は極めてシンプルな形式となっており、基本的にはドット終端の以下のような形式のルールを羅列する。
IF 条件 THEN アクション.
例えば、従来のログレベルに基づいたフィルタリングであるlog_error_verbosity=2と同様に、WARNING以上のメッセージだけを記録したい場合には、次のようにdragnet.log_error_filter_rulesを設定する。
mysql> SET PERSIST dragnet.log_error_filter_rules = 'IF prio>=INFORMATION THEN drop.';
フィルタリング用のコンポーネントも複数同時に利用可能なので、log_error_verbosityを指定しつつ別途ルールを記述することも可能であるが、冗長なだけなので、ルールベースのフィルタを用いるのであればlog_filter_dragnetのほうが良いだろう。フィルタリングはログレベルの他、MySQLのエラーコードやOSのエラーコード、ユーザー、ホストなどで行うことができる。アクションにはエラーメッセージを削除するdropの他、エラーの量を抑制し、ログが溢れないようにするthrottleなども指定可能だ。詳細はリファレンスマニュアルを参照して欲しい。

SRIDのサポート

空間データにおいて、SRID(Spatial Reference System Identifier)がサポートされた。SRIDは、平たく言えば座標系を識別するためのものである。空間データは、座標系ごとに座標の持つ意味合いや、スケールが異なる。SRIDを指定することで、よく知られた座標系上の空間データを定義し、各種関数の演算結果もその座標系に沿ったものにしてくれる。例えば4326は地理座標系であり、座標は緯度と経度によって表現される。例えば以下は東京ー新大阪間の直線距離を求めるSELECTである。
mysql> SELECT ST_Distance(ST_GeomFromText('POINT(35.681167 139.767052)', 4326), ST_GeomFromText('POINT(34.733165 135.500214)', 4326)) AS tokyo_shin_osaka_distance;
MySQLで利用可能なSRIDは、information_schema.ST_SPATIAL_REFERENCE_SYSTEMSで参照可能だ。SRIDについては、こちらの記事の解説が分かりやすいだろう。

インデックスの改良

インビジブルインデックスは、オプティマイザから一時的にインデックスが見えなくなるようにする機能である。インデックスの構築には時間がかかるので、本当に必要なインデックスをDROPすることは避けなければならない。そこで、インデックスを安全に削除できるかということを見極めるために、一時的にインデックスをオプティマイザから隠すことで、そのインデックスが使われているかどうかを判定することができる。インデックスをインビジブルにするには、次のようにコマンドを実行する。
mysql> ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE;
この状態では、オプティマイザが一時的に使わないだけで、インデックスは実際には存在する。上記のコマンドのINVISIBLEの部分をVISIBLEに変更してコマンドを実行すれば、インデックスはまたオプティマイザから見えるようになる。インビジブルにしておいて影響を見て、本当に削除しても大丈夫かを判断するわけである。インビジブルインデックスを活用すれば、運用においてインデックスを削除する際のリスクが、少しだけ減ることになる。

MySQL 8.0では、逆順に並んだインデックスを作成できるようになった。過去のバージョンでは、インデックス作成時にDESCを指定することは可能であったが、指定しても効果はなく、無視されるだけであった。MySQL 8.0では、逆順のインデックスを作成することが可能になる。検索を頭から行うか、末尾から行うかで、検索性能は若干ながら異なる。逆順のインデックスを使うと、ORDER BY DESCがついたクエリの検索性能が少しだけ改善されることになる。一方で先頭からソートするようなケースでは遅くなる。ソートの順序に合わせてインデックスの並びを選ぶことができるというのは、これまでにないメリットである。

その他のセキュリティ関係の改良

デフォルトで使われる認証プラグインが、従来のnative_passwordからcaching_sha2_passwordへと変更された。このプラグインは名前にcachingとついていることからも推測できるようにメモリ上のキャッシュを構築し、認証に必要なハッシュの計算を省略することで、パフォーマンスが向上するようになっている。認証にはSHA256ハッシュを使っているのだが、従来からあるsha256_password認証プラグインよりもパフォーマンスに優れている。

管理者が用いるSUPER権限は、非常に多岐にわたるタスクに対して利用できるものであるので、SUPER権限をユーザーに付与するのはためらわれることが多い。MySQL 8.0ではSUPER権限でできるタスクをより細かいものへと分割された。特定のタスクの実行だけを、root以外のユーザーへ許可するということが可能になっている。権限の詳細については、マニュアルを参照して欲しい。なお、新しく追加されたこれらの権限は、Dynamic Privilegeという仕組みを用いて提供されている。これは、MySQLサーバーが実行時に権限を定義できるというもので、各種プラグインやコンポーネントが独自の権限を定義するのに利用できるフレームワークである。例えば、監査ログプラグインは、AUDIT_ADMINという権限を定義するようになっている。

さようなら、クエリキャッシュ

MySQLに特徴的な機能として、多くの方から愛用されたクエリキャッシュは、時代と共にその役割を終え、ついにMySQL 8.0から削除された。クエリキャッシュのような結果をキャッシュする機能を使いたい場合は、アプリケーション側で結果をキャッシュするなど工夫して欲しい。MySQL側でやろうとするとオーバーヘッドが大きくなってしまうからだ。

クエリキャッシュファンには申し訳ないが、MySQL 8.0ではもう使うことはできないので諦めて欲しい。

まとめ

本稿で見ていただいたように、MySQL 8.0の改良点は非常に多岐にわたるものである。MySQL 5.7に続き、MySQL 8.0の新機能は負けるとも劣らないほどの充実ぶりである。いや、さらにMySQLの歴史を遡れば、MySQLの進化の速度は、バージョンを追うごとに速くなっていることは明確である。MySQLは立ち止まらない。これからも進化は続いていくだろう。

そして、これらの新機能は運用や開発の役に立つものが多い。UNDOログのトランケーションがデフォルトで有効化されている点など、意識しなくても恩恵に預かれるものが多い点も見過ごせない。新機能はまだまだこれだけではない。より詳しく知りたい人は、リファレンスマニュアルや、MySQLサーバーチームのブログを参照して欲しい。

なお、MySQLサーバー 8.0本体のリリースに伴い、ドライバ(MySQL用語ではコネクタ)やMySQL Shell、MySQL Routerといった周辺プロダクトについても、バージョン8.0のものが登場している。これらはMySQLサーバーとバージョンを合わせたものであり、バージョン番号によって互換性の有無が明確に分かるようになっている。これまではサーバー本体と他のプロダクトのバージョンに関連性がなく、互換性については直感的に分かるものではなかったが、今後はより分かりやすくすることで、利用における敷居がひとつ下がることだろう。

ぜひこれらの機能を活用し、より良いMySQLライフを満喫して欲しいと思う。

0 コメント:

コメントを投稿