MySQLでMaterialized Viewを実現する

こんにちは。 エキサイト株式会社の三浦です。

tech.excite.co.jp

以前こちらのブログで、「MySQLにおいて、Indexだけではパフォーマンス改善に限界があり、一定以上複雑なSQLやデータ構造に対してはIndex以外の手段を講じる必要がある」ことを説明しました。 今回は、その方法の1つであるMaterialized ViewをMySQLで実現する方法を説明します。

Materialized Viewとは

Materialized Viewとは、SQL実行パフォーマンスの改善等のために作るテーブルのことです。 通常のデータ構造でのIndexだけではパフォーマンス等が改善できないときに使用します。

例を挙げてみます。

以下のような article (記事)テーブルと article_tag (記事タグ)テーブルがあるとします。

article

article_code story category publish_date_time
article1 テスト lifestyle 2022-01-01 00:00:00
article2 テストテスト child 2022-01-01 01:00:00
article3 テストテストテスト beauty 2022-01-01 02:00:00
... ... ... ...

article_tag

article_code tag
article1 芸能
article1 ニュース
article2 子育て
article2 育児
article2 夫婦
article3 ニュース
... ...

これについて、「指定のカテゴリに属しており、かつ指定のタグを持つ記事の一覧を、記事の公開日順に並び替えて取得する」ことを考えてみます。 SQLとしては以下になるでしょう。

SELECT
    article.*,
    article_tag.tag

FROM
    article

INNER JOIN
    article_tag
    ON article_tag.article_code = article.article_code

WHERE
    article.category = "***"
    AND article_tag.tag = "***"

ORDER BY
    article.publish_date_time;

この場合、どんなIndexが適しているでしょうか?

article テーブルに関しては、 category で絞り込んで publish_date_time でソートしているため category, publish_date_time のIndexが良さそうに見えます。

一方で article_tag テーブルに関しては、 tag で絞り込んでいるので tag 単体のIndexか、あるいは article_codearticle テーブルと接続しているので tag, article_code の複合Indexでしょうか?

実はこちら、完璧にハマるIndexは存在しません。

全体の件数が少なかったり、検索したいタグがほとんどの記事に含まれているなどであれば検索速度は早いですが、全体の件数が多かったり、マイナーなタグを検索する場合は速度が大きく落ちてしまいます。

こういった場合にMaterialized Viewを使用します。

元のテーブルのデータを使って、以下のようなテーブルを作ります。

article_tag_materialized_view

article_code category tag publish_date_time
article1 lifestyle 芸能 2022-01-01 00:00:00
article1 lifestyle ニュース 2022-01-01 00:00:00
article2 child 子育て 2022-01-01 01:00:00
article2 child 育児 2022-01-01 01:00:00
article2 child 夫婦 2022-01-01 01:00:00
article3 beauty ニュース 2022-01-01 02:00:00
... ... ... ...

これを使用する場合、SQLは以下のようになります。

SELECT
    article.*,
    article_tag_materialized_view.tag

FROM
    article_tag_materialized_view

INNER JOIN
    article
    ON article.article_code = article_tag_materialized_view.article_code

WHERE
    article_tag_materialized_view.category = "***"
    AND article_tag_materialized_view.tag = "***"

ORDER BY
    article_tag_materialized_view.publish_date_time;

このSQLであれば、つけるべきIndexは簡単に推測できます。

article_tag_materialized_view については tag, category, publish_date_time のIndexが、 article については article_code のIndexがあれば高パフォーマンスとなるでしょう。

このように、Indexだけではパフォーマンスが改善できないデータ取得処理でも、Materialized Viewを用いることで改善することができます。

MySQLとMaterialized View

では、MySQLでMaterialized Viewを作るにはどうすれば良いでしょうか?

残念ながら、MySQLではデフォルトでMaterialized Viewを作る機能は存在しません。

データが増減することがないテーブルであれば最初にテーブルを作ってデータを入れてしまえば終わりですが、多くのテーブルはデータの増減や更新があるでしょう。 そこで、何らかの方法でエンジニアが手を加えて、データの変更があったときにも自動で対応されるようにMaterialized Viewを生成する必要があります。

方法としては、いくつか考えられます。

Triggerを使用する

MySQLにはTriggerという機能があります。 これは、指定テーブルに Insert / Update / Delete が走ったときに、自動的にこちらが指定したSQLを実行させることができる機能です。

例えば先程のMaterialized Viewであれば、 articlearticle_tag テーブルに Insert / Update / Delete が走ったら、関連するデータをMaterialized Viewに Insert / Update / Delete するようなSQLをTriggerとして設定しておけば、自動的にMaterialized Viewのデータが正しいものになります。

この方法には以下のメリット・デメリットがあります。

メリット

  • データ元テーブルのデータ変更時に自動的にSQLが実行されるため、変更が即時Materialized Viewに適用される
  • Triggerも含めてトランザクション単位となるので、トランザクションを使用してる場合、TriggerのSQLが正しければ必ずMaterialized Viewとデータ元テーブルのデータが一致する

デメリット

  • データ元テーブルの Insert / Update / Delete と一体となって実行されるため、Triggerが無いときと比べてそれらの処理に時間がかかるようになる
  • アプリケーションコード側に処理を書くのに比べ、テーブル側に処理を書く場合はその存在が見過ごされやすく、何かあったときにバグの原因になりやすい

バッチを使用する

Materialized Viewを更新するバッチを作成し、cron等で定期実行する方法でもMaterialized Viewを更新できます。

この方法には以下のメリット・デメリットがあります。

メリット

  • アプリケーションコードを使用するため、柔軟な処理を書くことができ、Materialized View変更に対するコストを最小限にすることができる
    • 最低限の変更のみ実行するようにする、複数件の変更を1SQLで実行するなど

デメリット

  • cronで定期実行する場合、反映が即時ではなくcronのタイミング次第となってしまう
  • Materialized Viewへの処理が何らかの理由で失敗してしまった場合、Materialized Viewのデータとデータ元テーブルのデータが乖離してしまう可能性がある

他にもあるかもしれませんが、ざっくり考えられるのはこのあたりでしょう。

個人的には、データ元テーブルに対する変更処理の実行速度の低下が問題にならないのであれば、データ整合性の観点から基本的にはTriggerを使い、そこがどうしても問題になったり、何かしらの理由でTriggerが十分に使えない場合はバッチを使うのが良いと考えています。

なおTriggerを使う場合は、Trigger自体を作成するSQLをバージョン管理するなどして、その存在が忘れられてしまわないよう対策を講じましょう。

最後に

Materialized Viewは非常に便利な概念です。 ですが、安易にたくさん作ってしまうと管理が大変になってしまうというデメリットも抱えています。

可能な限りデータ構造自体を正したりIndexを使ってパフォーマンスを改善するようにし、どうしようもない場合だけMaterialized Viewを使うようにしましょう。