MySQL 视图一次讲透:把复杂查询"封装成表",并且还能控权限、做解耦
视图(View)最像什么?最像"把一段 SELECT 查询起个名字",然后像查表一样去查它。这样做的好处是:复杂 SQL 变简单、敏感字段能隐藏、底层表结构变化时应用更不容易被波及。
1)先把目标对齐:用视图解决什么问题
来看这三件事:
- 明确视图的使用场景
- 掌握视图的创建与使用
- 理解视图在简化、权限与解耦中的价值
一个最直观的落地动作是:先把复杂查询写出来,再把它封装进视图里,后续直接 SELECT * FROM 视图名;。
sql
SELECT * FROM v_student_total_points;
2)视图是什么:虚拟表,不存数据,只存"查询逻辑"
来看定义:视图是一个虚拟表 ,基于一个或多个基本表(或其他视图)的查询结果集。视图本身不存储数据,每次访问时通过执行定义它的查询来动态生成结果;物理上依赖基础表数据,占用的不是"数据空间",而是"逻辑表示"。并且视图可以像普通表一样用于查询、更新和管理(但更新有条件限制,后面会列出哪些视图不可更新)。
来看这段代码:视图就是把 SELECT "命名"。
sql
CREATE VIEW v_only_name AS
SELECT id, name FROM student;
3)创建视图:两种常用写法(别名重命名 / 显式列名列表)
3.1 基本语法长这样
sql
CREATE VIEW view_name [(column_list)] AS select_statement;
语法是骨架,但关键在于:列名到底由谁决定 ------由 SELECT 里的别名决定,或者由 column_list 显式指定。
3.2 写法一:在 SELECT 里用别名,直接把结果列改成想要的名字
来看这段代码:把班级、课程、成绩相关列统一改成更清晰的列名。
sql
CREATE VIEW v_student_socre AS
SELECT
s.id, s.name, s.sno, s.age, s.gender, s.enroll_date,
c.id AS class_id, c.name AS class_name,
co.id AS course_id, co.name AS course_name,
sc.id AS score_id, sc.score
FROM student s, class c, course co, score sc
WHERE s.class_id = c.id
AND sc.student_id = s.id
AND sc.course_id = co.id
ORDER BY s.id;
3.3 写法二:在 VIEW 名后显式指定结果列名(column_list)
来看这段代码:不依赖 SELECT 的默认列名或别名,直接规定视图输出列名。
sql
CREATE VIEW v_student_socre_v1
(id, name, sno, age, gender, enroll_date,
class_id, class_name,
course_id, course_name,
score_id, score) AS
SELECT
s.id, s.name, s.sno, s.age, s.gender, s.enroll_date,
c.id, c.name,
co.id, co.name,
sc.id, sc.score
FROM student s, class c, course co, score sc
WHERE s.class_id = c.id
AND sc.student_id = s.id
AND sc.course_id = co.id;
4)使用视图:复杂多表查询"降维成一张表"
4.1 先看不用视图时,多表连接查询会写得很长
来看这段代码:一次性查"用户信息 + 班级 + 课程 + 成绩",SQL 很容易越写越长。
sql
SELECT
s.id, s.name, s.sno, s.age, s.gender, s.enroll_date,
c.id, c.name,
co.id, co.name,
sc.id, sc.score
FROM student s, class c, course co, score sc
WHERE s.class_id = c.id
AND sc.student_id = s.id
AND sc.course_id = co.id
ORDER BY s.id;
4.2 再看用视图:查询直接变成"一句查表"
视图建好后,复杂 join 细节被封装起来,后续查询只剩:
sql
SELECT * FROM v_student_socre;
SELECT * FROM v_student_socre_v1;
5)视图的典型用途:隐藏字段,只暴露"允许被看到的结果"
一个很经典的需求:只想展示学生姓名与总分,学号和各科明细不想暴露。
5.1 不用视图时:查询列表里随时能被"顺手加字段"
来看这段代码:本来只查 name + total,但完全可以临时加上 sno 等字段。
sql
SELECT s.name, SUM(sc.score) AS total
FROM student s, score sc
WHERE s.id = sc.student_id
GROUP BY sc.student_id
ORDER BY s.id;
5.2 用视图封装后:查询者只能看到视图定义暴露的列
来看这段代码:把"姓名 + 总分"固定成一个视图,后续 SELECT * 就只会返回这两类信息(以及视图定义中选出的列)。
sql
CREATE VIEW v_student_total_points AS
SELECT s.id, s.name, SUM(sc.score) AS total
FROM student s, score sc
WHERE s.id = sc.student_id
GROUP BY s.id
ORDER BY s.id;
SELECT * FROM v_student_total_points;
这种写法的效果是:视图变成"固定接口",字段能被统一控制。
6)视图也能参与联表:视图 + 真实表一起 join
视图并不是孤岛,它可以像表一样参与 join。
来看这段代码:把"总分视图"再与 student 表联表,用于补充其它信息或做二次过滤。
sql
SELECT *
FROM v_student_total_points v, student s
WHERE v.id = s.id;
7)修改数据:改表会影响视图,改视图也可能影响表(但有坑)
7.1 通过真实表修改数据:视图结果会同步变化
来看这段代码:直接更新 score 表里某人的某科成绩,再查视图会发现对应行已经更新。
sql
UPDATE score
SET score = 99
WHERE student_id = 1 AND course_id = 1;
SELECT * FROM v_student_socre;
7.2 通过视图修改数据:可能成功,也可能失败(取决于视图是否可更新)
来看这段代码:对带 ORDER BY 定义的视图直接 UPDATE,会报错(典型报错:Incorrect usage of UPDATE and ORDER BY)。
sql
UPDATE v_student_socre
SET score = 99
WHERE score_id = 3; -- 可能失败:视图定义里含 ORDER BY
换成不含 ORDER BY 的版本,就可以更新,并且基表会被真正改动:
sql
UPDATE v_student_socre_v1
SET score = 99
WHERE score_id = 3;
SELECT * FROM score WHERE student_id = 1 AND course_id = 5;
结论很硬:视图不是只读的代名词,但可更新视图必须满足条件。
8)注意事项:哪些视图"不可更新"(必须背熟的清单)
先记一句总原则:修改真实表会影响视图,修改视图也会影响真实表。
再看不可更新视图的典型特征(满足其一就要警惕):
- 视图定义里使用聚合函数
- 使用 DISTINCT
- 使用 GROUP BY / HAVING
- 使用 UNION / UNION ALL
- 查询列表中包含子查询
- FROM 子句引用了不可更新视图
来看这段代码:带聚合(GROUP BY)的视图通常不可更新,用 UPDATE 会直接走不通。
sql
CREATE VIEW v_class_cnt AS
SELECT class_id, COUNT(*) AS cnt
FROM student
GROUP BY class_id;
UPDATE v_class_cnt SET cnt = 100 WHERE class_id = 1; -- 通常不可更新
9)删除视图:一条 DROP VIEW 搞定
来看这段代码:
sql
DROP VIEW v_student_socre;
10)视图的优点:把复杂度、权限与变化"隔离开"
10.1 简单性:复杂查询封装成简单接口
来看这段代码:多表 join 写一次,封装成视图,后续调用变简单。
sql
CREATE VIEW v_student_socre_v1 AS
SELECT s.id, s.name, c.name AS class_name, co.name AS course_name, sc.score
FROM student s, class c, course co, score sc
WHERE s.class_id=c.id AND sc.student_id=s.id AND sc.course_id=co.id;
10.2 安全性:隐藏敏感字段,只暴露必要列
来看这段代码:用户表里隐藏 password,仅暴露公共信息。
sql
CREATE VIEW v_user_public AS
SELECT id, username, email FROM user;
10.3 逻辑数据独立性:底层表变了,优先改视图定义,应用少改
核心意思是"应用与数据库解耦":让应用依赖视图这个稳定接口,而不是依赖底表的细节。
来看这段代码:应用侧仍查 v_user_public,底表字段变化时主要调整视图定义。
sql
SELECT * FROM v_user_public;
10.4 重命名列:提升可读性
来看这段代码:列重命名后,返回结果更像业务语言而不是数据库语言。
sql
CREATE VIEW v_student_total AS
SELECT s.name AS 姓名, SUM(sc.score) AS 总分
FROM student s JOIN score sc ON s.id = sc.student_id
GROUP BY s.id;
总结
一句话收束:视图是"把查询变成可复用、可控、可解耦的接口"。写一次复杂 SQL,后续就像查表一样调用;同时还能天然做字段裁剪与安全隔离。