数据库课程笔记2-SQL与数据库保护

風船
22
2025-10-08

第三章 关系数据库标准语言SQL

概述

SQL的特点

  1. 综合统一:
    SQL 将多种数据库操作语言的功能集于一身,使用统一的语法完成不同任务。

    • 数据查询语言 (DQL): SELECT - 用于检索数据。

    • 数据定义语言 (DDL): CREATE, DROP, ALTER - 用于定义和管理数据库对象(如表、视图、索引)。

    • 数据操纵语言 (DML): INSERT, UPDATE, DELETE - 用于修改表中的数据。

    • 数据控制语言 (DCL): GRANT, REVOKE - 用于用户授权和访问控制。

  2. 高度非过程化:
    用户只需指明“做什么”,即需要检索或操作什么样的数据,底层的数据库管理系统会负责解析SQL语句,并自动优化生成最高效的执行计划。

  3. 面向集合的操作方式:
    SQL 的操作对象和返回结果都是元组的集合

  4. 以同一种语法结构提供两种使用方式:
    即交互式和嵌入式。

  5. 语言简捷,易学易用(?)

重要基本概念

  1. 基本表 vs. 导出表

    • 基本表:

      • 实际存在的表,在数据库中对应独立的存储文件,是数据存储的基础。

    • 导出表:

      • 是从基本表中派生出来的表,是一个逻辑概念。有两种主要的导出表:

      • ① 视图 (View):

        • 这是一个虚拟表。数据库中只存储视图的定义(即一条 SELECT 语句),而不存储视图的数据

        • 当查询视图时,DBMS会将其定义与用户的查询合并,最终转化为对基本表的操作。

      • ② 快照 (Snapshot):

        • (在许多现代数据库中也称为物化视图 Materialized View

        • 与视图不同,快照是将查询结果物理存储起来的一个副本,并会定期从基本表刷新数据。主要用于提高复杂查询的性能。

  2. SQL与三级模式结构

    • 外模式 → 视图: 为不同用户提供定制化的数据视图。

    • 模式 → 基本表: 定义了数据库的全局逻辑结构。

    • 内模式 → 存储文件: 定义数据的物理存储,对用户透明。

SQL数据查询

示例用表说明:

  • S (S#, SN, SA, SD): 学生表 (学号, 姓名, 年龄, 所在系)

  • C (C#, CN, PC#): 课程表 (课程号, 课程名, 先修课号)

  • SC (S#, C#, G): 选课表 (学号, 课程号, 成绩)

基本查询结构:SELECT-FROM-WHERE

SELECT [目标列名]
FROM   [基本表 或 视图]
WHERE  [检索条件];
  • 执行逻辑:

    1. FROM: 首先确定要查询的数据源表。

    2. WHERE: 按照“检索条件”对表中的行(元组)进行水平方向的筛选

    3. SELECT: 从筛选后的结果中,挑选出“目标列”,进行垂直方向的投影。(与投影运算不同,默认允许重复行!!!)

  • 结果: 一个查询块的执行结果仍然是一个

单表查询

1. 投影查询

  • 目的: 选择表中的特定列。

  • 语法: SELECT后跟列名,省略WHERE子句。

  • 去重: 使用 DISTINCT 关键字可以消除结果中的重复行。

-- 例1: 检索所有学生的姓名和年龄
SELECT SN, SA FROM S;
​
-- 例2: 检索所有被选修的课程的课程号(不重复)
SELECT DISTINCT C# FROM SC;

2. 选择查询

  • 目的: 根据条件筛选表中的行。

  • 语法: 使用 WHERE 子句指定条件。

  • 常用运算符:

    • 比较: =, <>, !=, >, <, >=, <=

    • 逻辑: AND, OR, NOT

    • 范围: BETWEEN ... AND ... (闭区间)

    • 集合: IN, NOT IN (详见嵌套查询)

    • 模糊匹配: LIKE (详见其他技巧)

    • 空值判断: IS NULL, IS NOT NULL

-- 例1: 检索选修了'C2'课程的学生的学号和成绩
SELECT S#, G FROM SC WHERE C# = 'C2';
​
-- 例2: 检索选修'C1'或'C2'且成绩大于70分的记录
SELECT S#, C#, G FROM SC WHERE (C#='C1' OR C#='C2') AND G >= 70;
​
-- 例3: 检索成绩在70到85分之间的记录
SELECT S#, C#, G FROM SC WHERE G BETWEEN 70 AND 85;

3. 结果排序

  • 目的: 将查询结果按指定列进行排序。

  • 语法: 使用 ORDER BY <列名> [排序方式] 子句,它必须是查询块的最后一个子句

  • [排序方式] 是一个可选部分:

    • ASC: 升序 (缺省默认值)

    • DESC: 降序

  • 支持多列排序。

-- 检索全体学生信息,按系号升序,同系学生按年龄降序
SELECT * FROM S ORDER BY SD ASC, SA DESC;

连接(连表)查询

  • 目的: 同时查询多个表,实现多表数据的联合检索。

  • 核心:WHERE 子句中指定连接条件(通常是主键=外键)。

1. 内连接

  • FROM 后列出所有要连接的表,WHERE 中写明连接条件和筛选条件。

-- 检索学生张华所学课程的成绩
-- 连接条件: S.S# = SC.S#
-- 筛选条件: SN = '张华'
SELECT SN, C#, G
FROM S, SC
WHERE S.S# = SC.S# AND SN = '张华';
-- 注意: 当不同表有同名列时,必须用"表名.列名"来区分。

2. 自身连接

  • 目的: 将一个表与它自身进行连接,常用于比较同一表中不同行的数据。

  • 实现:FROM 子句中为同一个表定义两个别名 (Alias),将其视为两个独立的表。

-- 检索所有比李勇年龄大的学生姓名和年龄
SELECT X.SN, X.SA
FROM S X, S Y
WHERE X.SA > Y.SA AND Y.SN = '李勇';

3. 外连接

问题: 内连接只会返回在两个表中都能找到匹配的记录。但如果我们想看一个表中的所有记录,以及它在另一个表中的匹配情况(即使没有匹配项),该怎么办?

例如: 我们想列出所有学生的选课成绩,这个名单里必须包括那些一门课都没选的学生。

解决方案: 使用外连接 (Outer Join)

外连接通过在 WHERE 子句的连接条件中使用一个特殊的操作符 (*或+) 来实现。这个操作符的作用是,指定哪一个表是允许出现 NULL 值的可选表

  • 语法规则:(*) 放在连接条件中某个表的列名一侧。另一个不带 (*) 的表就是主表主表的所有记录都将被保留。

示例:列出所有学生的选课情况,包括未选课的学生

假设我们的表数据如下:

学生表 S

S#

SN

s1

李勇

s2

刘晨

s4

张立

选课表 SC

S#

C#

G

s1

C1

92

s1

C2

85

s2

C2

90

SQL查询语句

SELECT S.S#, S.SN, SC.C#, SC.G
FROM S, SC
WHERE S.S# = SC.S#(*);
  • WHERE S.S# = SC.S#(*):

    • (*) 被放在了 SC 表的 S# 列旁边。

    • 这表示 S 表是主表,其所有行都会被包含在结果中。

    • SC 表是可选表。如果某个学生在 SC 表中找不到匹配的选课记录,那么 SC 表的列将被 NULL 填充。

查询结果

S#

SN

C#

G

s1

李勇

C1

92

s1

李勇

C2

85

s2

刘晨

C2

90

s4

张立

NULL

NULL

补充: 实际上(*或+)是一种较久的语法。现在更常见的做法是在 FROM 子句中使用 JOIN 关键字。例如,LEFT JOIN 可以达到完全相同的效果,它明确指定了保留左侧表(S)的所有记录:

SELECT S.S#, S.SN, SC.C#, SC.G
FROM S LEFT JOIN SC ON S.S# = SC.S#;

嵌套查询

  • 概念: 在一个查询块的WHEREFROMHAVING子句中嵌入另一个查询块。外层的叫主查询(或外部查询),内层的叫子查询(或嵌套查询)

  • 分类:

    • 不相关子查询: 子查询可独立执行,其结果被主查询使用。

    • 相关子查询: 子查询的执行依赖于主查询的当前行,主查询每处理一行,子查询就要重新执行一次

1. 带有比较运算符的子查询

  • 当子查询返回单个值时,可直接用 =, >, < 等比较。

  • 当子查询返回多个值时,必须配合 ANYALL 使用。

    • > ANY: 大于子查询结果中的任意一个值(即大于最小值)。

    • > ALL: 大于子查询结果中的所有值(即大于最大值)。

-- 检索与李勇同岁的学生 (子查询返回单值)
SELECT SN FROM S WHERE S.SA = (SELECT SA FROM S WHERE SN='李勇');
​
-- 检索C2课程成绩最高的学生学号 (子查询返回多值,这里用ALL)
SELECT S# FROM SC WHERE C#='C2' AND G >= ALL (SELECT G FROM SC WHERE C#='C2');

2. 带有 IN 的子查询

  • value IN (subquery): 判断 value 是否在子查询的结果集中。

  • IN 等价于 = ANY

  • 否定形式为 NOT IN ,等价于 != ANY

-- 检索选修了C2课程的学生姓名
SELECT SN FROM S WHERE S# IN (SELECT S# FROM SC WHERE C#='C2');

3. 带有 EXISTS 的子查询

  • EXISTS (subquery): 判断子查询的结果集是否为。如果不为空,则返回 TRUE;否则返回 FALSE

  • EXISTS 通常用于相关子查询,效率较高。

-- 检索选修了C2课程的学生姓名 (用EXISTS实现)
SELECT SN
FROM S
WHERE EXISTS (SELECT * FROM SC WHERE S#=S.S# AND C#='C2');
-- 解释: 对于S表的每一行,检查SC表中是否存在对应的C2选课记录。

4. 使用 NOT EXISTS 实现全称量词 (逻辑除法)

  • 由于 SQL 中不存在直接的全称量词,需要用 NOT EXISTS 来表达全称量词。

  • 转换逻辑: ∀(x)P ⇔ ¬(∃x ¬P(x))

-- 检索选修了所有课程的学生姓名
-- 逻辑: "不存在一门课程,该学生没有选修"
SELECT SN
FROM S
WHERE NOT EXISTS ( -- 不存在这样的课程 C
    SELECT *
    FROM C
    WHERE NOT EXISTS ( -- 这门课 C,该学生 S 没有选修
        SELECT *
        FROM SC
        WHERE S#=S.S# AND C#=C.C#
    )
);
使用 NOT EXISTS 实现蕴涵

与实现全称量词同理。

集合查询

  • 目的: 将两个或多个 SELECT 语句的结果集合并。

  • 要求: 参与操作的查询结果必须具有相同数量的列,且对应列的数据类型相容。

  • 操作符:

    • UNION: 并集 (自动去重)。

    • UNION ALL: 并集 (保留所有重复行)。

    • INTERSECT: 交集。

    • MINUS (或标准SQL中的 EXCEPT): 差集。

-- 检索选修了C1课程或C2课程的学生学号
(SELECT S# FROM SC WHERE C#='C1')
UNION
(SELECT S# FROM SC WHERE C#='C2');

-- 检索无人选修的课程号和名称。
SELECT C#, CN
FROM C
WHERE C# IN (
    -- 用全部课程的集合 减去 已被选修课程的集合 得到 无人选修的课程的集合
    
    -- 获取所有课程号的集合
    SELECT C# FROM C
    
    MINUS
    
    -- 获取所有被选修过的课程号的集合
    SELECT DISTINCT C# FROM SC
);

聚合函数(库函数)与分组查询

1. 聚合函数

  • 对一组值进行计算,返回单个值。只能在 SELECTHAVING 子句中出现。

  • 常用函数:

    • COUNT(*): 统计行数。

    • COUNT([DISTINCT] column): 统计某列的非空值个数(带 DISTINCT 则统计不重复非空值个数)。

    • SUM(column): 对列求和。

    • AVG(column): 对列求平均值。

    • MAX(column): 对列求最大值。

    • MIN(column): 对列求最小值。

-- 检索学生总人数
SELECT COUNT(*) FROM S;
​
-- 检索选修课程的学生总人数(去重)
SELECT COUNT(DISTINCT S#) FROM SC;
​
-- 求C1课程的最高分
SELECT MAX(G) FROM SC WHERE C#='C1';

2. 分组查询 GROUP BY

  • 目的: 将表中具有相同值的行分到一组,然后对每一组使用聚合函数。

  • SELECT子句中只能包含分组列聚合函数

3. 分组过滤 HAVING

  • 目的:GROUP BY 产生的分组结果进行筛选。

  • WHERE的区别:

    • WHERE 子句作用于分组前的原始数据行。

    • HAVING 子句作用于分组后的组。

  • 执行顺序: WHEREGROUP BYHAVINGSELECT 注意SELECT在最后执行!!!

-- 检索至少选修了3门课程的学生学号和选课门数
SELECT S#, COUNT(*)
FROM SC
GROUP BY S#
HAVING COUNT(*) >= 3;
​
-- 求选修四门以上且成绩及格的课程的学生学号和总成绩
SELECT S#, SUM(G)
FROM SC
WHERE G >= 60        -- 1. 先筛选出所有及格的记录
GROUP BY S#          -- 2. 按学号分组
HAVING COUNT(*) >= 4 -- 3. 筛选出选课数>=4的组
ORDER BY SUM(G) DESC;-- 4. 按总成绩排序

其他查询技巧

1. 算术表达式

  • SELECT子句中可以使用 +, -, *, / 等运算符进行计算,并使用 AS 定义别名( AS 可忽略)。

-- 假设有职工表EMP(EMPN, SALARY, BONUS),查询年总收入
SELECT EMPN, 12 * (SALARY + BONUS) AS TOTAL_INCOME FROM EMP;

2. 模糊查询 LIKE

  • 用于字符串的部分匹配。

  • 通配符:

    • %: 代表0个或多个任意字符。

    • _: 代表单个任意字符。

-- 检索所有姓"刘"的学生
SELECT S#, SN FROM S WHERE SN LIKE '刘%';

3. 派生表查询

  • 目的: 将子查询的结果作为一张临时的表,放在 FROM 子句中供主查询使用。

  • 要求: 派生表必须指定一个别名(与 SELECT 字句一致)

-- 检索每个学生超过自己平均成绩的课程号
SELECT T1.S#, T1.C#
FROM SC AS T1, (SELECT S#, AVG(G) AS avg_g FROM SC GROUP BY S#) AS T2
WHERE T1.S# = T2.S# AND T1.G > T2.avg_g;

-- FROM SC AS T1, (SELECT S#, AVG(G) AS avg_g FROM SC GROUP BY S#) AS T2
-- 也可写作 FROM SC AS T1, (SELECT S#, AVG(G) FROM SC GROUP BY S#) AS T2(S#, AVG(G))

注:当子查询内存在库函数时,必须为其指定列名,如 AVG(G) AS avg_gAS T2(S#, AVG(G)) 。若不存在,可不指定列名,此时自动继承子查询 SELECT 中的列名。

补充说明
  1. HAVING 子句中使用子查询的一个例子:
    “找出那些平均成绩高于所有学生的总平均成绩的系别。”

    这个问题可以拆解为两步:

    1. 计算出每个系的平均成绩。

    2. 计算出所有学生的总平均成绩(这动态基准)。

    3. 比较第1步的结果和第2步的结果,筛选出符合条件的系。

    HAVING 子句里的子查询就是用来完成第2步的。

    分析:

    1. 分组对象: 学生。我们需要按学号(S#)分组。

    2. 分组聚合值: 每个学生的平均成绩 AVG(G)。

    3. 过滤条件: AVG(G) 必须大于 (全校总平均成绩)。

    4. 动态基准: “全校总平均成绩”需要通过一个子查询 (SELECT AVG(G) FROM SC) 来获得。

    SQL 实现:

    SELECT S#, AVG(G)
    FROM SC
    GROUP BY S# -- 1. 按学号分组,形成每个学生的小组
    HAVING AVG(G) > ( -- 2. 对每个小组进行过滤
        -- 这是一个不相关子查询,它只执行一次
        -- 计算出整个SC表的总平均分,作为比较基准
        SELECT AVG(G) FROM SC 
    );

SQL数据定义

SQL的DDL部分用于定义和管理数据库的逻辑结构,包括创建、修改和删除数据库中的各种对象。

主要操作对象:

  • 基本表 (Table): 存储数据的核心结构。

  • 视图 (View): 基于基本表的虚拟表。

  • 索引 (Index): 用于加速数据检索的对象。

操作对象

创建 (Create)

删除 (Drop)

修改 (Alter)

表 (Table)

CREATE TABLE

DROP TABLE

ALTER TABLE

视图 (View)

CREATE VIEW

DROP VIEW

(通常通过重建)

索引 (Index)

CREATE INDEX

DROP INDEX

(通常通过重建)

定义、修改和删除基本表

创建基本表 (CREATE TABLE)

  • 基本语法:

     CREATE TABLE <表名> (
         <列名1> <数据类型> [<列级完整性约束>],
         <列名2> <数据类型> [<列级完整性约束>],
         ...
         [<表级完整性约束>]
     );
  • 常用数据类型 (SQL92标准):

    • CHAR(n): 固定长度字符串。

    • VARCHAR(n): 可变长度字符串,n 代表最大长度。

    • INT, SMALLINT: 整数。

    • NUMERIC(p, q): 精确数值,总共p位,小数点后q位。

    • REAL, DOUBLE PRECISION: 浮点数与双精度浮点数。

    • DATE: 日期 (年-月-日)。

    • TIME: 时间 (时:分:秒)。

    • INTERVAL: 两个 DATETIME 类型数据之间的差。

  • 完整性约束:

    • NOT NULL: 该列不能为空值。(缺省默认为 NULL ,即允许空值)

    • UNIQUE: 该列的值必须唯一。

    • PRIMARY KEY: 主键约束 (隐含 NOT NULLUNIQUE)。

    • FOREIGN KEY: 外键约束,用于建立表与表之间的引用关系。

    • CHECK: 检查约束,定义列值必须满足的条件。

  • 示例:创建 S 表和 SC 表

     -- 创建学生表 S
     CREATE TABLE S (
         S#    CHAR(5)    NOT NULL UNIQUE,
         SN    CHAR(20)   NOT NULL,
         SA    INT,
         SD    CHAR(15),
         PRIMARY KEY(S#), -- 表级主键约束
         CHECK (SA >= 18 AND SA <= 45) -- 表级检查约束
     );
     ​
     -- 创建选课表 SC
     CREATE TABLE SC (
         S#    CHAR(5)    NOT NULL,
         C#    CHAR(5)    NOT NULL,
         G     NUMERIC(4, 2), -- 成绩,总共4位,2位小数
         PRIMARY KEY (S#, C#), -- 复合主键
         FOREIGN KEY (S#) REFERENCES S(S#), -- 外键,引用S表的S#
         FOREIGN KEY (C#) REFERENCES C(C#)  -- 外键,引用C表的C#
     );

修改基本表 (ALTER TABLE)

  • 用于对已存在的表结构进行修改。

  • 基本语法:

     ALTER TABLE <表名>
     [ ADD <新列名> <数据类型> [完整性约束] ]  -- 增加新列
     [ MODIFY <列名> <新数据类型> ]           -- 修改列的数据类型
     [ DROP <完整性约束名> ]                 -- 删除约束
     [ DROP COLUMN <列名> ];                 -- 删除列 (标准SQL)
  • 示例:

     -- 1. 为S表增加“入学时间”列
     ALTER TABLE S ADD Scome DATE;
     ​
     -- 2. 将S表中SA列的数据类型改为SMALLINT
     ALTER TABLE S MODIFY SA SMALLINT;

删除基本表 (DROP TABLE)

  • 删除表的定义及其所有数据、索引等。此操作不可逆,需谨慎。

  • 语法:

     DROP TABLE <表名>;
  • 示例: DROP TABLE S;

定义和删除视图

创建视图 (CREATE VIEW)

  • 视图是基于一个或多个基本表的查询结果定义的虚拟表。

  • 基本语法:

     CREATE VIEW <视图名> [ (<列名1>, <列名2>, ...) ]
     AS
     <子查询>
     [WITH CHECK OPTION];
    • WITH CHECK OPTION: 对视图进行 INSERTUPDATE 操作时,确保修改后的数据仍然满足视图定义中的 WHERE 条件。

  • 示例:创建计算机系学生视图

     CREATE VIEW CS_Student (Sno, Sname, Sage)
     AS
     SELECT S#, SN, SA
     FROM S
     WHERE SD = 'CS'
     WITH CHECK OPTION;
    • 这个视图只包含计算机系学生的学号、姓名和年龄。

    • WITH CHECK OPTION 保证了你不能将一个CS系学生的系别改成非CS,也不能向该视图插入一个非CS系的学生。

删除视图 (DROP VIEW)

  • 删除视图的定义,不影响基本表的数据。

  • 语法: DROP VIEW <视图名>;

  • 示例: DROP VIEW CS_Student;

定义和删除索引

  • 作用: 索引是独立于表的数据库对象,主要目的是加快查询速度。它以空间换时间,会占用额外的存储空间。

  • DBMS会自动为主键和唯一键创建索引。

创建索引 (CREATE INDEX)

  • 语法:

     CREATE [UNIQUE] [CLUSTER] INDEX <索引名>
     ON <表名> (<列名1> [ASC|DESC], <列名2> [ASC|DESC], ...);
    • UNIQUE: 创建唯一索引,保证索引列的值是唯一的。

    • CLUSTER: 创建聚簇索引,表的物理存储顺序将与索引顺序一致。

  • 示例:为SC表的学号和课程号创建唯一索引

    CREATE UNIQUE INDEX Scno
    ON SC (S# ASC, C# DESC);

删除索引 (DROP INDEX)

  • 语法: DROP INDEX <索引名>;

  • 示例: DROP INDEX Scno;

补充内容
  1. 完整性约束中,哪些是列级的,哪些是表级的?

    总的原则是:

    • 如果一个约束只涉及单个列,那么它既可以定义为列级约束,也可以定义为表级约束。

    • 如果一个约束涉及多个列(例如复合主键或复合外键),那么它必须定义为表级约束。

    约束类型

    能否用作列级约束?

    能否用作表级约束?

    备注

    NOT NULL

    (只能)

    NOT NULL 是唯一一个只能在列定义后面声明的约束。

    UNIQUE

    单列 UNIQUE 两种方式都可以。多列 UNIQUE 必须是表级。

    PRIMARY KEY

    单列主键两种方式都可以。复合主键必须是表级。

    FOREIGN KEY

    单列外键两种方式都可以。复合外键必须是表级。

    CHECK

    只涉及单列的 CHECK 两种方式都可以。涉及多列的 CHECK 必须是表级。


SQL视图操作

视图的作用

  1. 能够简化用户操作:

    • 将复杂的、多表连接的查询封装成一个简单的视图。用户只需查询这个视图,就像查询一张单表一样,无需关心底层复杂的表结构和连接逻辑。

  2. 使用户能够以多种角度看待同一数据:

    • 可以为不同的用户或应用场景创建不同的视图,每个视图只展示他们关心的数据子集或经过处理的数据格式,满足个性化需求。

  3. 提供了一定程度的逻辑独立性:

    • 当底层基本表的结构发生变化时(例如增加列),只要视图的定义不受影响,那么基于该视图的应用程序就无需修改。

  4. 能够对数据提供安全保护:

    • 通过视图,可以向用户隐藏某些敏感的列或行。例如,可以创建一个视图,只包含员工的姓名和部门,而不包含薪资列,然后只将该视图的查询权限授予特定用户。

视图查询与视图消解

查询视图

  • 对视图的查询操作与对基本表的查询操作完全相同。你可以像使用 SELECT ... FROM <表名> 一样使用 SELECT ... FROM <视图名>

视图消解 (View Resolution)

  • 这是DBMS处理视图查询的核心机制。当用户查询一个视图时,DBMS并不会先生成一个完整的临时表。

  • 处理过程如下:

    1. DBMS从数据字典中找出该视图的定义(即创建视图时使用的 AS <子查询> 部分)。

    2. 将用户的查询语句与视图的定义合并、转换,重写成一个等价的、直接针对底层基本表的查询语句。

    3. 执行这个被重写和修正后的查询。

  • 这个转换过程就称为“视图消解”。 对用户来说,这个过程是透明的。

  • 示例:

     -- 1. 创建一个计算机系学生的视图
     CREATE VIEW CS_Student AS
     SELECT S#, SN, SA FROM S
     WHERE SD = 'CS';
     ​
     -- 2. 用户在视图上进行查询
     SELECT SN, SA FROM CS_Student WHERE SA < 20;
     ​
     -- 3. DBMS进行视图消解,将其转换为对基本表S的查询
     SELECT S.SN, S.SA 
     FROM S 
     WHERE S.SD = 'CS' AND S.SA < 20;
    • 可以看到,视图定义中的WHERE条件和用户查询的WHERE条件被合并到了一起。

SQL数据更新

SQL的数据更新功能用于对表中的数据进行插入、修改和删除操作。

操作类型

对应SQL语句

插入数据 (Insert)

INSERT 语句

修改数据 (Update)

UPDATE 语句

删除数据 (Delete)

DELETE 语句

插入数据 (INSERT)

INSERT 语句用于向表中添加新的行(元组)。

插入单个元组

  • 语法:

     INSERT INTO <表名> [ (<列名1>, <列名2>, ...) ]
     VALUES (<值1>, <值2>, ...);
  • 注意事项:

    • VALUES 子句中值的顺序和数据类型必须与 (<列名...>) 列表中的列一一对应。

    • 如果省略 (<列名...>) 列表,则 VALUES 必须为表中的所有列按其在表中定义的顺序提供值。

  • 示例:

     -- 向学生表 S 中插入一条完整的记录
     INSERT INTO S (S#, SN, SA, SD)
     VALUES ('S10', '陈冬', 18, 'IS');
     ​
     -- 如果VALUES提供了所有列的值,可以省略列名列表
     INSERT INTO S 
     VALUES ('S11', '王明', 19, 'CS'); 

插入子查询结果

  • 此方法可以一次性将一个查询的结果集(多行数据)插入到另一个表中。

  • 语法:

     INSERT INTO <表名> [ (<列名1>, <列名2>, ...) ]
     <子查询>;
  • 注意事项:

    • 子查询 SELECT 列表中的列数、顺序和数据类型必须与 INSERT INTO 的列列表匹配。

  • 示例:

     -- 假设有一个表 Dept_Age(Sdept, Avgage) 用于存储每个系的平均年龄
     -- 现在将计算出的各系平均年龄插入该表
     INSERT INTO Dept_Age (Sdept, Avgage)
     SELECT SD, AVG(SA) 
     FROM S 
     GROUP BY SD;

修改数据 (UPDATE)

UPDATE 语句用于修改表中已存在行的数据。

  • 语法:

     UPDATE <表名>
     SET <列名1> = <表达式1>, <列名2> = <表达式2>, ...
     [WHERE <条件>];
  • 核心组成:

    • SET 子句:指定要修改哪些列,以及它们的新值。

    • WHERE 子句:非常重要! 它指定了要修改哪些行。如果省略 WHERE 子句,表中所有行的指定列都将被更新,这通常是危险操作。

  • 示例:

     -- 1. 将学生 S1 的年龄改为 22 岁
     UPDATE S
     SET SA = 22
     WHERE S# = 'S1';
     ​
     -- 2. 将所有学生的年龄增加 1 岁
     UPDATE S
     SET SA = SA + 1; -- 省略WHERE,将更新所有行
     ​
     -- 3. 将计算机系所有学生的年龄改为 20 岁
     UPDATE S
     SET SA = 20
     WHERE SD = 'CS';

删除数据 (DELETE)

DELETE 语句用于从表中删除一行或多行数据。

  • 语法:

     DELETE FROM <表名>
     [WHERE <条件>];
  • 核心组成:

    • WHERE 子句:同样非常重要! 它指定了要删除哪些行。如果省略 WHERE 子句,表中所有行都将被删除,表会变空,但表结构依然存在。

  • 示例:

     -- 1. 删除学号为 S19 的学生记录
     DELETE FROM S 
     WHERE S# = 'S19';
     ​
     -- 2. 删除所有选课记录
     DELETE FROM SC; -- 省略WHERE,将删除SC表中的所有数据
     ​
     -- 3. 删除计算机系所有学生的选课记录 (使用子查询)
     DELETE FROM SC
     WHERE 'CS' = (SELECT SD FROM S WHERE S.S# = SC.S#);
     -- 更常见的写法是使用 IN
     DELETE FROM SC
     WHERE S# IN (SELECT S# FROM S WHERE SD = 'CS');
补充内容

对视图的更新操作(INSERT, UPDATE, DELETE)最终也会被转换为对底层基本表的更新。但是,并非所有视图都是可更新的

可更新视图的基本条件:

  • 视图必须是基于单个基本表定义的。

  • 视图的 SELECT 列表中必须包含基本表的所有主键(或所有具有 NOT NULL 约束的列)。

  • 视图的定义中不能包含 GROUP BYDISTINCT、聚合函数或集合运算符(UNION, INTERSECT 等)。

  • 视图的定义中不能包含复杂的表达式或计算字段。

WITH CHECK OPTION 的作用:

  • 如果在创建视图时指定了 WITH CHECK OPTION,那么对视图进行的 UPDATEINSERT 操作会受到检查。

  • 检查内容: 确保更新或插入后的行仍然满足视图定义中的 WHERE 条件。

  • 示例: 对于前面创建的 CS_Student 视图(WHERE SD = 'CS' WITH CHECK OPTION):

    • 不能执行 UPDATE CS_Student SET SD = 'IS' WHERE ...,因为 'IS' 不满足 SD = 'CS'

    • 不能执行 INSERT INTO CS_Student (..., SD) VALUES (..., 'MA'),因为 'MA' 也不满足条件。

    • 这个选项能有效防止通过视图“非法”地修改或插入不符合视图定义的数据。

SQL数据控制

SQL的数据控制功能(DCL)主要负责确保数据的一致性安全性。它包括:

  • 定义完整性约束

  • 事务管理

权限控制

即授予(GRANT)或收回(REVOKE)用户对数据库对象的特定操作权限。

1. 授权 (GRANT)

  • 作用: 将对特定数据库对象的操作权限授予一个或多个用户。

  • 语法:

     GRANT <权限列表> 
     ON <对象类型> <对象名>
     TO <用户列表>
     [WITH GRANT OPTION];
  • 核心元素:

    • <权限列表>:SELECT, INSERT, UPDATE, DELETE, ALL PRIVILEGES (所有权限) 等。

    • <对象类型> <对象名>:TABLE S, VIEW CS_Student

    • <用户列表>: 用户名,或 PUBLIC (所有用户)。

    • WITH GRANT OPTION: 允许获得该权限的用户将同样的权限再授予给其他用户。

  • 示例:

     -- 将对学生表 S 的查询权限授予用户 U1
     GRANT SELECT ON TABLE S TO U1;
     ​
     -- 将对选课表 SC 的所有操作权限授予用户 U2 和 U3,并允许他们再授权
     GRANT ALL PRIVILEGES ON TABLE SC TO U2, U3 WITH GRANT OPTION;

2. 收回权限 (REVOKE)

  • 作用: 从一个或多个用户那里收回已经授予的权限。

  • 语法:

     REVOKE <权限列表>
     ON <对象类型> <对象名>
     FROM <用户列表>;
  • 注意事项: 如果权限是通过 WITH GRANT OPTION 级联授予的,收回权限时可能会产生连锁反应,级联收回由该用户授予出去的权限。

  • 示例:

     -- 从用户 U1 手中收回对表 S 的查询权限
     REVOKE SELECT ON TABLE S FROM U1;
     ​
     -- 从用户 U2 和 U3 手中收回对表 SC 的所有权限
     REVOKE ALL PRIVILEGES ON TABLE SC FROM U2, U3;
补充内容
  • 事务 (Transaction): 是一系列数据库操作的逻辑工作单元。这个单元中的所有操作要么全部成功执行,要么在任何一步出错后全部回滚到初始状态,就好像什么都没发生过一样。

  • 事务的ACID特性:

    • 原子性 (Atomicity): 事务是不可分割的最小工作单元。

    • 一致性 (Consistency): 事务执行前后,数据库从一个一致性状态转移到另一个一致性状态。

    • 隔离性 (Isolation): 并发执行的多个事务之间互不干扰。

    • 持久性 (Durability): 一旦事务提交,其结果就是永久性的。

  • SQL中的事务控制语句:

    • COMMIT: 提交事务。将事务中所有已执行但尚未写入磁盘的操作永久保存下来,并结束当前事务。

    • ROLLBACK: 回滚事务。撤销当前事务中所有未提交的操作,使数据库恢复到事务开始前的状态,并结束当前事务。

  • 示例(银行转账):

     -- 开始一个事务 (很多数据库系统中,事务是自动开始的)
     START TRANSACTION; -- (或者 BEGIN TRANSACTION)
     ​
     -- 1. 张三账户减去100元
     UPDATE Account SET balance = balance - 100 WHERE name = '张三';
     ​
     -- 2. 李四账户增加100元
     UPDATE Account SET balance = balance + 100 WHERE name = '李四';
     ​
     -- 假设在第二步之后没有发生错误
     COMMIT; -- 提交事务,两步操作永久生效
     ​
     -- 如果在任何一步发生错误(比如李四账户不存在),则应执行:
     -- ROLLBACK; -- 回滚事务,张三账户减去的100元将被撤销

空值的处理

空值(NULL)的概念

  • 定义: NULL 是一个特殊的值,用来表示“缺失的未知信息”。它不等于 0,也不等于空字符串 ''

  • 含义: NULL 可以代表多种情况:

    • “不知道” (Unknown): 例如,一个新生的年龄尚未登记。

    • “不存在” (Non-existent) / “不适用” (Inapplicable): 例如,一个单身员工的配偶姓名。

    • “无意义” (Meaningless): 例如,某个字段的特定值没有业务含义。

  • 约束: 定义了 PRIMARY KEY, NOT NULL, 或 UNIQUE 约束的列不能包含 NULL 值。

空值的运算

NULL 的核心特点是其不确定性,这种不确定性会“传染”到所有与它相关的运算中。

算术运算

  • 规则: 任何值(包括另一个 NULL)与 NULL 进行算术运算(+, -, *, / 等),结果永远是 NULL

  • 示例:

    • 5 + NULLNULL

    • 10 * NULLNULL

    • NULL / 2NULL

    • NULL + NULLNULL

比较运算与三值逻辑

  • 问题: 传统逻辑只有“真”(TRUE)和“假”(FALSE)两种状态。但 NULL 的不确定性引入了第三种状态。

  • 三值逻辑 (Three-Valued Logic): 在SQL中,逻辑判断的结果可以是以下三种之一:

    • TRUE (真)

    • FALSE (假)

    • UNKNOWN (未知)

  • 规则:

    • 任何值(包括另一个 NULL)与 NULL 进行比较运算(=, >, <, <> 等),结果永远是 UNKNOWN

    • 特别注意:NULL = NULL 的结果也是 UNKNOWN,而不是 TRUE!因为两个未知的值不一定相等。

  • 示例:

    • SA = NULLUNKNOWN (即使 SA 列的值真的是 NULL )

    • 10 > NULLUNKNOWN

    • NULL <> NULLUNKNOWN

逻辑运算 (AND, OR, NOT)

三值逻辑下的布尔运算规则如下表所示(U 代表 UNKNOWN):

表达式

结果

助记方法

TRUE AND UNKNOWN

UNKNOWN

FALSE AND UNKNOWN

FALSE

FALSE 短路了 AND 表达式,无论未知是什么。

UNKNOWN AND UNKNOWN

UNKNOWN

TRUE OR UNKNOWN

TRUE

TRUE 短路了 OR 表达式,无论未知是什么。

FALSE OR UNKNOWN

UNKNOWN

UNKNOWN OR UNKNOWN

UNKNOWN

NOT UNKNOWN

UNKNOWN

对一个未知的值取反,结果依然是未知。

在查询中处理空值

如何判断空值 (IS NULL)

  • 由于 column = NULL 的结果是 UNKNOWN,所以不能用 = 来判断一个值是否为空。

  • 正确语法:

    • IS NULL: 判断一个值是否为 NULL

    • IS NOT NULL: 判断一个值是否不为 NULL

  • 示例:

     -- 检索所有没有登记所在系的学生
     SELECT * FROM S WHERE SD IS NULL;
     ​
     -- 检索所有已经登记了所在系的学生
     SELECT * FROM S WHERE SD IS NOT NULL;

WHEREHAVING 子句对 UNKNOWN 的处理

  • WHEREHAVING 子句的筛选过程中,只有条件判断结果为 TRUE 的行(或分组)才会被保留并输出

  • 如果条件判断结果为 FALSEUNKNOWN,相应的行(或分组)都将被丢弃

  • 包含 NULL 值的行在很多普通的比较查询中会被悄无声息地过滤掉,可能会导致意外的结果。

  • 示例: 假设我们要查询成绩不及格(<60)或成绩优秀(>=90)的选课记录。

     SELECT * FROM SC WHERE G < 60 OR G >= 90;

    如果某条选课记录的成绩 GNULL(例如,学生缺考),那么:

    1. G < 60 的判断结果是 UNKNOWN

    2. G >= 90 的判断结果也是 UNKNOWN

    3. UNKNOWN OR UNKNOWN 的结果是 UNKNOWN

    4. 因为 WHERE 子句的最终结果不是 TRUE,所以这条成绩为 NULL 的记录将不会被查询出来

嵌入式SQL

嵌入式SQL的意义

  • 问题背景:

    • SQL 是一种功能强大的非过程化、面向集合的查询语言,擅长数据管理和复杂查询。

    • 高级语言 (如 C, C++, Java) 是过程化的,拥有强大的流程控制(循环、判断)、计算和业务逻辑处理能力。

    • 两者各有优势,但单独使用时都有局限。例如,SQL无法实现复杂的界面交互或业务流程,而高级语言直接操作文件来管理数据则非常复杂且低效。

  • 解决方案: 将两者结合起来,取长补短。

    • 嵌入式SQL: 就是将SQL语句嵌入 到高级语言(称为宿主语言)的程序代码中。

    • 目的: 利用宿主语言的过程化能力来处理业务逻辑,同时利用SQL的强大功能来高效地访问和操纵数据库。这使得开发复杂的数据库应用程序成为可能。

嵌入式SQL的处理方式:预编译

由于宿主语言的编译器不认识SQL语法,因此嵌入式SQL程序不能直接编译。DBMS通常采用预编译 的方法来处理。

  • 处理流程:

    1. 预编译器:

      • 源程序(包含宿主语言代码和嵌入式SQL语句)首先被送入DBMS的预编译器。

      • 预编译器会扫描代码,找出所有的嵌入式SQL语句

      • 将这些SQL语句转换翻译成宿主语言能够识别的函数调用API调用

      • 同时,保留原始的宿主语言代码。

    2. 标准编译器:

      • 经过预编译后,源程序变成了一个纯粹的宿主语言程序(不含任何SQL语法)。

      • 这个纯净的程序可以被宿主语言的标准编译器(如 gcc for C)进行编译和连接,最终生成可执行文件。

嵌入式SQL的核心机制与语法

1. 区分SQL语句

  • 为了让预编译器能识别出SQL语句,所有嵌入式SQL语句都必须加上一个前缀

  • 语法: EXEC SQL <SQL语句>;

2. 宿主语言与数据库之间的数据交换:主变量

  • 问题: 如何将宿主语言程序中的变量值传递给SQL语句(例如作为查询条件),以及如何将SQL查询的结果存入宿主语言的变量中?

  • 解决方案: 使用主变量

    • 定义: 在宿主语言中定义的,可以被嵌入式SQL语句使用的变量。

    • 声明: 主变量必须在SQL的声明段中显式声明。

       EXEC SQL BEGIN DECLARE SECTION;
         int var_c1;
         char var_c2[21];
       EXEC SQL END DECLARE SECTION;
    • 使用: 在SQL语句中引用主变量时,必须在变量名前加上冒号 (:) 作为前缀,以区别于数据库中的列名。

       EXEC SQL FETCH foo_bar INTO :var_c1, :var_c2;

3. 处理单行与多行结果集:游标

  • 问题: SQL是面向集合的,一条 SELECT 语句可能返回多行结果;而宿主语言通常是“一次一记录”的处理模式。这种不匹配如何协调?

  • 解决方案: 使用游标 (Cursor)

    • 概念: 游标可以看作是一个指向 SELECT 语句查询结果集中的一个指针缓冲区。它允许程序逐行地从结果集中获取数据。

    • 使用步骤:

      1. DECLARE CURSOR (声明游标): 将一个游标与一条 SELECT 语句绑定起来。

         EXEC SQL DECLARE foo_bar CURSOR FOR SELECT c1, c2 FROM tbl;
      2. OPEN CURSOR (打开游标): 执行与游标绑定的 SELECT 语句,此时查询结果集被确定,游标指向第一行之前。

         // 1. DECLARE CURSOR (声明游标): 将一个游标与一条 SELECT 语句绑定起来。
         EXEC SQL DECLARE foo_bar CURSOR FOR SELECT c1, c2 FROM tbl;
         // 2. OPEN CURSOR (打开游标): 执行与游标绑定的 SELECT 语句,此时查询结果集被确定,游标指向第一行之前。
         EXEC SQL OPEN foo_bar;
      3. FETCH CURSOR (提取数据): 将游标指向的当前行的数据取出,并存入到主变量中。然后游标自动下移一行。通常在一个循环中执行此操作,直到取完所有数据。

         EXEC SQL FETCH foo_bar INTO :var_c1, :var_c2;
      4. CLOSE CURSOR (关闭游标): 释放游标占用的资源。

         EXEC SQL CLOSE foo_bar;

动态SQL

  • 静态SQL: 前面讨论的嵌入式SQL,其SQL语句的结构(如表名、列名)在编译时就是固定的。

  • 动态SQL: 允许程序在运行时根据用户输入或其他条件临时构建并执行SQL语句字符串。这提供了更大的灵活性,但也更复杂,并需要注意防止SQL注入等安全问题。

补充说明

什么是SQL注入?

SQL注入是一种网络安全漏洞。攻击者通过在应用的输入字段(如登录框、搜索框)中“注入”恶意的SQL代码片段,欺骗应用程序的数据库执行非预期的、恶意的命令。

简单来说,就是利用程序漏洞,让原本用于查询数据的SQL语句,变成了执行攻击者命令的工具。

为什么会存在安全问题?(根本原因)

这个漏洞的根本原因在于,应用程序在构建动态SQL语句时,将用户输入的数据(Data)和SQL命令(Code)不加区分地拼接在了一起

这给了攻击者一个机会,让他们输入的数据被数据库误解为可执行的命令。


一个经典的登录示例

让我们来看一个最经典的例子:一个网站的登录验证。

1. 开发者预期的正常流程

  • 前端界面: 有一个用户名输入框和密码输入框。

  • 后端代码(存在漏洞的写法):

     // 伪代码
     String userName = request.getParameter("user");
     String passWord = request.getParameter("pass");
     ​
     // **危险操作:直接将用户输入拼接到SQL字符串中**
     String query = "SELECT * FROM users WHERE username = '" + userName + "' AND password = '" + passWord + "';";
     ​
     // 执行这个拼接好的SQL查询
     database.executeQuery(query);
  • 正常用户输入:

    • 用户名: Alice

    • 密码: Password123

  • 最终执行的SQL语句:

     SELECT * FROM users WHERE username = 'Alice' AND password = 'Password123';

    这个查询完全符合预期,如果用户名和密码正确,用户就能成功登录。

2. 攻击者的恶意注入

现在,一个攻击者来到了这个登录页面。他并不知道任何人的密码。

  • 攻击者输入:

    • 用户名: admin' --

    • 密码: (随便输入什么,比如 abc)

  • 后端代码进行字符串拼接后,最终生成的SQL语句变成了:

     SELECT * FROM users WHERE username = 'admin' --' AND password = 'abc';

3. 为什么这个SQL语句是致命的?

在SQL语法中,-- (两个减号后面跟一个空格) 是单行注释符。它的作用是告诉数据库,-- 之后直到行尾的所有内容都被视为注释,直接忽略,不要执行

所以,数据库实际执行的SQL命令是:

 SELECT * FROM users WHERE username = 'admin'

发生了什么?

  1. username = 'admin' 这个条件为真(假设存在admin用户)。

  2. '--' 注释掉了后面所有的内容,包括 AND password = 'abc'; 这个验证密码的关键部分

  3. 查询成功返回了 admin 用户的所有信息。

  4. 应用程序判断查询成功,攻击者在不知道密码的情况下,成功以 admin 的身份登录了系统!

SQL注入的危害远不止于此

这只是最简单的“绕过登录”。真正的危害可以更严重:

  1. 窃取数据: 攻击者可以注入 UNION 查询,将其他表(如 credit_cards 表)的敏感数据拼接在查询结果中,从而盗取整个数据库的信息。

  2. 篡改数据: 攻击者可以注入 UPDATEINSERT 语句,修改数据库中的数据。

  3. 删除数据: 注入 DELETEDROP TABLE 语句,可以删除数据甚至整个数据表,造成毁灭性破坏。

  4. 获取系统权限: 在某些配置不当的数据库上,甚至可以执行操作系统命令,完全控制服务器。

如何防御SQL注入?

核心原则:永远不要信任用户的输入,并且要将代码和数据严格分离。

最有效的防御方法是使用 参数化查询 (Parameterized Queries)预编译语句 (Prepared Statements)

  • 安全的代码写法:

     // 伪代码
     String userName = request.getParameter("user");
     String passWord = request.getParameter("pass");
     ​
     // 1. SQL模板:使用占位符 `?` 代替用户输入
     String query = "SELECT * FROM users WHERE username = ? AND password = ?;";
     ​
     // 2. 预编译SQL模板
     PreparedStatement stmt = database.prepareStatement(query);
     ​
     // 3. 将用户输入作为“参数”安全地绑定到占位符上
     stmt.setString(1, userName); // 绑定第一个 '?'
     stmt.setString(2, passWord); // 绑定第二个 '?'
     ​
     // 4. 执行
     stmt.executeQuery();
  • 为什么这样是安全的? 在这种模式下,数据库首先接收并编译SQL命令的结构/模板 (SELECT ... WHERE username = ? ...)。然后,再接收用户输入的数据。数据库从一开始就知道 ? 位置上填入的只会是数据,而绝不是可执行的SQL代码

    当攻击者输入 admin' -- 时,数据库会把它完整地当作一个字符串,去数据库里寻找一个用户名恰好就叫 admin' -- 的用户。它不会-- 解释为注释符。因为找不到这样的用户,查询会失败,攻击也就被阻止了。

第四章 数据库保护

核心问题:

  1. 数据库面临哪些安全威胁?如何进行保护?

  2. 如何保证数据库中数据的正确、有效和一致性?

本章内容围绕以上两个核心问题展开,主要分为两大模块:数据库安全性控制数据库完整性控制

数据库安全性控制

1. 数据库安全性的含义

  • 定义:保护数据库,防止因不合法的使用而导致的数据泄露、篡改和破坏

  • 两个层面

    1. 授权用户:提供可靠的信息服务。

    2. 非授权用户:拒绝其存取请求,保证数据的可用性、完整性和一致性,保护数据所有者和使用者的合法权益。

2. 数据库安全性控制技术

一个完整的计算机系统安全模型包含多个层次的保护,从外到内依次是:

  • 用户/应用系统 -> DBMS -> 操作系统/网络 -> 数据库(DB)

对应的安全控制技术包括:

  • 用户标识与认证

  • 存取控制

  • 审计

  • 数据加密

  • 视图机制

  • 推理控制、隐通道分析等

2.1 用户标识与鉴别

  • 作用:系统提供的最外层安全保护措施。

  • 标识:系统用一种方式(如用户名)来标记每个用户或应用程序。

  • 鉴别/认证:系统在用户登录时,判断其是否为合法的授权用户。

  • 常用方法用户名 + 密码

2.2 存取控制

  • 目标:确保合法用户在 指定的权限范围 内使用DBMS和访问数据。

  • 机制组成

    1. 用户权限定义:将用户的权限(谁、能对什么对象、做什么操作)记录到数据字典中,形成授权规则。

    2. 合法权限检查:当用户发起操作时,DBMS依据数据字典中的授权规则进行检查,决定接受或拒绝该操作。

  • 这两个部分共同构成了DBMS的 安全子系统

  • 存取控制方法分类

    1. 自主存取控制 (DAC - Discretionary Access Control)

      • 特点:用户可以自主地将自己拥有的权限 转授 给其他用户。权限控制灵活。

      • 核心:权限由 (数据对象, 操作类型) 构成。

      • 授权 (Authorization):定义用户存取权限的过程,需指明:用户名、数据对象名、允许的操作类型

    2. 强制存取控制 (MAC - Mandatory Access Control)

      • 特点:系统强制执行安全策略,用户无法自主修改。安全性更高。

      • 核心概念

        • 主体:系统中的活动实体,如用户、进程。

        • 客体:系统中的被动实体,如表、文件、视图。

        • 敏感度标记 (Label)

          • 主体的标记称为 许可证级别

          • 客体的标记称为 密级 (如:绝密 > 机密 > 秘密 > 公开)。

      • 存取规则

        • 读权限:仅当 主体的许可证级别客体的密级 时,主体才能读取客体(向下读,向上不读)。

        • 写权限:仅当 主体的许可证级别 == 客体的密级 时,主体才能写入客体(平级写)。

2.3 基于角色的存取控制 (RBAC)

  • 定义:角色 (Role) 是一组相关权限的集合。

  • 工作方式:将权限授予角色,再将角色授予用户。

  • 优点:大大简化了权限管理。

2.4 SQL中的数据安全性控制

SQL通过 GRANTREVOKE 语句实现自主存取控制。

  • 权限授予 (GRANT)

    • 语法GRANT <权限列表> ON <对象名> TO <用户/角色/PUBLIC> [WITH GRANT OPTION];

    • WITH GRANT OPTION:允许接收权限的用户将该权限再次授予其他用户。

    • 示例:

      • 授予用户SCOTT连接数据库的权限:GRANT Create Session to SCOTT;

      • 授予用户Liming对Student表的查询和更新权限,并允许他再授权:

         GRANT Select, Update ON Student TO Liming WITH GRANT OPTION;
      • 将Student表的所有权限授予所有用户:

         GRANT ALL ON Student TO PUBLIC;
  • 权限收回 (REVOKE)

    • 语法REVOKE <权限列表> ON <对象名> FROM <用户/角色/PUBLIC>;

    • 特点:如果被收回权限的用户曾将此权限授予了其他用户,这些级联授予的权限也会被 一并收回

    • 示例:

      • 取消用户SCOTT创建表的权限:REVOKE Create Table FROM SCOTT;

      • 收回用户Liming对Student表的所有权限:

         REVOKE ALL ON Student FROM Liming;

2.5 其他安全性控制方法

  • 视图 (View) 机制

    • 原理:为不同用户定义不同的视图,将用户对数据的访问限制在视图定义的范围内(特定行、特定列)。

    • 示例:限制用户Wangping只能查询计算机系学生的信息。

       CREATE VIEW CS_Student AS
       SELECT Sno, Sname FROM Student WHERE Sdept = 'CS';
       ​
       GRANT SELECT ON CS_Student TO Wangping;
  • 审计

    • 原理:将用户对数据库的所有操作自动记录到审计日志中。DBA可以利用日志追溯事件,发现非法操作。

  • 数据加密

    • 原理:通过加密算法将原始数据(明文)变为不可识别的格式(密文),保护数据在存储和传输过程中的安全。

3. 可信计算机系统评测标准 (TCSEC)

  • TCSEC (橙皮书):1985年美国国防部制定,用于评估计算机系统的安全等级。

  • TDI:TCSEC对数据库管理系统的解释和扩展。

  • 安全级别 (从低到高)

    • D:最低保护。

    • C1:自主存取控制 (DAC)。

    • C2:比C1更强的DAC,并包含审计功能。

    • B1:标记安全保护,开始引入强制存取控制 (MAC)。

    • B2:结构化保护,有形式化的安全模型。

    • B3:安全域,安全内核。

    • A1:可验证设计,最高级别。

数据库完整性控制

1. 数据完整性的含义

  • 定义:指数据的 正确性相容性

    • 正确性:数据符合现实世界的语义,如类型、格式、取值范围正确。

    • 相容性:同一对象的不同数据之间逻辑自洽,没有矛盾。

  • 与安全性的区别

    • 安全性:防止 非法用户非法操作 对数据库的恶意破坏。

    • 完整性:防止 合法用户 通过 合法操作 向数据库中添加不符合语义的(错误)数据。

2. 完整性约束条件

  • 定义:施加在数据库数据之上的语义约束,是数据库系统进行完整性检查的依据。

  • 作用对象:列、元组(行)、关系(表)。

  • 关系模型中的三类完整性

    1. 实体完整性

      • 规则:主键 (Primary Key) 的值必须唯一且不能为空 (NOT NULL)。

      • SQL支持PRIMARY KEY 约束。

    2. 参照完整性

      • 规则:外键 (Foreign Key) 的值要么为空,要么必须等于其参照关系中某个元组的主键值。

      • SQL支持FOREIGN KEY 约束。

    3. 用户自定义完整性

      • 针对具体应用场景定义的约束。

      • SQL支持NOT NULLUNIQUECHECK 约束。

3. 完整性控制机制

DBMS的完整性控制应具备三个功能:

  1. 定义功能:提供定义完整性约束条件的机制。

  2. 检查功能:在用户执行INSERTUPDATEDELETE等操作时,检查是否违反了已定义的约束。

  3. 违约响应:如果操作违反了约束,则采取相应措施,如 拒绝 (REJECT) 执行操作、级联 (CASCADE) 执行等。

级联 (CASCADE) 执行

级联 (CASCADE) 执行” 是一种设置在 外键约束 上的规则。它定义了当被引用的“父表”中的记录发生 DELETE (删除) 或 UPDATE (更新) 操作时,与之关联的“子表”中的记录应该如何 自动地 做出响应。

简单来说,它的核心思想是:“父记录”怎么了,“子记录”就跟着怎么做。 就像多米诺骨牌一样,推倒第一张,后面的会跟着倒下。

我们通过一个经典的例子来详细解释:部门表 (Departments)员工表 (Employees)

  • 父表 (Parent Table): Departments

    • dept_id (部门ID, 主键)

    • dept_name (部门名称)

  • 子表 (Child Table): Employees

    • emp_id (员工ID, 主键)

    • emp_name (员工姓名)

    • works_in (所在部门, 外键, 引用 Departments.dept_id)

这里的 Employees.works_in 依赖于 Departments.dept_id。如果 Departments 表中的一个部门被删除或其ID被修改,Employees 表中属于该部门的员工记录就会变得“无家可归”,破坏了参照完整性。CASCADE 就是解决这个问题的一种策略。

CASCADE 主要用在两个场景下:

1. ON DELETE CASCADE (级联删除)

这是最常见也最需要小心使用的场景。

  • 规则:如果在 Departments 表中删除了一条记录(比如,删除了“IT”部门),那么在 Employees 表中所有 works_in 字段值为“IT”的员工记录也 将自动被删除

  • SQL 语法示例

     CREATE TABLE Employees (
         emp_id INT PRIMARY KEY,
         emp_name VARCHAR(50),
         works_in INT,
         FOREIGN KEY (works_in) REFERENCES Departments(dept_id)
         ON DELETE CASCADE  -- 在这里定义级联删除规则
     );
  • 执行效果: 当你执行 DELETE FROM Departments WHERE dept_id = 101; (假设101是“IT”部门),数据库会自动执行类似 DELETE FROM Employees WHERE works_in = 101; 的操作,将该部门的所有员工一并清除。

2. ON UPDATE CASCADE (级联更新)

  • 规则:如果在 Departments 表中更新了一个主键值(虽然不常见,但可能发生),那么在 Employees 表中所有引用了旧主键值的外键 将自动更新为新值

  • SQL 语法示例

     CREATE TABLE Employees (
         emp_id INT PRIMARY KEY,
         emp_name VARCHAR(50),
         works_in INT,
         FOREIGN KEY (works_in) REFERENCES Departments(dept_id)
         ON UPDATE CASCADE  -- 在这里定义级联更新规则
     );
  • 执行效果: 如果你执行 UPDATE Departments SET dept_id = 201 WHERE dept_id = 101; (将“IT”部门的ID从101改为201),数据库会自动执行类似 UPDATE Employees SET works_in = 201 WHERE works_in = 101; 的操作,确保员工的所属部门信息保持同步。

除了 CASCADE,还有哪些策略?

为了更好地理解 CASCADE,了解一下它的替代方案很有帮助:

  • RESTRICT / NO ACTION (限制 / 无操作 - 大多数数据库的默认行为)

    • 规则:如果试图删除或更新的父记录仍被子记录引用,则该操作 将被拒绝 并报错。

    • 场景:这是最安全的选项。它会强制你必须先手动处理完所有员工(比如将他们转移到其他部门或删除),然后才能删除部门。

  • SET NULL (置空)

    • 规则:如果父记录被删除或更新,所有引用它的子记录的外键字段将被 自动设置为 NULL

    • 场景:适用于“员工可以不属于任何部门”的情况。删除“IT”部门后,原部门的员工记录不会被删除,他们的 works_in 字段会变成 NULL前提是该外键列必须允许为NULL

  • SET DEFAULT (设置默认值)

    • 规则:如果父记录被删除或更新,所有引用它的子记录的外键字段将被 自动设置为预先定义的默认值

    • 场景:比如可以设置一个“待分配”部门,当某个部门被删除后,其员工会自动归入“待分配”部门。

总结

策略

行为

优点

缺点/风险

CASCADE

父变子随

自动化,保持数据强一致性,简化应用逻辑。

危险! 容易导致意外的大规模数据删除,难以追溯。

RESTRICT

拒绝操作

最安全,防止误操作,逻辑清晰。

需要应用层手动处理依赖关系,较为繁琐。

SET NULL

置为NULL

保留子记录,只断开关联,逻辑解耦。

外键列必须允许 NULL,可能产生孤立数据。

SET DEFAULT

置为默认值

为子记录提供一个“后备”关联。

需要预先定义一个有意义的默认值。

因此,“级联 (CASCADE) 执行” 是一种强大的自动化工具,用于维护数据库的参照完整性,但因其潜在的破坏性(尤其是级联删除),必须在充分理解其后果后 谨慎使用

4. SQL中的数据完整性支持

4.1 CREATE TABLE中的约束定义

在创建表时可以直接定义完整性约束。

  • 完整性约束关键字

    • NULL / NOT NULL

    • UNIQUE

    • PRIMARY KEY

    • FOREIGN KEY

    • CHECK

  • 示例:

     CREATE TABLE S (
         S#   CHAR(5) NOT NULL UNIQUE,       -- 列级约束:非空且唯一
         SN   CHAR(20) NOT NULL,
         SS   CHAR(2) CHECK (SS IN ('M', 'F')), -- 列级约束:性别只能是'M'或'F'
         SA   INT CHECK (SA >= 18 AND SA <= 45),-- 列级约束:年龄范围
         SD   CHAR(15),
         PRIMARY KEY(S#),                      -- 表级约束:定义主键
         CHECK (SS = 'F' OR SN NOT LIKE 'Ms.%')-- 表级约束:一个复杂的元组约束
     );

4.2 断言 (Assertion)

  • 作用:用于定义涉及多个表或需要使用聚合函数的复杂约束。

  • 语法CREATE ASSERTION <断言名> CHECK (<条件>);

  • 示例:要求每一门课程的选修人数不能超过60人。

     CREATE ASSERTION ASSE-SC-SNUM1
     CHECK (60 >= ALL (SELECT COUNT(*) FROM SC GROUP BY C#));

4.3 触发器 (Trigger)

  • 定义:一种由 事件驱动 的特殊过程,当对表的特定操作(INSERT, UPDATE, DELETE)发生时自动执行。

  • 作用:可以实现比CHECK和断言更复杂的完整性控制逻辑。

  • 语法结构:

     CREATE TRIGGER <触发器名>
     {BEFORE | AFTER} <触发器事件> ON <表名>
     FOR EACH {ROW | STATEMENT}
     [WHEN <触发条件>]
     <触发动作体>

5.补充概念

5.1 完整性约束条件的分类

除了按照实体、参照、用户自定义来划分,完整性约束还可以从数据状态的角度分为两类:

  • 静态约束

    • 定义:指数据库在 任何一个确定的时间点(状态) 下,数据都必须满足的约束。它反映的是数据库状态的 合理性

    • 示例

      • 静态列级约束:学生的年龄必须在15到30岁之间。

      • 静态元组约束:对于一条订单记录,发货日期不能早于下单日期。

      • 静态关系约束:在一个学生表中,学号(主键)必须唯一。

  • 动态约束

    • 定义:指数据库从一个状态 转变为 另一个状态时,新旧数据值之间需要满足的约束。它反映的是数据库状态 变迁的合理性

    • 示例

      • 动态列级约束:员工的工资只能增加,不能减少 (UPDATE 操作时,新工资值必须大于等于旧工资值)。

      • 动态元组约束:员工的职位级别只能平调或晋升,不能降级。

5.2 完整性检查的时机

根据完整性约束是在何时被检查,可以分为两类:

  • 立即执行约束

    • 定义:在 每一条 INSERTUPDATEDELETE 语句执行完毕后,立即 检查该操作是否违反了完整性约束。

    • 行为:如果违反,系统会立刻拒绝该操作并报错。这是绝大多数数据库约束的 默认行为

  • 延迟执行约束

    • 定义:在一个 事务 (Transaction) 的执行过程中,完整性检查被 推迟到事务提交 (COMMIT) 时 才进行。

    • 行为:事务执行期间可以暂时处于不满足完整性约束的中间状态,只要在事务结束时数据恢复到满足约束的状态即可。如果事务提交时检查不通过,整个事务将被回滚 (ROLLBACK)。

    • 应用场景:处理复杂的、需要多个步骤才能完成的数据更新。例如,要交换两个部门的主管,需要先将一个主管的外键设置为空,再进行指派,这个中间状态可能会违反 NOT NULL 约束,但最终状态是合法的。

5.3 完整性规则的表示

一条完整的完整性规则,可以用一个形式化的 五元组 (D, O, A, C, P) 来描述,这有助于精确地定义和理解一个约束。

  • D (Data):约束所作用的 数据对象。例如,TEACHER 表的 工资 列。

  • O (Operation):触发完整性检查的 数据库操作。例如,对 TEACHER 表的 INSERTUPDATE

  • A (Assertion):数据对象必须满足的 断言 或语义约束。例如,工资 >= 10000

  • C (Condition):一个谓词,用于选择断言 A 所作用的 数据对象值的范围。例如,职称 = '教授'

  • P (Procedure):当违反完整性规则时,系统触发的 过程或动作。例如,拒绝执行该操作

示例:

  • 规则TEACHER 表中,"教授"的"工资"不得低于10000元。

  • 五元组表示

    • DTEACHER 表的 工资

    • OINSERT, UPDATE

    • A工资 >= 10000

    • C职称 = '教授'

    • P:拒绝该操作


动物装饰