跳到主要内容

视图:把复杂查询存起来当表用

📖 本节导读

  • 理解什么是视图(View):一条被"存档"的查询,一张不存数据的虚拟表
  • 掌握视图的创建、查询、修改、删除:CREATE VIEWCREATE OR REPLACEALTER VIEWDROP VIEW
  • 把学生-课程-成绩三表连接查询做成视图 v_student_score,体验"一次编写,处处使用"
  • 了解视图的两大用途(简化查询、权限隔离)和使用上的局限

一、先看一个痛点

在前面的章节里,我们经常要查"每个学生每门课考了多少分"。因为学生姓名在 student 表、课程名在 course 表、分数在 score 表,每次都得写一个三表连接:

SELECT s.name AS 学生, c.name AS 课程, sc.score AS 成绩
FROM score sc
JOIN student s ON sc.student_id = s.id
JOIN course c ON sc.course_id = c.id;

这段 SQL 本身不难,但问题是:每次想看成绩单都要重新敲一遍,写十次就烦十次,还容易敲错。

有没有办法把这条查询保存起来,以后想用的时候直接喊它的名字?——有,这就是视图

二、什么是视图

视图(View)就是一条保存在数据库里的查询语句,它对外表现得像一张表,所以也叫虚拟表

两个关键点:

  1. 视图不存数据。它只保存"查询的写法",数据仍然在原来的表(叫基表或源表)里。
  2. 每次查询视图,MySQL 都会去执行它背后的那条 SELECT,把最新结果给你。所以源表的数据变了,视图查出来的结果自动跟着变

类比:视图就像手机里的"智能相册"(比如"所有自拍")。相册本身不复制照片,只是保存了一个筛选规则;你每次点开它,看到的都是当前手机里符合规则的最新照片。

为什么要用视图?

  • 简化复杂查询:把又长又难写的多表连接、聚合查询包装成一个简单的名字,以后当普通表查。
  • 权限隔离:假如 student 表里有生日等敏感字段,可以创建一个只包含姓名、班级的视图,然后只把视图的查询权限给别人(下一节会学授权),对方就看不到敏感列了。

三、创建视图:CREATE VIEW

语法:

CREATE VIEW 视图名 AS
SELECT 语句;

把开头那条三表连接查询做成视图,习惯上视图名用 v_ 开头,一眼就能认出这是视图:

USE school;

CREATE VIEW v_student_score AS
SELECT s.name AS student_name,
c.name AS course_name,
sc.score
FROM score sc
JOIN student s ON sc.student_id = s.id
JOIN course c ON sc.course_id = c.id;
Query OK, 0 rows affected (0.02 sec)

创建成功。注意 0 rows affected——因为视图不存数据,创建它并没有搬运任何一行数据。

四、查询视图:和查普通表一模一样

现在,三表连接被浓缩成了一个名字。查成绩单只需要:

SELECT * FROM v_student_score;
+--------------+-------------+-------+
| student_name | course_name | score |
+--------------+-------------+-------+
| 张三 | 数学 | 90.0 |
| 张三 | 英语 | 85.0 |
| 张三 | 计算机 | 88.0 |
| 李四 | 数学 | 76.0 |
| 李四 | 英语 | 92.0 |
| 王小红 | 数学 | 95.0 |
| 王小红 | 英语 | 60.0 |
| 王小红 | 计算机 | 70.0 |
| 赵敏 | 数学 | 58.0 |
| 赵敏 | 计算机 | 81.0 |
| 陈刚 | 英语 | 73.0 |
| 陈刚 | 计算机 | NULL |
+--------------+-------------+-------+
12 rows in set (0.00 sec)

还可以在视图上继续加 WHEREORDER BY,甚至做聚合——把它当成一张普通表用就行

