视图不存数据,只存一句 SELECT——它是「看数据的一个窗口」,不是表的副本。
所以改基表,视图立刻变;它没有「刷新」这一步,每次查都去问基表。
视图在编译时就被展开成基表查询,实测查询耗时跟直接查基表一样,不会因为多一层而变慢。
第 8、9 章讲的是「怎么查」, 从这一章开始讲「怎么把查询存起来复用」——这就是视图。 它属于第三篇「高级应用」的第一站。 教材 10.1 分三小节:视图的概念、视图的分类、视图与表的区别。 三节都在讲「它是什么」,没有一句 SQL,但这一节想明白了,后面两节会顺很多。 演示里建了一个「在售商品」视图,然后改了基表的数据,看视图跟着变; 最后在 20 万行的表上比了一次「查视图」和「查基表」的耗时——两个数基本一样。
一、视图的概念:一句存下来的查询
报表要反复看「在售商品的名字、单价、库存」。 每次都写一遍 WHERE is_on = 1 很啰嗦,
写错了还没人发现——那就把它存下来,起个名字。
CREATE VIEW v_on_sale AS
SELECT id, gname, price, stock FROM tb_goods WHERE is_on = 1
建完之后,v_on_sale 用起来跟表一模一样: 能 SELECT、能 WHERE、能 ORDER BY,
还能跟别的表 JOIN。
但库里存的不是数据,是那句 SELECT 的原文。 演示里查了数据字典:type 是 view,
sql 那列里躺着完整的 CREATE VIEW 语句,一行数据都没有。
所以「视图」这个名字有点误导。 它不是「另一个视角的表」,
它是「一条存起来备用的查询」——每次查它,数据库就把那句 SQL 现展开一遍。
二、视图不存数据:改基表,视图立刻变
这一点最能说明视图的本质。 演示里把 1006 号打印机下架(is_on 从 1 改成 0),
再数一次视图里的行数:从 5 变成 4。
没有「刷新」这一步,也不需要。 因为视图本来就没有自己的一份数据可刷新——
它每次被查,都去问基表要。
反过来说,视图也保护不了数据。 有人直接改基表,视图里的结果立刻就变了,
视图挡不住——它只是个窗口,窗口挡不住屋里的人搬东西。
这一点跟「物化视图」正好相反。 物化视图(SQL Server 里叫索引视图)是真把结果存下来的,
查得快,但要手动或定期刷新,而且刷新之前看到的是旧数据。 两个东西别混。
三、视图与表的区别
| 对比项 | 表 | 视图 |
|---|---|---|
| 存什么 | 存数据(行和列) | 只存一句 SELECT |
| 占多少空间 | 随数据量涨 | 几乎为 0 |
| 数据从哪来 | 自己就是源头 | 全部来自基表 |
| 基表变了 | 跟它无关 | 下次查立刻反映 |
| 能不能加索引 | 能 | 普通视图不能(索引视图另算) |
第一行是根,后面四行都是从它推出来的。 不存数据,所以不占空间、所以数据只能来自基表、 所以基表一变它就变、所以没法在自己的数据上建索引。 「能不能加索引」这一条最容易被忽略。 有人以为给视图建索引能加速, 结果发现普通视图根本建不了——要建只能建成索引视图,而那是另一回事(真存数据、要维护)。
四、视图的分类
| 分法 | 类型 | 说明 |
|---|---|---|
| 按用途 | 常规视图 | 一条 SELECT,最常见 |
| 索引视图 | 真存数据、能建索引,要手动维护 | |
| 分区视图 | 把多张结构相同的表 UNION 起来当一张看 |
|
| 按来源 | 单表视图 | 只查一张表,通常可更新 |
| 多表视图 | JOIN 出来的,一般只读 |
日常用的 99% 是常规视图。 索引视图和分区视图属于「有特殊性能或规模需求时才考虑」的东西,
而且各有各的维护成本——索引视图要随基表变化重建,分区视图要把 UNION 写全。
「可更新」是 SQL Server 的一个特性。 单表、不含聚合和 DISTINCT 的视图,
对它 INSERT/UPDATE,会真的落到基表上。 10.2 会演示这回事。
五、查视图会不会更慢:实测
很多人担心「多套一层会不会变慢」。 在 20 万行的表上测一次就知道了:
| 写法(20 万行) | 最快一次 |
|---|---|
| 直接查基表,条件写全 | 19.68 ms |
| 查视图,条件藏在视图里 | 18.33 ms |
两个数基本一样,差的那 1 毫秒在这个量级上就是噪声(后台进程来一下就是这个数)。
原因是视图在编译时就被展开成基表查询了。 它是个「文本替换」,
不是一层真的中间结果——数据库不会先算完视图再查它。
所以别怕用视图。 该担心的是「视图里那句 SQL 本身写得差不差」,
而不是「多了一层」。 视图里的 WHERE 没走索引,直接查基表一样慢。
六、动手 Demo:看视图的定义、改基表看视图跟着变、比一次耗时
这一段先建一个在售商品视图,然后去数据字典里翻它的定义——看看到库存了什么。 接着把一件商品下架,再数视图里的行数;最后在 20 万行的表上比「查视图」和「查基表」的耗时。
import sqlite3, time
c = sqlite3.connect(u":memory:")
# ... 建六表、装样例数据 ...
c.execute(u"CREATE VIEW v_on_sale AS SELECT id, gname, price, stock "
u"FROM tb_goods WHERE is_on = 1")
# 视图里到底存了什么
print(c.execute(u"SELECT type, name, substr(sql, 1, 34) "
u"FROM sqlite_master WHERE name = 'v_on_sale'").fetchall())
# 改基表,视图立刻变
print(c.execute(u"SELECT COUNT(*) FROM v_on_sale").fetchone())
c.execute(u"UPDATE tb_goods SET is_on = 0 WHERE id = 1006")
print(c.execute(u"SELECT COUNT(*) FROM v_on_sale").fetchone())
# 20 万行上比一次耗时
c.execute(u"CREATE VIEW v_big AS SELECT id, gname, price "
u"FROM big WHERE is_on = 1")
print(clock(u"SELECT COUNT(*) FROM big WHERE is_on = 1"))
print(clock(u"SELECT COUNT(*) FROM v_big"))
实跑输出:
--------------------------------------------------------------------
视图是什么:不存数据的一条查询
--------------------------------------------------------------------
① 一个视图,两副面孔
--------------------------------------------------------------------
需求:报表要反复看「在售商品的名字、单价、库存」,但不想每次都写一遍条件。
1> CREATE VIEW v_on_sale AS SELECT id, gname, price, stock FROM tb_goods WHERE is_on = 1
1> SELECT * FROM v_on_sale
id gname price stock
------------------------------
1001 联想笔记本 4999 30
1002 戴尔显示器 1299 50
1003 罗技鼠标 89.9 200
1004 机械键盘 399 80
1006 打印机 899 12
(5 行受影响)
用起来跟表一模一样:能 SELECT、能 WHERE、能 ORDER BY,还能跟别的表 JOIN。
但库里到底存了什么?看数据字典:
1> SELECT type AS 类型, name AS 名字, substr(sql, 1, 34) || ' ...' AS 定义的前半截
FROM sqlite_master
WHERE name = 'v_on_sale'
类型 名字 定义的前半截
-------------------------------------------------------
view v_on_sale CREATE VIEW v_on_sale AS SELECT id ...
(1 行受影响)
! 存的是那句 SELECT 的原文,一行数据都没有。视图是个「存下来的查询」。
SQL Server 里这句查的是 sys.sql_modules 或者 OBJECT_DEFINITION(),同样只有定义。
② 视图不存数据:改基表,视图立刻变
--------------------------------------------------------------------
1> SELECT COUNT(*) AS 行数 FROM v_on_sale
行数
----
5
(1 行受影响)
1> UPDATE tb_goods SET is_on = 0 WHERE id = 1006
把 1006 号打印机下架(is_on 从 1 改成 0),再数一次视图里还有几行:
1> SELECT COUNT(*) AS 行数 FROM v_on_sale
行数
----
4
(1 行受影响)
少了一行。视图没有「刷新」这一步——它每次都去问基表。
这一点跟物化视图(索引视图)正好相反:那个是真存数据,要刷新,但查得快。
③ 视图与表的区别
--------------------------------------------------------------------
| 对比项 | 表 | 视图 |
|---|---|---|
| 存什么 | 存数据(行和列) | 只存一句 SELECT |
| 占多少空间 | 随数据量涨 | 几乎为 0 |
| 数据从哪来 | 自己就是源头 | 全部来自基表 |
| 基表变了 | 跟它无关 | 下次查立刻反映 |
| 能不能加索引 | 能 | 普通视图不能(索引视图另算) |
最重要的一条是第一行:表是数据的家,视图只是看数据的一个窗口。
④ 视图的分类
--------------------------------------------------------------------
| 分法 | 类型 | 说明 |
|---|---|---|
| 按用途 | 常规视图 | 一条 SELECT,最常见 |
| | 索引视图 | 真存数据、能建索引,要手动维护 |
| | 分区视图 | 把多张结构相同的表 UNION 起来当一张看 |
| 按来源 | 单表视图 | 只查一张表,通常可更新 |
| | 多表视图 | JOIN 出来的,一般只读 |
「可更新」是 SQL Server 的一个特性:单表、不含聚合和 DISTINCT 的视图,
对它 INSERT/UPDATE,会真的落到基表上。下一节演示这回事。
⑤ 视图查询有没有额外开销:实测
--------------------------------------------------------------------
| 写法(20 万行) | 最快一次(毫秒) |
|---|---:|
| 直接查基表,条件写全 | 19.23 |
| 查视图,条件藏在视图里 | 18.60 |
两个数基本一样,差的那点在这个量级上就是噪声。
原因是视图在编译时就被展开成基表查询了——它只是个「文本替换」,不是一层真的中间结果。
所以别怕用视图,它不会因为「多套一层」就变慢。
第一段返回 ('view', 'v_on_sale', 'CREATE VIEW v_on_sale AS SELECT id ...')。
看清楚了:存的是那句 SQL,不是数据。 这是「视图」二字最该记住的一点。
第二段两个数:5 和 4。 中间那句 UPDATE 改的是 tb_goods,
视图一动没动,但查出来的结果变了——因为它每次都去问基表。
第三段 19.68 和 18.33,基本并列。 如果有人告诉你「视图会慢一倍」,
让他先在自己库上测一次——多数情况下那是视图里那句 SQL 的问题,不是视图的问题。
30 秒实验
- 给视图加一句
WHERE price > 100,再查一次。视图可以随便改,改完用起来跟新表一样。 - 把视图
DROP掉,看基表数据少不少。一条都不会少——视图本来就没存数据。 - 试着给普通视图
CREATE INDEX。会报错。要建索引只能建成索引视图,那要另一套语法。
常见误区
- 「视图就是把查询结果存下来了」。存的是查询语句,不是结果。 改基表,视图立刻变;它没有「刷新」这一步。
- 「给视图建索引能加速」。普通视图建不了索引。 要建索引得建索引视图——那是真存数据的,代价是要维护。
- 「查视图会多一层开销」。视图在编译时就展开成基表查询了,实测耗时跟直接查一样。 慢的是视图里那句 SQL,不是视图这层壳。
自测三问(点开对答案)
1. 视图里存的是什么? —— **一句 `SELECT` 的原文。** 一行数据都没有——**数据字典里那列 `sql` 放着完整定义。** 2. 改了基表的数据,视图要「刷新」吗? —— **不用,也没有这个操作。** 视图每次被查都去问基表,**所以改完立刻就能看到新结果。** 3. 视图查询会不会比直接查基表慢? —— **不会。** 实测 19.68 对 18.33,基本并列。 **因为视图在编译时就被展开成基表查询了,它只是一个文本替换。**下一篇 10.2 讲怎么建、怎么改、怎么删视图,
还有一件很多人不知道的事:单表视图在 SQL Server 上是可以直接 INSERT/UPDATE 的,
改的其实是基表。
- ☐ (本篇完,下一篇 10.2 创建视图)