Day10 後半のゴール
「ビューの“できること・できないこと”を具体例で説明できるようになる」
前半では、ビュー=「保存されたSELECT文」「仮想テーブル」というイメージを作りました。
後半のゴールはここです。
ビュー経由でINSERT / UPDATEできる場合・できない場合を説明できる
ビューが遅くなりやすいパターンをイメージできる
「ビューでやるべきこと」と「やらない方がいいこと」の線引きを持つ
ここまで行くと、「とりあえずVIEW作る」から一歩抜け出せます。
更新可能なビューと更新できないビュー
「“単純な1テーブルビュー”は基本的に更新できる」
まず一番よく聞かれるところからいきます。
ビューって、INSERT / UPDATE / DELETE できるの?
答えは、
条件を満たす“単純なビュー”ならできる
複雑なビューは基本的にできない
です。
単純な1テーブルビューの例
前半で作った active_users を少し変えてみます。
CREATE VIEW active_users AS
SELECT
id,
name,
email
FROM users
WHERE is_deleted = 0;
SQLこのビューに対して、こう書くことができます。
UPDATE active_users
SET name = '新しい名前'
WHERE id = 1;
SQLこれは、裏側で
UPDATE users
SET name = '新しい名前'
WHERE id = 1
AND is_deleted = 0;
SQLのようなイメージで処理されます。
ポイントは、
1つの元テーブル(users)だけを参照している
GROUP BY や集計がない
DISTINCT などで行が潰れていない
といった「素直なビュー」であることです。
更新できないビューの典型パターン
「JOIN・集計・DISTINCT が入ると“どこを直すか”が曖昧になる」
逆に、更新できないビューはどんなものか。
JOIN を含むビュー
CREATE VIEW user_orders AS
SELECT
u.id AS user_id,
u.name AS user_name,
o.id AS order_id,
o.total AS order_total
FROM users u
JOIN orders o
ON o.user_id = u.id;
SQLこのビューに対して、
UPDATE user_orders
SET user_name = 'X'
WHERE user_id = 1;
SQLと書いたとき、
DB側からすると「users を直すのか? orders を直すのか?」が曖昧です。
(この例ならusersだろう、と人間は思いますが、ビューの定義だけでは一般化しづらい)
MySQLは、こういう「どのテーブルをどう更新すべきか曖昧なビュー」は
基本的に更新不可とみなします。
集計ビュー
CREATE VIEW user_order_summary AS
SELECT
u.id AS user_id,
u.name AS user_name,
SUM(o.total) AS total_amount
FROM users u
JOIN orders o
ON o.user_id = u.id
GROUP BY u.id, u.name;
SQLこのビューに対して、
UPDATE user_order_summary
SET total_amount = 0
WHERE user_id = 1;
SQLと書かれても、
「どの orders の行をどう変えれば total_amount=0 になるのか」が定義できません。
こういう「集計結果を持つビュー」も、当然更新不可です。
ビューのパフォーマンスの注意点
「ビューは“SQLを短くする”だけで、“自動で速くなる”わけではない」
ここが誤解されがちなポイントです。
ビューを作ったからといって、
クエリが自動的に速くなるわけではありません。
ビューはあくまで「SELECT文の別名」です。SELECT * FROM user_orders を実行すると、
内部的にはビューに書かれたJOINやWHEREがそのまま展開されます。
ビューの上にさらに複雑なクエリを重ねると…
例えば、こういう構造を考えます。
user_orders ビュー
→ users × orders のJOIN
その上で、さらにこう書く:
SELECT
user_id,
SUM(order_total) AS total_amount
FROM user_orders
WHERE order_total >= 1000
GROUP BY user_id;
SQL見た目はスッキリしていますが、
内部的には、
users × orders のJOIN
→ その結果に対して WHERE / GROUP BY
という処理が行われます。
ビューを使わずに最初から書いても、
ほぼ同じ実行計画になります。
つまり、
ビューは「読みやすさ・再利用性」を上げる道具であって、
それだけで性能が上がる魔法ではない
ということです。
ビューを乱用したときの“つらさ”
「ビューの上にビューを重ねると、何が起きているか分からなくなる」
実務でよくある失敗パターンがこれです。
view_a … そこそこ複雑なSELECTview_b … view_a をさらに加工したビューview_c … view_b をさらに加工したビュー
そしてアプリからは SELECT * FROM view_c。
こうなると、
どのテーブルがどれだけJOINされているのか
どこでフィルタされているのか
どこにインデックスを張れば効くのか
が、だんだん見えなくなっていきます。
パフォーマンスが悪くなったときに、
原因を追いかけるのが一気に難しくなります。
Day10 の段階では、
ビューは「1段か2段まで」に抑える
ビューの上にビューを重ねすぎない
という感覚だけ持っておけば十分です。
マテリアライズドビューとの違い
「MySQLのVIEWは“生のSELECT”、キャッシュではない」
他のDB(PostgreSQLなど)には、
「マテリアライズドビュー」という機能があります。
マテリアライズドビュー
SELECTの結果を一度テーブルとして保存しておき、
必要なタイミングでリフレッシュする仕組み
これは「ビュー+キャッシュ」のようなイメージです。
MySQLのVIEWは、
少なくとも標準的な機能としては「マテリアライズドビュー」ではありません。
毎回、元テーブルに対してSELECTが実行される
→ 結果を保存しておくわけではない
という点を押さえておいてください。
もし「重い集計結果をキャッシュしたい」なら、
集計結果を格納する専用テーブルを作る
バッチやトリガーでそのテーブルを更新する
といった設計を自分で組む必要があります。
ビューを使うべきところ・避けるべきところ
「“意味を名前にする”ところで使う、“重い処理の隠れ蓑”にはしない」
ここまでの話を、実務目線で一言にまとめるとこうなります。
ビューを使うべきところ
意味のある概念に名前を付けたいとき(active_users, user_orders など)
見せてよいカラムだけを切り出したいとき(users_public など)
複雑なJOINやWHEREを1か所に集約したいとき
ビューを避けるべきところ
重い集計やJOINを「見えなくするため」に隠すだけのとき
ビューの上にさらにビューを重ねて、実行計画が追えなくなりそうなとき
「速くなるはず」と期待して、とりあえずVIEWにしているだけのとき
特にセキュリティの観点では、
ビューは「見せる範囲を制限する窓」としてはとても有効
ビューは「重い処理を隠すカーテン」として使うと危険
という線引きをしておくと、
設計の判断がブレにくくなります。
Day10 後半のまとめ
MySQL のビューは「保存されたSELECT文」であり、単純に1つのテーブルだけを参照し、GROUP BY や DISTINCT などで行を潰していないビューであれば、UPDATE active_users SET name = ... のようにビュー経由で更新できるが、JOIN や集計を含むビューは「どの元テーブルをどう更新すべきか」が曖昧になるため、基本的に更新不可とみなされる。
ビューはテーブルのように SELECT * FROM view_name で使えるものの、内部的には毎回元テーブルに対してSELECTが展開されるだけで、「ビューにしたから速くなる」ということはなく、むしろビューの上にビューを重ねていくと、どれだけJOINされているか・どこでフィルタされているかが見えにくくなり、パフォーマンス問題の原因追跡が難しくなる。
他のDBにある「マテリアライズドビュー」のように結果を物理的にキャッシュする仕組みは、MySQLの通常のVIEWにはなく、重い集計結果をキャッシュしたい場合は専用テーブル+バッチ更新などを自分で設計する必要があるため、ビューは「意味のある概念に名前を付ける」「見せてよいカラムだけを切り出す」といった用途に絞り、「重い処理を隠すためのカーテン」として乱用しない、という線引きが実務では重要になる。