-- 查张三的所有成绩
SELECT * FROM v_student_score WHERE student_name = '张三';
+--------------+-------------+-------+
| student_name | course_name | score |
+--------------+-------------+-------+
| 张三 | 数学 | 90.0 |
| 张三 | 英语 | 85.0 |
| 张三 | 计算机 | 88.0 |
+--------------+-------------+-------+
3 rows in set (0.00 sec)
-- 基于视图统计每门课的平均分
SELECT course_name, AVG(score) AS 平均分
FROM v_student_score
GROUP BY course_name;
+-------------+----------+
| course_name | 平均分 |
+-------------+----------+
| 数学 | 79.75000 |
| 英语 | 77.50000 |
| 计算机 | 79.66667 |
+-------------+----------+
3 rows in set (0.00 sec)

陈刚的计算机成绩是 NULL(缺考),AVG() 会自动忽略 NULL,所以计算机的平均分是 (88.0 + 70.0 + 81.0) ÷ 3 ≈ 79.67。

源表变,视图跟着变

给陈刚补录计算机成绩:

UPDATE score SET score = 66.0 WHERE student_id = 5 AND course_id = 3;

SELECT * FROM v_student_score WHERE student_name = '陈刚';
+--------------+-------------+-------+
| student_name | course_name | score |
+--------------+-------------+-------+
| 陈刚 | 英语 | 73.0 |
| 陈刚 | 计算机 | 66.0 |
+--------------+-------------+-------+
2 rows in set (0.00 sec)

我们没有动过视图,但视图的查询结果立刻更新了——再次证明视图不存数据,它只是源表数据的一扇"窗户"

(为了和后续章节的数据保持一致,把这条成绩改回去:)

UPDATE score SET score = NULL WHERE student_id = 5 AND course_id = 3;

五、查看视图的定义:SHOW CREATE VIEW

忘了视图背后是哪条 SELECT?可以查看它的定义:

