CREATE VIEW 视图名 AS 查询,视图里不存数据,存的是一条 select 语句,
每次查它都重新跑一遍。
所以基表一改,视图立刻跟着变——不会过期、也没有同步延迟,
但也缓存不了任何东西,复杂视图用多了会慢。
本沙箱的视图一律只读,写会报 cannot modify … because it is a view;
MySQL 的「简单视图」是可以写的,复杂视图只读。
要在这边写,只能挂 INSTEAD OF 触发器。
前面十章都在「写查询」,这一篇开始「把查询存起来」。 视图的价值不是省打字,是「把口径固定下来」: 一段五张表的连接、一堆条件,写成视图之后只有一处定义, 十个人写十份报表,用的都是同一个口径。
一、视图是什么:存起来的一条查询
create view v_boy as select id, name, height from students where gender = '男',
建完就能像表一样查。
但 v_boy 里没有存任何数据——
它存的是「那条 select 语句」,每次查它都重新跑一遍。 所以视图也叫「虚拟表」。
students 是真表(数据在磁盘上),v_boy 是视图(只有一条规则)。
这个区别决定了很多行为:能不能写、能不能建索引、能不能存数据,全都跟着它。
二、视图不存数据,基表一变它就变
张伟的身高在 students 里改成 199.9,视图里立刻跟着变,因为它每次都在重新查 students。
这一点很重要:视图不会「过期」,也不会「同步延迟」——它压根没有自己的数据。
反过来说,视图也「缓存」不了任何东西:
查一次跑一次,复杂视图用多了会慢。 所以「用视图优化性能」是错的——
它可能比直接写查询还慢一点,因为它多了一层解析。
三、视图最实用的两个好处
一是「把复杂的查询收起来」。
一段五张表的连接、一堆 where,写成视图之后,业务代码只写 select * from v_xxx。
改逻辑时只改视图定义一处,不用翻遍所有调用点。
二是「按行授权」。
MySQL 可以只给某个账号视图的权限、不给基表的权限,
于是「他只能看男生」这件事就由视图的 where 保证,
而不是靠每个查询自己记得加条件。 本沙箱没有权限系统,这一条只能在 MySQL 上用。
还有一个隐性好处:口径统一。
「活跃用户」怎么算、「平均身高」要不要排除空值,
这些定义写进视图之后就不会各算各的。 这是视图在数据团队里最大的价值,比省打字重要得多。
四、能不能通过视图改数据:这里一律只读
insert/update/delete 三条全报 cannot modify v_boy because it is a view。
本沙箱的视图是严格只读的,再简单的视图也不许写。
MySQL 不一样:
「简单视图」是可以写的——一对一映射、没有聚合、没有 distinct、没有 union,
写进去会落到基表上。 复杂视图(带聚合、group by、union)一样只读。
所以同一句 update 视图,换个数据库行为完全不同。
写跨库代码时不能假设「视图能写」或「视图不能写」,要么统一走基表,要么在目标库上先试一次。
五、要让视图可写:INSTEAD OF 触发器
本沙箱的解法是「用触发器接管」:
INSTEAD OF INSERT ON 视图 的意思是「本来要插视图,改成干这件事」。
Demo 里插视图成功了,数据落在 students 里。
触发器里的 NEW 指「你往视图里塞的那一行」。
这条路能走通,但代价是「每加一条写入路径就多一个触发器」:
插入、更新、删除各一个,还要自己保证条件对得上。
所以实际项目里更常见的做法是「视图只读,写直接写基表」——少一层间接,少一类 bug。
六、建视图时给列改个名字
create view 视图名 (列名, 列名, …) as 查询,列名按顺序对应,个数必须一致。
这个写法在「查询里有重复列名」或「计算列没名字」时几乎是必须的:
select a.id, b.id 两个都叫 id,不起名根本没法引用。
所以多表视图里起别名是常规动作,不是可选项。
七、动手 Demo:建一个视图,看它跟着基表变,再撞三次只读
Demo 六段:建视图并查它、改基表看视图跟着变、连撞三条写操作、用 INSTEAD OF 触发器让它可写、
最后给列改名。
第二段和第三段是全篇的核心对照——「读的时候是活的」和「写的时候不行」是视图的一体两面。
import _q46 as Q
c = Q.con()
# 给一条查询起个名字
Q.run(c, "CREATE VIEW v_boy AS "
"SELECT id, name, height FROM students WHERE gender = '男'")
Q.show(c, "SELECT id, name, height FROM v_boy")
# 视图不存数据:改基表,视图跟着变
Q.run(c, "UPDATE students SET height = 199.9 WHERE id = 1")
Q.show(c, "SELECT id, name, height FROM v_boy WHERE id = 1")
# 视图一律只读
c.execute("INSERT INTO v_boy (id, name) VALUES (99, '甲')")
c.execute("UPDATE v_boy SET name = '改过' WHERE id = 1")
c.execute("DELETE FROM v_boy WHERE id = 1")
# 让它可写:INSTEAD OF 触发器接管
c2 = Q.con()
c2.execute("CREATE VIEW v_boy AS SELECT id, name, gender, height "
"FROM students WHERE gender = '男'")
c2.execute("CREATE TRIGGER tr_ins INSTEAD OF INSERT ON v_boy "
"BEGIN INSERT INTO students (id, name, gender) "
"VALUES (NEW.id, NEW.name, NEW.gender); END")
Q.run(c2, "INSERT INTO v_boy (id, name, gender) VALUES (99, '甲', '男')")
# 建视图时给列改名
c.execute("CREATE VIEW v_c (编号, 姓名, 身高) AS "
"SELECT id, name, height FROM students WHERE gender = '男'")
实跑输出:
--------------------------------------------------------------------
第五十八课:视图——给一条查询起个名字
--------------------------------------------------------------------
视图是什么:存起来的一条查询
SQL> CREATE VIEW v_boy AS SELECT id, name, height FROM students WHERE gender = '男'
完成
SQL> SELECT id, name, height FROM v_boy
id name height
-----------------------
1 张伟 172.50
4 刘洋 178.30
6 杨帆 175.10
8 黄磊 180.20
9 周涛 174.00
11 徐明 169.70
(6 行)
**建完就能像表一样查它。** **但 `v_boy` 里没有存任何数据**——
**它存的是「那条 `select` 语句」,每次查它都重新跑一遍。**
**所以视图也叫「虚拟表」。**
**`students` 是真表(数据在磁盘上),`v_boy` 是视图(只有一条规则)。**
视图不存数据,基表一变它就变
SQL> UPDATE students SET height = 199.9 WHERE id = 1
影响 1 行
SQL> SELECT id, name, height FROM v_boy WHERE id = 1
id name height
-----------------------
1 张伟 199.90
(1 行)
**张伟的身高在 `students` 里改成 199.9,视图里立刻跟着变。**
**因为它每次都在重新查 `students`。**
**这一点很重要**:
**视图不会「过期」,也不会「同步延迟」**——
**它压根没有自己的数据。** **反过来说,视图也「缓存」不了任何东西**,
**查一次跑一次,复杂视图用多了会慢。**
视图最实用的两个好处
**一是「把复杂的查询收起来」。**
**一段五张表的连接、一堆 `where`,写成视图之后,业务代码只写 `select * from v_xxx`**——
**改逻辑时只改视图定义一处,不用翻遍所有调用点。**
**二是「按行授权」。** **MySQL 可以只给某个账号视图的权限、不给基表的权限**,
**于是「他只能看男生」这件事就由视图 `where gender = '男'` 保证**,
**而不是靠每个查询自己记得加条件。** **本沙箱没有权限系统,这一条只能在 MySQL 上用。**
能不能通过视图改数据:这里一律只读
SQL> INSERT INTO v_boy (id, name) VALUES (99, '甲')
报错:cannot modify v_boy because it is a view
SQL> UPDATE v_boy SET name = '改过' WHERE id = 1
报错:cannot modify v_boy because it is a view
SQL> DELETE FROM v_boy WHERE id = 1
报错:cannot modify v_boy because it is a view
**三条全报 `cannot modify v_boy because it is a view`。**
**本沙箱的视图是严格只读的,再简单的视图也不许写。**
**MySQL 不一样**:
**「简单视图」(一对一映射、没有聚合、没有 `distinct`、没有 `union`)是可以写的**,
写进去会落到基表上。
**复杂视图(带聚合、`group by`、`union`)一样只读。**
**所以同一句 `update 视图`,换个数据库行为完全不同。**
要让视图可写:INSTEAD OF 触发器
**本沙箱的解法是「用触发器接管」**:
**`INSTEAD OF INSERT ON 视图` 的意思是「本来要插视图,改成干这件事」。**
SQL> INSERT INTO v_boy (id, name, gender) VALUES (99, '甲', '男')
影响 0 行
SQL> SELECT id, name, gender FROM students WHERE id = 99
id name gender
-----------------------
99 甲 男
(1 行)
**插视图成功了,数据落在 `students` 里。** **触发器里的 `NEW` 指「你往视图里塞的那一行」。**
**这条路能走通,但代价是「每加一条写入路径就多一个触发器」**:
**插入、更新、删除各一个,还要自己保证 `where` 条件对得上。**
**所以实际项目里更常见的做法是「视图只读,写直接写基表」**——**少一层间接,少一类 bug。**
建视图时给列改个名字
SQL> SELECT 编号, 姓名 FROM v_c LIMIT 2
编号 姓名
--------------
1 张伟
4 刘洋
(2 行)
**`create view 视图名 (列名, 列名, …) as 查询`**——**列名按顺序对应,个数必须一致。**
**这个写法在「查询里有重复列名」或「计算列没名字」时几乎是必须的**:
**`select a.id, b.id` 两个都叫 `id`,不起名根本没法引用。**
**所以多表视图里起别名是常规动作,不是可选项。**
第一段六行:男生六个。
第二段改基表,视图里张伟身高立刻变 199.90。
第三段三条写操作全报错,cannot modify … because it is a view。
第四段挂触发器之后插入成功,数据在 students 里能查到。
第五段给列改名,select 编号, 姓名 能直接用。
30 秒实验
CREATE VIEW v AS SELECT * FROM students之后改了一个学生的名字,v里看到的是新名字还是旧的?INSERT INTO 视图在本沙箱会怎样?MySQL 呢?- 为什么「用视图优化性能」这个想法是错的?
常见误区
- 把视图当表改。 本沙箱一律只读,MySQL 只有简单视图能写。写之前先确认目标库的行为。
- 指望视图缓存结果、加速查询。 它每次重新跑,复杂视图只会更慢。要加速得建索引或物化。
- 多表视图不起列别名。 两个
id撞在一起,外面引用时根本分不清哪个是哪个。
自测三问(点开对答案)
1. 视图里存的是什么? —— 一条 `select` 语句。视图本身不存数据,每次查它都重新执行那条查询。 2. 为什么改基表之后视图立刻变了? —— 因为视图没有自己的数据,每次查询都在重新读基表。 3. 本沙箱的视图能写吗?MySQL 呢? —— 本沙箱一律只读,要写只能挂 `INSTEAD OF` 触发器。 MySQL 的简单视图(无聚合、无 distinct、无 union)可写,复杂视图只读。视图建好了,下一篇看怎么查它:11.2 查看视图——它也只是系统表里的一条记录。
- ☐ (本篇完,下一篇 11.2 查看视图)