目录一、外模式的理论承诺让不同用户看到不同的数据库二、视图的定义与存储只存定义不存数据三、视图消解虚拟表的透明编译四、视图的两重价值逻辑独立性 安全控制五、视图的可更新性问题理论判据与SQL限制六、WITH CHECK OPTION更新一致性的守护约束七、结语虚拟却坚固的外模式一、外模式的理论承诺让不同用户看到不同的数据库在第1篇中我们讨论了数据库系统的三大核心目标之一——数据独立性。第2篇的三层映射体系将其具体化为逻辑独立性与物理独立性两个层次。但到目前为止我们主要在概念模式关系模式和内模式存储结构之间游走尚未系统性地讨论外模式。外模式是三级模式架构中直接面向用户的一层。它的设计动机来自一个朴素的工程需求同一套数据不同的用户应当看到不同的子集、不同的结构、甚至不同的名字。财务部门看到的“员工”表中应当隐去年薪字段之外的个人信息部门经理看到的“员工”表中应当只有本部门员工的行而外部审计师可能只能看到经过脱敏和汇总的统计结果——尽管底层存储的都是同一张员工基表。外模式对用户的承诺是你所看到的就是你的全部数据库。外模式之下隐藏了什么、基表如何连接、字段如何脱敏与用户无关。外模式的这一特性恰恰实现了逻辑数据独立性——当概念模式发生变化例如基表被纵向拆分为两个表只要外模式的定义随之更新以保持对用户接口不变用户的查询和应用逻辑就完全不受影响。在SQL中承担外模式职责的核心机制就是视图。一张视图是一个命名的SELECT查询它以虚拟表的形式呈现在用户面前。用户可以对视图执行SELECT查询就如同它是一张真实的基表——视图的“虚拟性”对用户完全透明。然而在系统内部对视图的查询必须被翻译为对基表的查询这一翻译过程就是视图消解。二、视图的定义与存储只存定义不存数据CREATE VIEW语句的语法简洁到近乎平淡sqlCREATE VIEW 员工薪资概览 AS SELECT 姓名, 部门名称, 工资 FROM 员工 JOIN 部门 ON 员工.部门编号 部门.部门编号 WHERE 工资 5000;这条语句执行后数据库中并没有出现一张新的物理表。系统只做了一件事将视图的名称“员工薪资概览”与AS之后的SELECT查询文本或其编译后的语法树绑定存储在数据字典中。数据字典是数据库管理系统的元数据仓库它记录着所有的模式信息——有哪些表、哪些列、哪些约束、哪些视图。由于视图不存储数据视图的创建是瞬间完成的不消耗表空间。同样视图所引用的基表发生数据变更时通过视图查询到的结果自动反映这些变更——因为每一次查询视图时系统都是重新执行其定义查询而非从某个缓存副本中读取过期数据。视图的这一“定义查询”本质决定了它不同于物化视图。物化视图是将视图的查询结果实际存储在磁盘上的特殊对象查询物化视图时直接读取存储结果而不重新执行定义查询。物化视图以存储空间和更新延迟为代价换取查询性能常用于数据仓库场景。但物化视图破坏了视图“零存储”的纯粹性且引入了结果一致性的维护问题基表更新后物化视图如何刷新超出了关系模型经典理论的讨论范围。本文集中于标准视图机制。三、视图消解虚拟表的透明编译当用户提交一条针对视图的查询时数据库系统面临一个选择是直接执行这条查询这是不可能的因为视图没有物理存储还是将它改写成对基表的查询系统选择后者——这一改写过程称为视图消解。视图消解的核心思想是将用户查询中引用视图名称的位置替换为视图的定义查询。这听起来简单但其中涉及查询结构的合并与条件融合需要精确的编译技术支持。视图消解的基本步骤如下第一步语法树解析。系统将用户查询解析为一棵语法树。同时系统从数据字典中取出视图的定义查询同样解析为语法树。第二步查询合并。系统将用户查询的语法树与视图定义查询的语法树进行合并。合并的核心逻辑是用户查询的FROM子句中引用视图名称的位置被整体替换为视图定义查询的表连接结构用户查询的WHERE条件与视图定义的WHERE条件进行合取AND连接用户查询的SELECT投影列表在视图定义的SELECT投影列表之上进行二次投影。以一个具体示例展示这一过程。假设视图定义如下sqlCREATE VIEW 高薪员工 AS SELECT 姓名, 工资, 部门编号 FROM 员工 WHERE 工资 8000;用户提交了针对该视图的查询sqlSELECT 姓名 FROM 高薪员工 WHERE 部门编号 D1;经过视图消解后等价于sqlSELECT 姓名 FROM 员工 WHERE 工资 8000 AND 部门编号 D1;这个消解过程的结果在语义上与用户的原查询完全等价但操作的对象已经从视图“高薪员工”转换为了基表“员工”。值得注意的是消解后的查询中WHERE条件工资 8000 AND 部门编号 D1是视图定义条件与用户条件的合取。优化器可以自由地对这些条件进行重排和优化——它可以选择先应用部门编号 D1如果该列上有索引且选择率更低再应用工资 8000而不必严格遵循定义的书写顺序。多层视图消解是实践中常见的复杂场景——一个视图可能基于另一个视图定义形成视图栈。例如视图A基于视图B视图B基于基表C。当用户查询视图A时系统需要递归消解两层视图定义最终将整个查询展开为仅对基表C的操作。理论上这种递归消解可以处理任意深度的视图嵌套但在工程实现中可能面临性能挑战——深层视图嵌套可能导致消解后的查询语句异常庞大复杂增加优化器的编译负担。四、视图的两重价值逻辑独立性 安全控制在理解了视图消解的机制之后我们可以更深刻地理解视图在数据库架构中的双重价值。第一重价值逻辑数据独立性。这是视图最根本的架构职能。假设在数据库的生命周期中由于性能或需求变化员工表被垂直拆分为两个表员工基本信息(工号, 姓名, 出生日期)和员工薪酬信息(工号, 工资, 奖金)。所有直接依赖原员工表的应用程序查询将全部失效。但如果应用层从一开始就只通过视图员工视图来访问员工数据那么只需修改该视图的定义——将原来的SELECT * FROM 员工改为两个新表的连接查询——所有上层应用的查询便无需任何修改即可继续工作。这种“隔离变化”的能力正是外模式的设计初衷。视图在概念模式与外部应用之间构筑了一道缓冲带概念模式的变动冲击被视图吸收不传导至应用层。第二重价值安全与权限控制。视图天然是一个权限过滤层。通过对同一张基表创建不同的视图可以向不同用户暴露不同的行集合和列集合。财务部可以访问包含薪资字段的视图行政部只能访问包含联系方式的视图两者都无法窥探对方领域的数据。结合DCL的GRANT/REVOKE机制参见第9篇可以精确控制哪些用户有权查询哪些视图而基表的直接访问权限可以被完全收回。通过视图实现的行级安全只展示某些行和列级安全只展示某些列远比在应用层实现安全过滤来得可靠——应用层的过滤代码可能被绕过数据库层的视图权限检查则是操作系统级别的硬约束。五、视图的可更新性问题理论判据与SQL限制视图的“虚拟表”幻象在查询操作中完美运行但在更新操作中却出现了裂隙。当用户试图对视图执行INSERT、UPDATE或DELETE操作时系统面临一个根本难题视图中的一行并不对应基表中的一行视图的列也不一定对应基表的列。将视图上的更新操作逆向翻译为基表上的更新操作在理论上并非总是可能。视图可更新性的形式化判据可以从关系代数的视角得到严谨的回答。一个视图是可更新的当且仅当系统能够从用户对视图的更新请求中无歧义地推导出对基表的对应更新操作。这种推导必须满足三个条件其一属性映射的唯一性。视图的每一列必须能无歧义地映射到某个基表的某一列。这意味着视图定义中不能包含聚集函数如AVG、SUM——这些值不是任何基表行的属性、不能包含表达式计算如工资*12——其逆运算不唯一系统不知道更新后的年薪应该通过修改工资还是修改月份数来实现、不能包含常量和字面量。如果视图的某列由工资*12 AS 年薪定义用户将年薪更新为120000时系统无法确定是将工资改为10000还是将隐含的月份数改为10。其二元组映射的确定性。用户意图更新的视图行必须能无歧义地映射到基表中的特定行。这要求视图定义中必须包含基表的主码或至少一个候选码——否则系统无法确定用户要更新基表中的哪一行。此外如果视图定义涉及多表连接系统必须能够唯一确定哪个基表是更新的目标表。SQL标准规定一次视图更新操作只能修改一个基表的数据。其三更新传导的无副作用性。视图更新不应导致违反基表的完整性约束。例如如果视图定义包含了WHERE条件工资 5000用户通过视图插入一行工资为3000的记录该行插入基表后却并不满足视图的WHERE条件——这意味着插入成功后该行对视图不可见。这种“插入成功但查询不可见”的怪异行为在理论上破坏了视图语义的一致性因此多数数据库系统拒绝这种更新。SQL标准的规定将这些理论判据落地为具体的语法限制。根据SQL标准一个视图在满足以下条件时是可更新的视图的定义查询不包含JOIN某些实现允许一对一的连接、不包含UNION/INTERSECT/EXCEPT、不包含GROUP BY或HAVING、不包含DISTINCT、不包含聚集函数、SELECT子句中的列均为直接的列引用非表达式、非常量且视图的行可以唯一映射到基表的行通常要求视图中包含基表的主码。在实践中满足这些限制的视图非常有限。对于复杂视图上的更新需求通常的做法不是强行让视图可更新而是通过INSTEAD OF触发器——在视图上定义触发器当用户对视图执行增删改操作时触发器中的自定义代码接管执行手动将更新传导至基表。INSTEAD OF触发器为开发者提供了完全的灵活度但也将正确性的责任从系统转移到了开发者身上。六、WITH CHECK OPTION更新一致性的守护约束在视图可更新的条件下存在一个容易被忽视的语义陷阱。考虑这个视图sqlCREATE VIEW 北京员工 AS SELECT * FROM 员工 WHERE 所在地 北京;如果用户通过该视图更新了一名员工的所在地将其改为上海那么更新成功后这名员工就不再满足视图的WHERE条件——他从“北京员工”视图中“消失”了。这听起来荒谬却完全合法UPDATE操作本身没有错误只是该行不再被视图覆盖。WITH CHECK OPTION正是为防止这种语义反常而设计的。如果在视图定义末尾加上WITH CHECK OPTIONsqlCREATE VIEW 北京员工 AS SELECT * FROM 员工 WHERE 所在地 北京 WITH CHECK OPTION;那么任何试图通过该视图将所在地改为非“北京”的UPDATE操作都将被拒绝任何试图插入所在地为非“北京”的INSERT操作也将被拒绝。系统在执行更新后会检查更新后的行是否仍然满足视图的WHERE条件不满足则回滚操作。WITH CHECK OPTION是视图作为外模式安全边界的一条重要防线——它确保通过视图修改的数据不会“逃逸”出视图的视野从而维护了视图语义的封闭性。七、结语虚拟却坚固的外模式视图是SQL中最具架构美感的设计之一。它用极简的语法一个命名查询承载了两项沉重的架构职能逻辑数据独立性的守护和安全访问控制的边界。视图不存储数据却让用户感觉在操作真实的数据表视图不物理隔离数据却能让不同用户安全地共享同一套基表。理解视图的关键在于始终区分定义时和使用时两个视角。定义时视图只是一个被保存的查询字符串。使用时视图消解算法将用户的查询与视图定义查询合并编译转化为对基表的操作。这种“使用时展开”的惰性求值策略使得视图永远反映基表的最新状态但也使得视图的更新操作面临理论上的严格限制——当用户试图逆向操作时“虚拟表”的幻觉触碰到了“真实数据”的硬边界。下一篇我们将从SQL语言的具体语法中抽身上升到数据库设计的全局视角——实体-联系模型如何被系统性地转化为关系模式以及在这一转化过程中设计者必须遵循的七条核心策略。