概述
Chapter 3 解决的是:
- 关系怎样用 SQL 建出来
- 基本查询怎样写
- 聚合、子查询、增删改怎样表达
Chapter 4 进一步解决:
- 多表连接怎样写得更清楚、更安全
- 数据类型和 domain 怎样更规范地表达业务含义
- 数据库怎样自己保证数据合法
- view 为什么是“虚拟表”,什么时候可以更新
- 索引为什么能加速查询
- 事务为什么要求 all-or-nothing
- 数据库权限应该如何管理
目录
- 概述
- 目录
- Joined Relations
- SQL Data Types and Schemas
- Integrity Constraints
- Views
- Indexes
- Transactions
- Authorization
Joined Relations
select name, course_idfrom instructor, teacheswhere instructor.ID = teaches.ID;连接要区分两个维度。 1. 连接条件怎么写
natural joinjoin ... using(...)join ... on <predicate>
2. 对匹配不上的元组怎么处理
inner joinleft outer joinright outer joinfull outer join
所以连接的核心其实是两个问题:
哪些行应该匹配在一起? 匹配不上的行要不要保留?
自然连接 natural join
natural join 会:
- 自动寻找两张表中所有同名属性
- 要求这些同名属性值相等
- 并且在结果中只保留一份公共列
例如:
select name, course_idfrom instructor natural join teaches;这里之所以合理,是因为:
instructor和teaches共同拥有ID- 而
ID恰好就是应该拿来连接的属性
所以这条查询会得到:
- 教师姓名
name - 他教授的课程号
course_id
natural join 的优点与风险
优点
- 写法短
- 对“共享列就是连接键”的情况很方便
风险
- 只要两张表里有同名列,
natural join就会自动把它们拿来相等比较 - 如果这些同名列在业务上并不该作为连接条件,就会出错
例如:
select name, titlefrom instructor natural join teaches natural join course;这条语句的风险在于:
instructor和course都有dept_namenatural join会自动要求它们相等- 但这里真正想用来连接
teaches和course的关键属性其实是course_id
所以自然连接虽然方便,但不能无脑使用。
join ... using(...)
如果你知道要按哪个同名属性连接,但又不想让其他同名属性偷偷参与连接,可以写:
select name, titlefrom (instructor natural join teaches) join course using (course_id);这里的含义是:
- 前半段
instructor natural join teaches仍然通过ID连接 - 后半段显式指定:只通过
course_id把课程表连进来
using (course_id) 的好处是:
- 比
natural join更安全 - 比
on instructor.course_id = course.course_id更简洁 - 结果中公共列
course_id只保留一份
join ... on
如果连接条件不是简单的“同名列相等”,或者你想完全显式地控制条件,就用 on:
select name, titlefrom instructor join teaches on instructor.ID = teaches.ID join course on teaches.course_id = course.course_id;这可以看作最通用、最清楚的写法。
你自己明确指定:
- 哪两张表连接
- 按什么条件连接
因此在真实开发里,join ... on 往往比 natural join 更稳妥。
外连接 outer join
普通的连接(inner join)只保留匹配成功的元组。
但有时我们不希望丢掉那些“没有匹配上”的数据,这时就需要外连接。
1. left outer join
保留左表中的所有元组:
- 匹配上的正常拼接
- 匹配不上的右表属性补成
null
2. right outer join
与 left outer join 相反,保留右表中的所有元组。
3. full outer join
左右两边的元组都尽量保留,匹配不上的部分补 null。
为什么需要外连接
假设:
- 有些课程没有 prerequisite
- 有些 prerequisite 信息记录得不完整
如果只用 inner join,这些“不匹配”的行会被直接丢掉。
而 outer join 的作用就是:
尽量保留信息,而不是因为没有匹配就让数据消失。
SQL Data Types and Schemas
用户自定义类型 create type
例如:
create type Dollars as numeric(12,2) final;然后建表时可以写:
create table department ( dept_name varchar(20), building varchar(15), budget Dollars);它的含义是:
Dollars本质上还是numeric(12,2)- 但我们给这个类型起了一个更有业务含义的名字
这样做的核心价值是:
- 提高可读性
- 强化语义表达
域 domain
相比 type,domain 更进一步。
它不只是“给类型起别名”,还可以附带约束。
例如:
create domain person_name char(20) not null;表示:
- 只要某列使用
person_name - 它就统一是
char(20) - 并且不能为空
再例如:
create domain degree_level varchar(10)constraint degree_level_testcheck (value in ('Bachelors', 'Masters', 'Doctorate'));这表示:
- 这个 domain 的值只能是三种学位等级之一
所以可以把 domain 理解成:
带规则的类型。
学号、工号、邮箱这类字段通常:
- 会在很多表里反复出现
- 格式规则相对固定
- 不希望每次建表都重复写一遍
varchar(...) + check(...)
例如学号就可以统一定义成:
create domain student_id varchar(10) not null;之后凡是表示“学号”的列,都直接写 student_id。
要注意的是:
- 共用同一个 domain 表示“取值规则一致”
- 但不代表自动形成外键关系
外键仍然要单独写。
大对象 blob / clob
数据库里有些数据不是小字符串或小数字,而是很大的对象,例如:
- 图片
- 音频
- 视频
- 很长的文本文档
这类数据可以用 large object 存储。
blob
blob = binary large object
适合存:
- 图片
- 音视频
- 任意二进制文件
数据库把它当作一串字节,不关心内部内容。
clob
clob = character large object
适合存:
- 很长的纯文本
- 文档正文
- 日志文本
所以:
blob偏二进制clob偏大文本
大对象查询时常常返回“引用”而不是直接返回全部内容
因为大对象可能非常大。
如果每查一行就把整张图片、整段视频都完整搬出来,代价会很高。
因此很多系统会:
- 先返回一个定位信息或引用
- 真正需要时再取具体内容
Integrity Constraints
完整性约束的目标是:
- 防止不合法数据进入数据库
- 防止合法操作把数据库改得不一致
not null 与 unique
not null
name varchar(20) not null,budget numeric(12,2) not null含义很直接:
- 这些列不能取
null
它适用于那些业务上“必须有值”的属性,例如:
- 姓名
- 预算
- 学号
unique
unique(email)表示:
- 该属性值不能重复
也可以是复合唯一性:
unique(course_id, sec_id, semester, year)表示这几个属性组合起来不能重复。
TIP
unique和primary key的区别二者都会保证唯一,但:
primary key
- 唯一
- 不能为
null- 用来作为这张表的主要标识
unique
- 唯一
- 通常允许
null(具体数据库实现细节可能不同)- 更像“备用的唯一性规则”
例如学生表里:
ID是 primary key
check (P)
check 用来表达:
这个属性或这条记录必须满足某个逻辑条件。
例如:
check (semester in ('Fall', 'Winter', 'Spring', 'Summer'))这表示:
- 学期字段只能取四个合法值之一
再比如:
check (salary > 29000)check (year > 1759 and year < 2100)这说明 check 特别适合表达:
- 范围限制
- 枚举值限制
- 同一行内部字段之间的逻辑关系
foreign key 与参照完整性
外键表达的是:
一张表中的某些值,必须引用另一张表中真实存在的值。
例如:
create table course ( course_id char(5) primary key, title varchar(20), dept_name varchar(20), foreign key (dept_name) references department(dept_name));意思是:
course.dept_name的取值必须能在department.dept_name中找到
这就是参照完整性。
它能防止出现这种脏数据:
- 课程表里写了一个根本不存在的院系名
on delete / on update
当被引用表中的记录被删除或修改时,引用它的表怎么处理?
常见策略有:
cascadeset nullset defaultrestrict
例如:
foreign key (dept_name) references department(dept_name) on delete set null on update cascade可以理解为:
cascade:跟着删 / 跟着改set null:把外键列改成nullset default:改成默认值restrict:不允许这样删除或修改
自引用外键
外键不一定引用别的表,也可以引用自己这张表。
例如:
create table person ( ID char(10), name char(40), mother char(10), father char(10), primary key (ID), foreign key (father) references person, foreign key (mother) references person);这里:
person.ID是主键father和mother也都是某个人的ID- 它们引用的仍然是
person表本身
这种结构很常见,例如:
- 员工表中的
manager_id - 评论表中的
reply_to - 树结构中的
parent_id
WARNING自引用外键带来的插入顺序问题
因为如果你先插入孩子:
father = 'F001'mother = 'M001'而
F001、M001还没插入表中,那么外键约束就会失败。常见解决办法有:
- 先插父母,再插孩子
- 先把
father、mother设为null,之后再更新- 把约束检查延迟到事务结束时统一检查
这说明完整性约束不仅影响“能不能这么写”,还影响“应该按什么顺序操作数据”。
复杂约束、assertion 与 trigger
前面几种约束都比较“局部”:
- 限制某一列
- 限制某一行
- 限制某张表对另一张表的引用
但现实中还有更复杂的约束,例如:
- 每个 section 至少要有一位老师教
- 学生的
tot_cred必须等于所有已通过课程学分之和
这种约束往往需要跨表检查。
带子查询的 check
理论上 SQL 标准里可以写:
check (time_slot_id in ( select time_slot_id from time_slot))或者:
check ((course_id, sec_id, semester, year) in ( select course_id, sec_id, semester, year from teaches))但实际主流数据库几乎都不支持在 check 中写子查询。
所以这部分更重要的是理解思想:
有些完整性约束是跨表的,普通
check不够表达。
assertion
SQL 标准提供了更强的机制:
create assertion <assertion-name>check <predicate>;它可以表达整个数据库范围内都必须成立的规则。
例如可以表达:
- 对每个学生,
tot_cred必须等于他所有通过课程的学分总和
这种约束比普通 check 更强,因为它不是“某列的规则”,而是“整个数据库的全局规则”。
但现实问题是:
- 大多数数据库系统并不真正支持
assertion
所以它更多是一个“理论上非常强的工具”。
trigger
既然:
- 带子查询的
check通常不支持 assertion也通常不支持
现实中常见替代方案就是 trigger(触发器)。
trigger 可以理解成:
当某个事件发生时,数据库自动执行一段预先定义好的动作。
典型形式是:
before insertafter updatebefore delete
trigger 常用来做:
- 复杂完整性检查
- 自动联动更新
- 自动修正输入值
所以:
- 约束偏声明式
- trigger 偏过程化
Views
什么是 view
view 可以理解成:
用一个查询结果“伪装”出来的一张表。
它看起来像表,但通常不是把数据重新存了一份,而是保存了查询定义。
因此 view 常被称为:
- virtual relation
- 虚拟表
view 有三个核心用途:
-
隐藏不想给某些用户看的数据
例如:
- 不让普通用户看 ,到教师工资
- 不让某些用户看到完整的个人信息
-
封装复杂查询
- 把一条经常要写的复杂 SQL 封装成一个名字,以后像查表一样去查它。
-
在逻辑结构改变时,给旧程序保留兼容接口
create view
基本形式:
create view v as <query expression>;例如:
create view faculty asselect ID, name, dept_namefrom instructor;这个 view 的含义是:
- 从
instructor表中取出三列 - 构成一个名为
faculty的虚拟表
以后就可以写:
select namefrom facultywhere dept_name = 'Biology';这里的 faculty 用起来就像一张普通表。
复杂条件 view
例如:
create view physics_fall_2009 asselect course.course_id, sec_id, building, room_numberfrom course, sectionwhere course.course_id = section.course_id and course.dept_name = 'Physics' and section.semester = 'Fall' and section.year = '2009';这个 view 不是来自单表,而是来自两张表:
coursesection
它本质上做的是:
- 把
course和section按course_id连接 - 只保留 Physics 系课程
- 只保留 2009 年 Fall 学期开设的 section
- 输出
course_id / sec_id / building / room_number
这个例子非常典型,因为它体现了 view 的另一大用途:
把一个经常要写的多表筛选查询封装成“一个名字”。
带聚合的 view
例如:
create view departments_total_salary(dept_name, total_salary) asselect dept_name, sum(salary)from instructorgroup by dept_name;这个 view 表示:
- 对每个系统计工资总和
这里显式写 (dept_name, total_salary) 是因为:
- 聚合表达式
sum(salary)默认没有一个很自然的列名 - 所以要手动给 view 的列命名
View Expansion
view expansion 的核心思想是:
查询中一旦出现 view 名,数据库会把它替换回原来的定义,再继续执行。
例如先定义:
create view physics_fall_2009_watson asselect course_id, room_numberfrom physics_fall_2009where building = 'Watson';这里的 physics_fall_2009 其实不是一张真实表,而是另一个 view。
数据库处理时会把它展开成原始定义,相当于:
select course_id, room_numberfrom ( select course.course_id, building, room_number from course, section where course.course_id = section.course_id and course.dept_name = 'Physics' and section.semester = 'Fall' and section.year = '2009')where building = 'Watson';进一步理解,它最后还是在查底层真实表:
coursesection
所以:
普通 view 存的通常不是“数据副本”,而是“查询定义”。
View 可以基于 View 再定义
上面的 physics_fall_2009_watson 就是一个典型例子:
physics_fall_2009基于真实表定义physics_fall_2009_watson再基于前一个 view 定义
这说明 view 的本质确实更像“命名查询”。
View 的更新问题
既然 view 用起来像表,那么能不能对 view 做:
insertupdatedelete
答案是:
有时可以,但很多时候不行。
例 1:简单 view 的插入问题
create view faculty asselect ID, name, dept_namefrom instructor;然后:
insert into facultyvalues ('30765', 'Green', 'Music');问题在于:
faculty只有三列- 但底层真实表
instructor还有salary
那么数据库到底该如何翻译这次插入?
一种可能是:
insert into instructorvalues ('30765', 'Green', 'Music', null);但这取决于:
salary是否允许为null- 数据库是否愿意自动这样补值
这说明:
更新 view,本质上必须能翻译成对底层真实表的更新。
例 2:多表 view 的更新更难
create view instructor_info asselect ID, name, buildingfrom instructor, departmentwhere instructor.dept_name = department.dept_name;如果你执行:
insert into instructor_infovalues ('69987', 'White', 'Taylor');数据库就会犯难:
- 这条记录到底该怎么拆到
instructor和department两张表里? - 如果
Taylor楼中有多个院系,那它到底属于哪个系?
所以多表连接得到的 view 往往没有唯一清晰的更新语义。
什么是 updatable view
大多数数据库系统只允许对 simple view 进行更新。
一般来说,一个 view 可更新,通常需要满足:
from子句中只有一张真实表select子句中只出现该表的属性名,不出现表达式- 没有
group by - 没有
having - 没有聚合函数
- 没有
distinct - 没出现在 view 中的底层列必须允许填
null
也就是说,只有当:
- view 和底层表行之间还有比较直接的对应关系
更新它才有可能被正确翻译回底层表。
with check option
再看一个典型 view:
create view history_instructors asselect *from instructorwhere dept_name = 'History';如果你通过这个 view 插入:
('25566', 'Brown', 'Biology', 100000)从底层表角度看,这条记录完全可以插入 instructor。
但问题是:
- 它不属于
History - 所以插进去后,它不会出现在
history_instructors这个 view 里
这就很奇怪:
- 你明明是通过这个 view 插入的
- 插完之后却在这个 view 中看不到它
为了解决这个问题,可以写:
create view history_instructors asselect *from instructorwhere dept_name = 'History'with check option;其含义是:
通过这个 view 进行插入或更新后,结果仍然必须满足这个 view 自己的筛选条件。
所以 with check option 可以理解为:
- 不允许你通过 view 引入“改完后不再属于该 view”的数据
Materialized View
普通 view 通常只保存定义,不保存结果。
而 materialized view 则不同,它会:
把定义该 view 的查询结果真的计算出来并存成一个物理表。
例如:
create materialized view departments_total_salary(dept_name, total_salary) asselect dept_name, sum(salary)from instructorgroup by dept_name;这表示:
- 先把“每个系工资总额”的结果实际存下来
之后如果再查询:
select dept_namefrom departments_total_salarywhere total_salary > ( select avg(total_salary) from departments_total_salary);就不需要每次都重新对 instructor 做 group by + sum。
普通 view vs materialized view
普通 view
- 存的是定义
- 查询时现算
- 总能反映底层表最新状态
- 但复杂查询可能慢
materialized view
- 存的是结果
- 查询时直接读结果
- 可能更快
- 但结果可能过期,需要维护
Materialized view 的维护问题
如果底层关系被更新:
- 新老师加入了
- 工资变了
- 旧老师被删除了
那么物化视图中的结果就可能变旧。
因此需要做 materialized view maintenance。
维护方式可能包括:
- 立即更新
- 查询时再刷新
- 周期性刷新
所以 materialized view 是一个典型的“性能换维护成本”的机制。
View 与 Logical Data Independence
这是 view 的一个更高层作用。
假设原来有一个关系:
S(a, b, c)后来为了更合理的设计,你把它拆成:
S1(a, b)S2(a, c)
这时如果旧程序原来都写:
select * from S where ...那它们岂不是全要改?
为了避免这种情况,可以重新定义一个 view:
create view S(a, b, c) asselect a, b, cfrom S1 natural join S2;这样:
- 底层逻辑结构已经变了
- 但外层程序仍然可以像以前一样使用
S
这就体现了 logical data independence(逻辑数据独立性):
修改逻辑模式时,尽量不影响用户看到的外部接口。
所以 view 不只是“方便查询”,还是数据库模式演化时的重要兼容层。
Indexes
索引的核心作用可以用一句话概括:
给数据库提供一个更快查找记录的目录结构。
例如:
create table student ( ID varchar(5), name varchar(20) not null, dept_name varchar(20), tot_cred numeric(3,0) default 0, primary key (ID));然后:
create index studentID_index on student(ID);TIP索引到底解决什么问题?
如果没有索引,执行:
select *from studentwhere ID = '12345';数据库可能需要:
- 把整张
student表一行一行看过去这叫全表扫描。
而有索引后,系统可以利用索引快速定位到:
ID = '12345'对应的记录位置就像书后面的目录:
- 不必逐页翻书
- 可以直接定位关键词对应页码
可以把索引理解成:
- 表数据是正文
- 索引是附加的查找目录
所以:
- 索引不是数据本身
- 它是加速访问的数据结构
索引的代价
索引能提升查询速度,但代价是:
- 额外占用存储空间
- 插入、删除、更新数据时,索引本身也要维护
因此不是每一列都适合建索引。
Transactions
Transaction 是数据库系统里最重要的机制之一。
Transaction就是:
一个必须整体成功或整体失败的工作单元。
典型例子是转账:
- A 账户减 100
- B 账户加 100
这两步不能只做一半。
如果:
- 第一步做了
- 第二步还没来得及做,系统崩了
数据库就会处于错误状态。
所以事务强调:
none or all
也就是:
- 要么全部完成
- 要么像从未发生过一样
commit 与 rollback
事务通常隐式开始,以两种方式之一结束:
commit
提交事务。
表示:
- 这次事务中的所有修改正式生效
rollback
回滚事务。
表示:
- 撤销这次事务中的所有修改
- 像它从来没有发生过一样
MySQL 中的一个典型例子
SET AUTOCOMMIT=0;UPDATE account SET balance = balance - 100 WHERE ano = '1001';UPDATE account SET balance = balance + 100 WHERE ano = '1002';COMMIT;这里:
- 先关闭自动提交
- 两条
UPDATE共同组成一次转账事务 - 最后统一
COMMIT
如果中途出错,就应该 ROLLBACK。
AUTOCOMMIT
- 每条 SQL 语句都会自动提交
这意味着:
- 你写一条
update - 它往往立刻就生效了
而Transaction的关键,恰恰是把多条语句捆成一个整体来提交或回滚。
ACID
事务的四个经典性质是 ACID。
1. Atomicity(原子性)
事务中的操作:
- 要么全部反映到数据库中
- 要么一个都不反映
这就是“all or nothing”。
2. Consistency(一致性)
如果事务本身逻辑正确,那么它执行前后,数据库都应保持一致。
例如:
- 主键仍然唯一
- 外键仍然合法
- 业务规则仍然成立
3. Isolation(隔离性)
多个事务可以并发执行,但每个事务都不该看到别的事务的中间状态。
也就是说:
- 虽然数据库里很多人同时在操作
- 但每个事务都应该像“自己单独运行”一样
4. Durability(持久性)
一旦事务成功 commit:
- 它对数据库的修改应永久保留下来
- 即使随后系统故障、断电,也不能丢
Transaction Boundaries
事务边界指的是:
哪些操作应该被视为同一个不可分割的工作单元。
这个问题很重要,因为事务并不是越大越好,也不是越小越好。
- 如果划得太小,原本必须一起成功的操作被拆开,就会出错
- 如果划得太大,会增加并发冲突,降低效率
所以事务边界的核心原则是:
把必须一起成功或失败的操作放进同一个事务。
Authorization
数据库不是所有用户都能随便访问所有数据的。
权限控制就是为了解决这个问题。
基本权限类型
对数据最常见的权限有:
selectinsertupdatedelete
对模式(schema)和对象管理的权限还有:
createalterdropindexcreate view
所以数据库权限大致分成两层:
数据级权限
- 能不能读、插、改、删数据
结构级权限
- 能不能创建或修改数据库对象
grant
SQL 用 grant 授予权限:
grant <privilege list>on <relation name or view name>to <user list>;例如:
grant select on instructor to U1, U2, U3;grant select on department to public;grant update (budget) on department to U1, U2;grant all privileges on department to U1;含义分别是:
- U1/U2/U3 可以查询
instructor - 所有用户都可以查询
department - U1/U2 只能更新
department表中的budget列 - U1 拥有
department上的全部权限
public
public 表示:
- 所有当前用户
- 以及未来新加入的用户
因此把权限授给 public,相当于全面开放。
revoke
SQL 用 revoke 收回权限:
revoke <privilege list>on <relation name or view name>from <user list>;例如:
revoke select on branch from U1, U2, U3;表示收回这些用户对 branch 的查询权限。
一个用户可能通过多条路径获得同一权限
这点非常重要。
如果:
- U1 把某权限授给了 U5
- U2 也把同一权限授给了 U5
那么即使后来撤销 U1 → U5 这条授权:
- U5 仍然可能保留该权限
- 因为他还可以通过 U2 那条路径继续获得它
所以权限系统并不是简单的一对一关系,而是一张传播网络。
Roles
role 可以理解成:
一组权限的打包名字。
例如:
create role instructor;grant instructor to Amit;grant select on takes to instructor;意思是:
- 创建一个角色
instructor - 这个角色拥有
select on takes的权限 - 再把这个角色授给 Amit
于是 Amit 就拥有了这个角色对应的权限。
TIP为什么要用 role,而不是直接把权限一条条授给每个人?
因为现实中很多用户其实扮演的是相同角色,例如:
- 学生
- 教师
- 助教
- 系主任
- 院长
如果每来一个新老师都手写几十条
grant,会非常麻烦。而角色的好处是:
- 先把权限绑定到角色上
- 再把角色分配给人
于是权限管理就更像现实世界中的“职位管理”。
role 可以继承
例如:
create role teaching_assistant;grant teaching_assistant to instructor;这表示:
instructor角色继承teaching_assistant的权限
再例如:
create role dean;grant instructor to dean;grant dean to Satoshi;说明:
dean包含instructor的权限- Satoshi 被授予
dean - 所以也拥有
instructor那部分权限
View 与权限控制
view 不只是查询工具,也是权限控制工具。
例如:
create view geo_instructor asselect *from instructorwhere dept_name = 'Geology';
grant select on geo_instructor to geo_staff;这表示:
- 先创建一个只包含地质系教师的 view
- 再只把这个 view 的查询权限授给
geo_staff
这样:
geo_staff并没有完整instructor表的访问权- 但他们可以看到自己被允许看到的那一部分数据
所以 view 在安全控制中的一个重要作用是:
把“整张表”裁剪成“可安全开放的窗口”。
references 权限
除了读写数据外,还有一种容易忽略的权限:
grant references (dept_name) on department to Mariano;这个权限的含义是:
- 允许 Mariano 引用
department.dept_name - 例如拿它作为别的表中的外键参照目标
也就是说:
select是“我能看你的数据”references是“我能把我的表结构建立在你的列之上”
它属于一种“结构依赖级”的权限。
with grant option
默认情况下,一个用户即使得到了某项权限,也不能继续把它转授给别人。
如果你写:
grant select on department to Amit with grant option;表示:
- Amit 自己可以
select department - 并且 Amit 还可以把这个权限继续授给别人
这就让权限可以继续传播。
权限传播图
一旦允许转授,就会形成:
- DBA → U1
- U1 → U4
- U2 → U5
- …
这样的一张授权图。
一个用户是否拥有某个权限,不只是看“有没有人给过他”,而要看:
从根授权者到他之间是否还存在一条有效路径。
cascade 与 restrict
当你收回权限时,就会遇到一个问题:
如果被收回者又把这个权限授给了别人,该怎么办?
cascade
revoke select on department from Amit cascade;表示:
- 不只收回 Amit 的权限
- 还要把那些“依赖 Amit 这条路径”获得该权限的人也一起收回
这叫级联撤销。
restrict
revoke select on department from Amit restrict;表示:
- 如果这次撤销会连带影响别人
- 那数据库就拒绝执行这次撤销
所以:
cascade:允许连锁回收restrict:一旦会波及别人,就直接禁止这次回收
只撤销转授权,不撤销实际权限
还可以写:
revoke grant option for select on department from Amit;它表示:
- Amit 仍然保留
select department的权限 - 但 Amit 以后不能再继续把这个权限授给别人
这个语义非常细,但很重要。