0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DB用語の歯がゆさ 第10回】ビューとマテリアライズドビュー ― どっちをいつ使う?

0
Posted at

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト

このシリーズについて

「知ってはいる。けど、人に説明しろと言われると詰まる」——そんな歯がゆいDB用語を、1記事1用語・図解中心で解消していくシリーズです。

まず自己診断

「この集計クエリ、毎回書くの面倒だからビューにしとくね」——よくある会話です。では:

ビューにすると、そのクエリは速くなりますか?

なりません。1ミリ秒も。 ここで「あれ?」と思ったなら、この記事の対象読者です。

そして「速くしたいならマテリアライズドビュー」と言われても、なぜそっちは速いのか・何を失うのかまで説明できる人は多くありません。整理します。

結論:この1枚

両者の違いは、「SELECT文を保存する」のか「SELECT文の結果を保存する」のか、それだけです。

ビュー マテリアライズドビュー
保存されるもの SELECT文 実行結果(実データ)
参照時の処理 毎回そのSELECTを実行 保存済みの表を読むだけ
鮮度 常に最新 REFRESHした時点のまま
速度 元のクエリと同じ 桁違いに速い
ディスク使用 ほぼゼロ 結果ぶん消費

「最新か、速いか」のトレードオフ。これが本質です。

以下、PostgreSQL 16.14(Docker postgres:16) で顧客5万件・注文500万件(orders は363MB)を使った実測です。

「ビューにすると速くなる」を実測で解体

地域別の売上を集計する、それなりに重いクエリを用意しました。これをビューにします。

CREATE VIEW v_region_sales AS
SELECT c.region, count(*) AS order_count, sum(o.amount) AS total_amount,
       avg(o.amount)::numeric(10,2) AS avg_amount
  FROM orders o JOIN customers c ON c.id = o.customer_id
 GROUP BY c.region;

① 元のクエリを直接実行した実行計画(抜粋):

Finalize GroupAggregate  (cost=81427.91..81428.99 rows=4 width=37)
  ->  Hash Join  (cost=1444.00..59594.51 rows=2083330 width=9)
        ->  Parallel Seq Scan on orders o  (cost=0.00..52681.30 rows=2083330 width=8)
Execution Time: 1083.785 ms

② ビュー経由(SELECT * FROM v_region_sales):

Finalize GroupAggregate  (cost=81427.91..81428.99 rows=4 width=37)
  ->  Hash Join  (cost=1444.00..59594.51 rows=2083330 width=9)
        ->  Parallel Seq Scan on orders o  (cost=0.00..52681.30 rows=2083330 width=8)
Execution Time: 1187.876 ms

コストの数字が小数点まで完全に同一です。500万行のスキャンも集計もそのまま行われています。実行時間の差(1083ms と1187ms)は測定ごとのブレで、有意差ではありません。

ビューは「名前を付けただけ」。参照するたびに中身のSELECTが展開されて実行されます。だから速くなりようがありません。

ビューの価値は速度ではなく、複雑なSQLに名前を付けて隠すこと・見せたい列だけを見せることにあります。

マテリアライズドビューの実測

同じ定義でマテリアライズドビューを作ります。

CREATE MATERIALIZED VIEW mv_region_sales AS
SELECT c.region, count(*) AS order_count, ... (定義はビューと同じ);

作成に 919.713 ms かかりました。これはこの時点で1回集計を実行し、結果を保存したからです。参照してみます。

Seq Scan on mv_region_sales  (cost=0.00..18.80 rows=880 width=64)
Execution Time: 0.016 ms

1083 ms → 0.016 ms。 500万行の集計が消え、4行の小さな表を読むだけになりました。ディスク使用量も確認すると、性格の違いがはっきりします。

ビュー v_region_sales     : 0 bytes
マテビュー mv_region_sales: 24 kB
ベース表 orders           : 363 MB

代償:鮮度が止まる

ここが本題です。ベーステーブルに10万件追加してみます。

INSERT INTO orders (customer_id, amount, created_at)
SELECT 4, 100, now() FROM generate_series(1,100000);

追加前と追加後で、両者を比べます(east地域の注文件数)。

ビュー マテリアライズドビュー
INSERT前 1,250,000 1,250,000
INSERT後 1,350,000 1,250,000 ← 古いまま

マテリアライズドビューは自動で更新されません。 明示的にREFRESHして初めて追いつきます。

REFRESH MATERIALIZED VIEW mv_region_sales;   -- Time: 1400.441 ms

REFRESH後は両者とも 1,350,000 で一致しました。当然ながら、REFRESHには集計をやり直すぶんの時間(今回は約1.4秒)がかかります。

つまりマテリアライズドビューを使うとは、「どれくらい古いデータなら許せるか」を決めることとほぼ同義です。REFRESHを1時間おきにするなら、最大1時間古い数字がユーザーに見えます。

なお通常の REFRESH は完了までそのマテビューへの参照をブロックします。REFRESH MATERIALIZED VIEW CONCURRENTLY を使えば参照を止めずに更新できますが、一意インデックスが必須です。実際に付けずに実行するとこう怒られます。

ERROR:  cannot refresh materialized view "public.mv_region_sales" concurrently
HINT:  Create a unique index with no WHERE clause on one or more columns of the materialized view.

エンジン差:MySQLにマテリアライズドビューはない

ここも歯がゆさポイントです。MySQLにマテリアライズドビューは存在しません。 公式FAQに明記されています。

A.6.5. Does MySQL have materialized views?
No.

MySQL 8.0でも、最新の9.7のマニュアルでも回答は同じ「No」です。CREATE VIEW(通常のビュー)は使えますが、CREATE MATERIALIZED VIEW に相当する構文はありません。

ではMySQLで重い集計を高速化したいときはどうするか。集計結果を入れる普通のテーブルを自分で作り、バッチやイベントスケジューラで定期的に作り直す——これが定石です。やっていることはマテリアライズドビューと同じで、PostgreSQLが機能として持っているものを、MySQLでは手作業で組むという違いになります。

で、どっちをいつ使う?

使う場面 選ぶもの
複雑なJOINに名前を付けて再利用したい ビュー
特定の列だけ見せたい(権限の窓口にする) ビュー
ダッシュボードの重い集計を毎回走らせたくない マテリアライズドビュー
日次・時間単位の鮮度で構わないレポート マテリアライズドビュー
口座残高・在庫数など、常に正確でないと困る値 どちらでもなく、クエリ側を改善する

第4回で扱った3層スキーマを覚えているでしょうか。利用者ごとの見え方を定義する「外部スキーマ」の実装が、まさにビューです。ビューは高速化の道具ではなく、設計上の「見せ方」を表現する道具——そう捉えると位置づけがはっきりします。

まとめ:1行で説明するなら

「ビューはSELECT文に名前を付けたもので、参照のたびに中身が展開されて実行されます。だから常に最新ですが、速くはなりません。マテリアライズドビューは実行結果そのものを保存するので桁違いに速い代わりに、REFRESHするまでデータが古いままです。最新が要るならビュー、速度が要って鮮度に妥協できるならマテリアライズドビュー。なおMySQLに後者はないので、集計テーブルを自作します」

冒頭の「ビューにしとくね」に戻ります。それは整理のためであって、速くはならない。 ここが言えるかどうかが分かれ目です。

次回:最終回

第11回:レプリケーションとラグ ——「書いた直後に読めないのはなぜ?」を図解します。実は今回の「鮮度が遅れる」話と、同じ構造の問題が待っています。

(シリーズ全11回の予定は第1回に掲載しています)

参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?