图 1:本文封面,视图是一扇裁剪过的窗,不是一张新表。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「视图的作用,视图可以更改么」是两连问:第一问考你知不知道它解决什么问题,第二问考你清不清楚它的边界。两个问题都答实了,才算会用视图。
第一步,先认清本质:视图是一条存起来的 SELECT。
视图本身不存任何数据,它只是一条被命名保存的查询定义。每次查视图,数据库把定义实时展开到底表上执行——MySQL 默认尽量用 MERGE 算法把视图定义合并进外层查询,合并不了(比如含聚合)才退到 TEMPTABLE,先把视图结果物化成临时表再查,这也是带 TEMPTABLE 的视图一律不可更新的原因。这一个事实决定了后面所有结论:视图是逻辑层,不是性能层——它不缓存结果,查一次算一次;复杂视图套视图,要警惕层层展开后的执行计划。
第二步,两个真正值钱的作用。
查询简化:把多表连接加过滤条件封装成一个名字,调用方像查单表一样用。报表、看板类查询反复用到同一段连接逻辑时,视图把复杂度收敛到一个地方,改定义一处生效。
权限隔离:这是更常被低估的用途。给标注团队、分析团队这类外部角色授权时,直接授底表权限意味着所有列都暴露;授一个裁剪过的视图权限,敏感列天然不可见——列级裁剪不靠应用层自觉,由数据库强制。比如用户表里有手机号、身份证列,给客服系统一个只含工单所需字段的视图,客服账号连底表权限都没有,却能把该查的都查到。配合「视图定义者权限」的语义,还能让没有底表权限的账号透过视图读到允许的部分。
第三步,能不能改:看行能不能唯一映射回底表。
改视图本质是改底表,所以 MySQL 官方文档给出的总判据是:视图里的每一行必须与底表的行一一对应。具体地,视图包含以下任何一项就不可更新:聚合或窗口函数、DISTINCT、GROUP BY、HAVING、UNION、选择列表里的子查询、引用不可更新的视图、只引用字面量,以及 ALGORITHM = TEMPTABLE。多表视图可以更窄地支持:必须用内连接,且一次 UPDATE 只能改其中一张表的列。想 INSERT 还有附加条件——视图列不能有重名、必须包含底表所有无默认值的列、列必须是简单引用不能是表达式。服务器在建视图时就算好了可更新标记,可以直接查:
-- 查视图的可更新标记:YES 表示 UPDATE/DELETE 合法
SELECT TABLE_NAME, IS_UPDATABLE
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'your_db';
-- 可更新的典型:单表、无聚合、行与底表一一对应
CREATE VIEW v_active_users AS
SELECT id, name, city FROM users WHERE status = 'active';
-- 不可更新的典型:聚合让多行合并成一行,无法映射回底表
CREATE VIEW v_city_cnt AS
SELECT city, COUNT(*) AS cnt FROM users GROUP BY city;
第一个视图里改一行 name,就是改 users 表里那一行;第二个视图里 cnt 是若干行压缩出来的数字,「把 cnt 改成 5」根本没有对应的底表操作。
图 2:视图的本质、两个用途与可更新判定路径。
可能的追问与易错点。
「不可更新的视图想写怎么办?」两条路:直接写底表,或者把写逻辑封装成存储过程、视图只负责读。「WITH CHECK OPTION 是什么?」加在可更新视图上,要求对视图的插入和更新结果必须仍满足视图的 WHERE 条件——防止透过视图把行改到「窗外」去,属于可更新视图的配套保险。易错点三个:把视图当缓存用,指望它提速——要缓存结果得用物化方案,普通视图做不到;权限设计时只授底表,靠应用层过滤敏感列,一旦绕过应用直接连库就全裸;在嵌套视图上做复杂查询不看执行计划,展开后的连接顺序可能完全出乎意料。回到答题现场:先一句「视图是存起来的 SELECT,不存数据」立住本质,再讲简化与权限两个作用,然后用「行能否唯一映射回底表」串起可更新条件,最后提一句 IS_UPDATABLE 可查、CHECK OPTION 兜底。你团队的数据库权限是按表授的还是按视图授的?欢迎聊聊。