SHOW CREATE VIEW v_student_score\G
*************************** 1. row ***************************
View: v_student_score
Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost`
SQL SECURITY DEFINER VIEW `v_student_score` AS select `s`.`name` AS
`student_name`,`c`.`name` AS `course_name`,`sc`.`score` AS `score` from
((`score` `sc` join `student` `s` on((`sc`.`student_id` = `s`.`id`))) join
`course` `c` on((`sc`.`course_id` = `c`.`id`)))
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
2 rows in set (0.00 sec)

末尾的 \G 表示把结果"竖着"显示,字段太多横着看会乱时很好用。另外 SHOW TABLES; 的结果里也会列出视图——它看起来就和表混在一起。想区分谁是表谁是视图,可以用 SHOW FULL TABLES;,视图会标注为 VIEW

六、修改视图

方式一:CREATE OR REPLACE VIEW(推荐)

"存在就替换,不存在就创建",最省心:

CREATE OR REPLACE VIEW v_student_score AS
SELECT s.name AS student_name,
c.name AS course_name,
c.teacher,
sc.score
FROM score sc
JOIN student s ON sc.student_id = s.id
JOIN course c ON sc.course_id = c.id;

上面给视图新增了一列任课老师 teacher

方式二:ALTER VIEW

只能修改已存在的视图,语法和 CREATE VIEW 类似:

ALTER VIEW v_student_score AS
SELECT s.name AS student_name,
c.name AS course_name,
sc.score
FROM score sc
JOIN student s ON sc.student_id = s.id
JOIN course c ON sc.course_id = c.id;

(这条把视图改回了最初的三列定义。)

七、删除视图:DROP VIEW

DROP VIEW v_student_score;

删除视图只删掉"那条保存的查询",源表 studentcoursescore 里的数据毫发无损——再一次体现"视图不存数据"。

如果不确定视图是否存在,可以写 DROP VIEW IF EXISTS v_student_score;,避免报错。

本教程后面还会用到这个视图,如果你刚才删掉了,可以用第三节的 CREATE VIEW 语句重新创建。

八、视图的局限

视图很方便,但要知道它的边界:

  1. 一般只用来查。虽然某些简单视图(单表、没有聚合函数、没有 GROUP BY、没有 DISTINCT)可以执行 INSERT/UPDATE,但像 v_student_score 这种多表连接视图,更新会受到很多限制甚至直接报错。新手请把视图当成"只读"的,要改数据就去改源表。
  2. 不存数据,也不会更快。每次查视图,MySQL 实际执行的还是背后那条 SELECT。视图简化的是"写 SQL 的人",不是"跑 SQL 的机器"。
  3. 依赖源表。如果源表被删除或者改了列名,视图就会失效,查询时报错。

⚠️ 新手常见坑

  1. 以为视图里存了一份数据副本。视图只存"查询的写法",数据永远在源表里。源表一变,视图结果就变;源表被删,视图就查不动了。
  2. 试图往多表连接视图里 INSERT 数据。多表连接、含聚合的视图基本不可更新,会报类似 ERROR 1471 (HY000): The target table v_xxx of the INSERT is not insertable-into 的错误。改数据请直接操作源表。
  3. 视图名和表名混淆。视图在 SHOW TABLES; 里和普通表长得一样,删的时候却必须用 DROP VIEW(对视图执行 DROP TABLE 会报错)。所以强烈建议视图名统一加 v_ 前缀。
  4. 在视图上套视图、层层嵌套。技术上允许,但嵌套多了既难维护又难排查性能问题,新手阶段一层就好。
  5. 以为把查询做成视图能提速。视图不是缓存,查询该多慢还是多慢。想提速请回顾上一章的索引。

📝 小结

  • 视图 = 保存起来的 SELECT 查询 = 不存数据的虚拟表
  • 两大用途:简化复杂查询(长 SQL 起个短名字)、权限隔离(只暴露部分列/行)
  • 常用语句:
    • 创建:CREATE VIEW 名 AS SELECT ...
    • 查询:SELECT * FROM 视图名;(和查表完全一样)
    • 查定义:SHOW CREATE VIEW 视图名;
    • 修改:CREATE OR REPLACE VIEW / ALTER VIEW
    • 删除:DROP VIEW 视图名;
  • 局限:一般只读、不提速、依赖源表

✍️ 练习题

1. 判断题:删除视图 v_student_score 后,score 表里的成绩数据也会被删除。

参考答案

错误。视图不存储数据,DROP VIEW 只删除保存的查询定义,源表数据完全不受影响。

2. 请创建一个视图 v_boy,只包含 student 表中所有男生的 id、name、class_id 三列,并写出查询该视图的语句。

参考答案
CREATE VIEW v_boy AS
SELECT id, name, class_id
FROM student
WHERE gender = '男';

SELECT * FROM v_boy;

查询结果应包含张三、李四、陈刚三行。

3. 请创建一个视图 v_fail,列出所有不及格(score < 60)的记录,包含学生姓名、课程名、分数。提示:可以基于三表连接来写。

参考答案
CREATE VIEW v_fail AS
SELECT s.name AS student_name, c.name AS course_name, sc.score
FROM score sc
JOIN student s ON sc.student_id = s.id
JOIN course c ON sc.course_id = c.id
WHERE sc.score < 60;

查询 SELECT * FROM v_fail; 应得到一行:赵敏 数学 58.0。

4. 想查看视图 v_student_score 背后保存的是哪条 SELECT 语句,应该用什么命令?

参考答案
SHOW CREATE VIEW v_student_score;

(结尾用 \G 代替 ; 可以竖排显示,更易读。)

5. 简答:视图的两个主要用途是什么?为什么说"用视图并不能让查询变快"?

参考答案

两个主要用途:① 简化复杂查询——把长 SQL 保存成一个简单名字复用;② 权限隔离——只把视图授权给别人,隐藏源表中的敏感列或行。 视图不能提速,因为它不保存查询结果,每次查询视图时 MySQL 仍然要完整执行背后的那条 SELECT,工作量和直接写原始查询一样。


⬅ 上一节 | 🏠 返回目录 | 下一节 ➡