视图 VIEW
本教程共 50 篇 · 第 33 篇 · 更新于 2026-07-31
33. 视图 VIEW
本节目标:学完本章你能说出视图是什么、会用 CREATE VIEW 把常用查询封装成”虚拟表”,并清楚 SQLite 视图的只读限制。
在 SQLite 里,视图(VIEW) 可以理解为”把一条查询存成一个名字”。它本身不存数据,打开数据库文件你也找不到视图对应的那块数据区——它只是把一条 SELECT 语句打包,起个名字,以后你就当表一样去查它。所以视图又常被称为”虚拟表(virtual table)“。再次提醒:SQLite 是 serverless 的,数据库就是单个文件,而视图只是这个文件里存的一段查询定义,不占额外数据空间。
一、为什么需要视图
视图主要解决三类问题,理解动机比背语法更重要:
- 简化复杂查询:当你有一条又长又绕的查询(比如多表 JOIN + 聚合),每次都重写一遍很累。把它做成视图,之后只要
SELECT * FROM 视图名即可。 - 限制数据访问(安全):你可以只暴露某些列、某些行给其他人或程序,把完整的底层表藏起来。例如只让对方看到用户名和年龄,看不到邮箱这种敏感字段。
- 统一口径:团队里大家都用同一个视图取数,避免每个人写的统计 SQL 口径不一致。
其中”限制数据访问(权限隔离)“是视图在企业里最经典的用途。假设 users 表既有姓名、年龄,也有邮箱、手机号这类敏感字段。SQL 层面你做不到”只给某人看这张表的第 2、4 列”这种精细授权,但你可以建一个只 SELECT 姓名和年龄的视图,再把视图的查询权限开放出去。底层整张表对他不可见,他只能从视图这扇”小窗”看你想让他看的列。配合 WHERE 你还能做到行级过滤,比如只暴露年龄大于 18 岁的用户。这种”用视图做接口、把基表藏起来”的做法,在数据对外共享、多团队共用同一库时特别实用——你改了基表结构,只要视图还能产出同样的列,调用方完全无感。
二、创建视图
基本语法:
CREATE [TEMP] VIEW [IF NOT EXISTS] view_name [(列名列表)]
AS
select_statement;
TEMP(或TEMPORARY):建一个临时视图,只在当前连接可见,连接关闭后自动消失。IF NOT EXISTS:视图不存在才创建,存在就跳过,不报错。- 视图的列名默认来自 SELECT 的结果列,你也可以用小括号显式指定。
下面用统一的示例表 users 和 orders 演示。假设我们常要统计每个用户的累计消费,把它封成一个视图:
sqlite> CREATE VIEW IF NOT EXISTS v_user_spend AS
...> SELECT u.id, u.name, SUM(o.amount) AS total_spend
...> FROM users u
...> LEFT JOIN orders o ON o.user_id = u.id
...> GROUP BY u.id, u.name;
sqlite> SELECT * FROM v_user_spend WHERE total_spend > 100;
以后只要查 v_user_spend 就行,复杂的 JOIN 和聚合都被藏起来了。
Note重点提示:视图里 SELECT 所引用的那些真实表,叫做”基表(base table)“。视图查到的数据始终来自基表,基表变了,视图查出来的结果也随之变——视图本身不存副本。
三、使用与删除视图
视图用起来和普通表几乎一样,能 SELECT、能 JOIN、能放在子查询里。删视图用 DROP VIEW:
sqlite> DROP VIEW IF EXISTS v_user_spend;
想列出当前库里有哪些视图,可以查系统表 sqlite_master:
sqlite> SELECT name FROM sqlite_master WHERE type = 'view';
四、SQLite 视图的只读限制
这是 SQLite 视图最容易踩的坑:SQLite 的视图是只读的。你不能写 INSERT、UPDATE、DELETE 来通过视图改基表的数据。官方明确不支持直接对视图做写操作(不像某些数据库允许”可更新视图”)。
如果业务上确实需要通过视图来写入,SQLite 提供的一条出路是:在视图上创建 INSTEAD OF 触发器(第 35 章会讲),把对视图的 INSERT/UPDATE/DELETE 拦截下来,转成你自定义的对基表的实际操作。这属于进阶技巧,初学阶段知道”视图默认只读”就够了。
Tip实用技巧:视图特别适合”固定报表”和”对外接口”。把常用统计口径做成一个视图,调用方只管 SELECT,底层表结构怎么调整都不影响他们——视图帮你挡住了变化。
Warning常见坑:① 别指望
UPDATE 视图名 SET ...能改数据,SQLite 视图只读,直接写会报错。② 视图依赖基表,如果基表被改名或删除,视图就废了;DROP 基表不会自动删视图,查询视图时才暴露问题。③ 视图不带索引,它本质是一条查询,性能取决于底层基表有没有合适的索引,别以为建了视图就自动变快。
五、视图的边界与常见误区
视图好用,但也有清晰的边界,用错反而添乱:
- 视图不是缓存,不提升性能:视图每次被查询,底层那条 SELECT 都会重新跑一遍。如果基表很大、查询很重,反复查视图一样慢。真要”存结果”得用别的手段(比如把结果写进一张普通表),SQLite 没有物化视图(materialized view)。
- 视图和 CTE(WITH 子句)不是一回事:CTE 是单条查询内部的临时命名结果,查询结束就消失;视图是持久化在数据库里的命名查询,任何连接随时能查。简单说,CTE 是一次性的,视图是长期存着的。
- 嵌套视图要克制:视图可以基于另一个视图创建,但层数太多会让执行计划变复杂、难调试,也更容易因基表变动而连环失效。
- 别用视图做写入口:再次强调,SQLite 视图只读,想通过视图写入请走 INSTEAD OF 触发器这条路,且只在确有需要时再上。
Warning常见坑:① 误以为”建了视图查询就快”——视图不带数据也不带索引,快慢全看基表。② 在视图上套视图、套很多层,出问题时很难定位是哪一层定义的 SQL 写了错。③ 把视图当表去 ALTER,SQLite 不支持
ALTER VIEW,要改视图只能先 DROP 再 CREATE。
六、SQLite 没有物化视图,如何曲线实现
有些场景你确实想要”视图的结果被真正存下来、查起来飞快”,比如每天凌晨算好的销售汇总。前面说过,视图每次被查询都会把底层那条 SELECT 重新跑一遍,它不存计算结果。SQLite 不支持物化视图(materialized view,即把查询结果物理落盘、按需刷新的机制),但有两种常见替代思路:
- 手动落地成普通表:把视图的查询结果
INSERT INTO 汇总表 SELECT ... FROM 视图名,或者写成一段脚本定期重算(先DELETE旧数据再重插)。好处是结果被固化、查起来快;代价是要自己维护刷新时机,数据不是实时的,可能落后几分钟甚至一天。 - 用表 + 触发器自动维护:如果你希望汇总表随基表变化自动更新,可以在基表上建触发器,在 INSERT/UPDATE/DELETE 时同步去更新那张汇总表。这相当于”自己实现了一个会自我维护的物化视图”。代价是写入时多了维护开销,且触发器逻辑要写对,否则汇总值会和真相漂移(第 35 章会讲触发器)。
Tip实用技巧:要不要搞”物化”取决于查询有多重、数据对实时性的要求有多高。轻量统计直接查视图即可;只有真的慢到影响体验、又允许有一定延迟时,才值得落地成表或用触发器维护。别一上来就给所有视图都做物化,那是过度设计。
类比小结:视图就像书店门口的”畅销榜海报”——它不存书,只是把仓库(基表)里按某种规则挑出来的书名列出来给你看;海报怎么换、看的人怎么查,都不改变仓库本身。想从海报直接往仓库塞书?SQLite 说不行,除非你配个专门的”代收发件员”(INSTEAD OF 触发器)。海报贴得太多太乱(嵌套视图),顾客反而找不到北。