数据库系统概论笔记

发布时间 2023-12-28 22:51:30作者: hjiaqi

参考视频及文章:

视频:【公开课】数据库系统概论(王珊老师)

书籍:《数据库系统概论》第五版

PPT:PPT查看

下载方法:f12->点击ppt->找到相应的请求->找到下载地址->访问下载

带*号表示不是重点

其他:判断是否为无损连接分解

PL/SQL存储过程
个人blog:https://hjiaqi.top/2023/11/21/数据库/数据库系统概论/

第一章 绪论

数据库概述

数据库的4个基本概念

数据(Data)

  • 数据(Data)是数据库中存储的基本对象

  • 数据的定义:描述事物的符号记录称为数据,可以是数字、文字、图形、图像、音频、视频等,它们都可以经过数字化后存入计算机

  • 数据的解释是指对数据含义的说明,数据的含义称为数据的语义,数据与其语义是不可分的

数据库(DataBase,DB)

  • 数据库是长期储存在计算机内(永久存储)、有组织的、可共享的大量数据的集合
  • 数据库中的数据按一定的数据模型组织、描述和储存,具有较小的冗余度(redundancy)、较高的数据独立性(data independency)和易扩展性(scalability),并可为各种用户共享

数据库管理系统(DataBase Management System, DBMS)

  • 数据库管理系统是位于用户与操作系统之间的一层数据管理软件,和操作系统一样是计算机的基础软件,也是一个大型复杂的软件系统,用于高效地获取和维护数据
  • 主要功能
    • 数据定义功能:提供数据定义语言(Data Definition Language, DDL);定义数据库中的数据对象
    • 数据组织、存储和管理:分类组织、存储和管理各种数据;确定组织数据的文件结构和存取方式;实现数据之间的联系;提供多种存取方法提高存取效率
    • 数据操纵功能:提供数据操纵语言(Data Manipulation Language, DML);实现对数据库的基本操作 (查询、插入、删除和修改)
    • 数据库的事务管理和运行管理:数据库在建立、运行和维护时由DBMS统一管理和控制;保证数据的安全性、完整性、多用户对数据的并发使用;发生故障后的系统恢复
    • 数据库的建立和维护功能(由实用程序或管理工具实现):数据的输入、转换;数据库的转储、恢复;数据库的重组织;性能监视分析等
    • 数据库控制语言(DCL):grant,deny,revoke
    • 其他功能:DBMS与网络中其它软件系统的通信;两个DBMS系统的数据转换;异构数据库之间的互访和互操作

数据库系统(DataBase System, DBS)

  • DBS=计算机系统(硬件、软件平台、人(DBA))+DBMS+DB

  • 数据库系统的构成

    • 数据库:用于存储数据

    • 数据库管理系统(及其应用开发工具)管理数据的工具。提供了插入、更新、删除和查询功能,DBMS还负责保护和维护数据的完整性。

    • 应用程序:负责处理数据。应用程序通过DBMS与数据库交互,以获取所需的数据。

    • 数据库管理员(DataBase Administrator, DBA):负责维护数据。DBA的职责包括设计和维护数据库结构、监控和优化数据库性能、确保数据安全等。

数据管理技术的发展和特点

发展阶段比较

  • 在计算机硬件、软件发展的基础上,数据管理技术经历了人工管理、文件系统、数据库系统3个阶段。比较如表1-1所示

文件系统和数据库系统比较示例

  • 例设一个学生的信息包括学号、姓名、性别、年龄、专业和奖励部。假设该学籍管理系统具有录入学生信息、根据学号可以找到一个学生信息等功能。

  • 采用文件系统实现学籍管理

    • 计算机操作系统实现了文件系统,可以把学生基本信息采用定长记录方式存放到一个“学生基本信息”文件中,其中位置和长度描述的是另一个奖励文件中记录的开始位置和长度

    • 奖励数据有的学生较多,有的学生没有,因此采用变长记录方式存放在一个“奖励”文件中

    • 确定数据的存储方式后,需要编写程序来实现数据的录入功能和查询功能。要注意的是,要把奖励情况的开始位置和长度写到“学生基本信息”文件中。

    • 查询功能采用顺序查找方法。从第1条记录开始比较学号字段的值是否和要查找的学号相同,如果相同则读出该学生的信息,并根据位置字段和长度字段的值到“奖励”文件中读出奖励信息

  • 采用数据库系统实现学籍管理

    • 在数据库中建立两张表:STUDENT存放学生的基本信息,AWARD存放学生的奖励情况。使用数据库管理系统的两条DDL语句来实现

      CREATE TABLE STUDENT(
          Sno     CHAR(8),
          Sname   CHAR(10),
          Ssex    CHAR(2),
          Sage    SMALLINT,
          Major   CHAR(20));
      CREATE TABLE AWARD(
          Sno     CHAR(8),
          Details VARCHAR(2000));
      
    • 添加数据或查询数据也可以通过DML来完成

  • 使用文件系统时,程序员要关注记录的结构和不同文件中记录之间的联系,使用文件系统提供的fopen(打开)、fread(读)、fwrite(写)、fseek(移动读写位置)、fclose(关闭)等操作来编程,工作量大、编程复杂,且开发速度慢

  • 而数据库系统提供了功能强大的操作,如查询操作只需要写一条语句就可以实现,提高了程序员的开发效率

各个阶段特点

  • 人工管理阶段特点:
    1. 数据不保存。(纸带)
    2. 应用程序管理数据。
    3. 数据不共享。
    4. 数据不具有独立性。(和程序绑定在一起)
    5. 缺点:数据的逻辑结构或物理结构发生变化后必须对应用程序做相应的修改,这就加重了程序员的负担。
  • 文件系统阶段
    • 特点
      1. 数据可以长期保存
      2. 由文件系统管理数据
    • 缺点
      1. 数据共享性差,冗余度大
      2. 数据独立性差
  • 数据库系统阶段
    • 数据结构化
      • 整体数据的结构化是数据库的主要特征之一
      • 整体结构化:不再仅仅针对某一个应用,而是面向全组织;不仅数据内部结构化,整体是结构化的,数据之间具有联系。例如,一个学校的信息系统中不仅要考虑教务处的课程管理,还要考虑学生处的学生学籍管理,学校的学生数据就要面向各个处室的应用
    • 数据的共享性高、冗余度低且易扩充
      • 数据库系统从整体角度看待和描述数据,数据面向整个系统,可以被多个用户、多个应用共享使用。
      • 数据共享的好处:减少数据冗余,节约存储空间;避免数据之间的不相容性与不一致性;使系统易于扩充
    • 数据独立性高
      • 数据独立性:借助数据库管理数据的一个显著优点,是由DBMS的二级映像功能来保证的
      • 物理独立性:指用户的应用程序与存储在磁盘上的数据库中数据是相互独立的。当数据的物理存储改变了,应用程序不用改变。
      • 逻辑独立性:指用户的应用程序与数据库的逻辑结构是相互独立的。数据的逻辑结构改变了,用户程序也可以不变。
    • 数据由DBMS统一管理和控制
      1. 数据的安全性(secufity)保护
        • 保护数据,防止不合法的使用造成数据的泄密和破坏。
      2. 数据的完整性(integrity)检查
        • 数据的完整性指数据的正确性、有效性和相容性。
        • 数据的完整性检查将数据控制在有效范围内,保证数据之间满足一定的关系。
      3. 并发(concurrency)控制
        • 对多用户的并发操作加以控制和协调,防止相互干扰而得到错误的结果。
      4. 数据库恢复(recovery)
        • 将数据库从错误状态恢复到某一已知的正确状态

数据模型

  • 数据模型(data model):是对现实世界数据特征的抽象。数据模型是数据库系统的核心和基础。
  • 通俗地讲数据模型就是现实世界的模拟。
  • 数据模型应满足三方面要求:
    • 能比较真实地模拟现实世界
    • 容易为人所理解
    • 便于在计算机上实现

数据模型的分类

  • 根据模型应用的不同目的,数据模型分为两类:一类是概念模型,一类是逻辑模型和物理模型。

  • 概念模型(conceptual model)也称信息模型,它是按用户的观点来对数据和信息建模,主要用于数据库设计

  • 逻辑模型和物理模型

    • 逻辑模型主要包括层次模型(hierarchical model)、网状模型(network model)、关系模型(relational model)、面向对象数据模型(object oriented data model)和对象关系数据模型(object relational data model)、半结构化数据模型(semistructured data model)等。它是按计算机系统的观点对数据建模,主要用于DBMS的实现
    • 物理模型是对数据最底层的抽象,它描述数据在系统内部的表示方式和存取方法,或在磁盘或磁带上的存储方式和存取方法,是面向计算机系统的
  • 客观对象的抽象过程:

    • 现实世界中的客观对象抽象为概念模型
    • 把概念模型转换为某一DBMS支持的数据模型

概念模型

  • 概念模型是现实世界到机器世界的个中间层次,表现为:

    1. 概念模型用于信息世界的建模
    2. 现实世界到信息世界的第一层抽象
    3. 数据库设计人员进行数据库设计的有力工具
    4. 数据库设计人员和用户之间进行交流的语言
  • 特点

    • 有较强的语义表达能力,能够方便、直接地表达应用中的各种语义知识
    • 简单、清晰、易于用户理解
  • 基本概念

    • 实体(entity):客观存在并可相互区别的事物称为实体。实体可以是具体的人事物,也可以是抽象的概念或联系
    • 属性(attribute):实体所具有的某一特性称为属性。如学生实体由学号、姓名等属性组成
    • 码(key):唯一标识实体的属性集称为码。
    • 域(Domain):域是一组具有相同数据类型的值的集合,属性的取值范围来自于域。如年龄的域为整数。
    • 实体型(entity type):用实体名及其属性名集合来抽象和刻画同类实体,称为实体型。如学生(学号,姓名,性别,出生年月,所在院系,入学时间)就是一个实体型。
    • 实体集(entity set):同一类型实体的集合称为实体集。
    • 联系(relationship):实体内部的联系通常指实体各属性之间的联系,实体之间的联系通常指不同实体集之间的联系。实体之间的联系有一对一、一对多和多对多等多种类型。
  • 实体-联系方法(Entity-Relationship approach)

    • 该方法用E-R图(E-R diagram)来描述现实世界的概念模型,E-R方法也称为E-R模型。

数据模型的组成要素

  • 数据模型通常由数据结构、数据操作和数据的完整性约束条件三部分组成。
  • 数据结构
    • 数据结构描述数据库的组成对象以及对象之间的联系。
    • 即包括两个方面:
      1. 数据本身:类型、内容、性质。如关系模型中的域、属性、关系等。
      2. 数据之间的联系:数据之间是如何相互关联的,如关系模型中的主码、外码联系等
  • 数据操作
    • 对数据库中各种对象(型)的实例(值)允许执行的操作,及有关的操作规则
    • 数据库主要有查询和更新(包括插入、删除、修改)两大类操作
  • 数据的完整性约束条件
    • 数据的完整性约束条件是一组完整性规则
    • 完整性规则定义了在特定数据模型中,数据应如何与其他数据相关联,以及数据应如何受到限制和依赖。
    • 规定数据库状态及状态变化所应满足的条件,以保证数据的正确性、有效性和相容性

常用的数据模型

  • 数据库领域中主要的逻辑数据模型有

    • 层次模型(hierarchical model)
    • 网状模型(network model)
    • 关系模型(relational model)
    • 面向对象数据模型(object oriented data model)
    • 对象关系数据模型(object relational data model)
    • 半结构化数据模型(semistructure data model)
  • 简要介绍层次模型、网状模型和关系模型,重点学习关系模型

  • 所谓基本层次联系是指两个记录以及它们之间的一对多(包括一对一)的联系

层次模型

  • 层次模型是数据库系统中最早出现的数据模型

  • 层次数据库系统的典型代表是IBM公司的IMS(Information Management System)

  • 层次模型用树形结构来表示各类实体以及实体间的联系

  • 层次模型的数据结构

    • 满足下面两个条件的基本层次联系的集合为层次模型

      1. 有且只有一个结点没有双亲结点,这个结点称为根结点
      2. 根以外的其它结点有且只有一个双亲结点
    • 在层次模型中,同一双亲的子女结点称为兄弟结点(twin或 sibling),没有子女结点的结点称为叶结点。

    • 一个层次模型的示例

  • 层次模型的特点

    • 一对多的关系:每个节点表示一个记录类型,记录类型之间的联系用连线(有向边)表示。这种联系是父子之间的一对多的关系,这就使得层次数据库系统只能处理一对多的实体关系。
    • 记录类型和字段:每个记录类型可以包含若干个字段,记录类型描述的是实体,字段描述实体的属性。记录类型和字段必须命名且不能同名。
    • 排序字段:每个记录类型可以定义一个排序字段,也称为码字段。如果定义该排序字段的值是唯一的,则它能唯一地标识一个记录值。
    • 数量限制:虽然一个层次模型在理论上可以包含任意有限个记录类型和字段,但任何实际系统都会因为存储容量或实现复杂度限制了记录类型和字段的数量。
    • 路径依赖性:任何一个给定的记录值只能按其层次路径查看,子女记录值不能够脱离双亲记录值而独立存在。这意味着在层次模型中,数据的访问和操作都是基于其在层次结构中的位置。
  • 例:

    • 教员学生层次数据库模型如下图
    • 教员学生层次数据库的一个值如下图
  • 层次模型的数据操纵与完整性约束

    • 层次模型的数据操纵主要有查询、插入、删除和更新。
    • 层次模型的完整性约束条件:
      • 无相应的双亲结点值就不能插入子女结点值
      • 如果删除双亲结点值,则相应的子女结点值也被同时删除
      • 更新操作时,应更新所有相应记录,以保证数据的一致性
  • 层次模型的存储结构

    • 邻接法:按照层次顺序把所有的记录邻接存放,即通过物理空间的位置相邻来实现层次顺序。

      • 例如按照层次树前序遍历的顺序把所有记录值依次邻接存放,即通过物理空间的位置相邻来实现层次顺序。

      • 例如对于下图,(a)是数据模型,它有一个实例(b),其物理空间位置如表格所示

    • 链接法:各个记录存放时不是按层次顺序,而是用指针按层次顺序把它们链接起来。

      • 子女-兄弟链接法

        • 每个记录两类指针,分别指向最左边的子女(每个记录型对应一个)和最近的兄弟

      • 层次序列链接法

        • 按树的前序穿越顺序链接各记录值

  • 层次模型的优缺点

    • 优点
      • 层次模型的数据结构比较简单清晰
      • 查询效率高,性能优于关系模型,不低于网状模型
      • 层次数据模型提供了良好的完整性支持
    • 缺点
      • 多对多联系表示不自然
      • 对插入和删除操作的限制多,应用程序的编写比较复杂
      • 查询子女结点必须通过双亲结点
      • 由于结构严密,层次命令趋于程序化

网状模型

  • 网状数据库系统采用网状模型作为数据的组织方式

  • 典型代表是DBTG系统,亦称CODASYL系统,70年代由DBTG(Data Base Task Group)提出的一个系统方案,奠定了数据库系统的基本概念、方法和技术

  • 网状模型的数据结构

    • 满足以下两个条件的基本层次联系集合为网状模型

      1. 允许一个以上的结点无双亲
      2. 一个结点可以有多个的双亲
    • 网状模型是一种比层次模型更具普遍性的结构。层次模型实际上是网状模型的一个特例

    • 在网状模型中联系可以不唯一,因此要为每个联系命名,例

    • 实例:一个学生可以选修若干门课程,某一课程可以被多个学生选修,学生与课程之间是多对多联系

      • 引进一个学生选课的联结记录,由3个数据项组成:学号、课程号、成绩

      • 学生与选课之间的联系是一对多的联系,联系名为S-SC。同样,课程与选课之间的联系也是一对多的联系,联系名为C-SC 。

      • 于是学生选课的网状数据模型为

  • 网状模型的数据操纵与完整性约束

    • 网状模型一般来说没有层次模型那样严格的完整性约束条件,但具体的网状数据库系统对数据操纵都加了一些限制,提供了一定的完整性约束。
      • 如DBTG对数据操纵加了一些限制,提供了一定的完整性约束
        • 码:唯一标识记录的数据项的集合
        • 一个联系中双亲记录与子女记录之间是一对多联系
        • 支持双亲记录和子女记录之间某些约束条件
  • 网状模型数据的存储结构

    • 单向链接

    • 双向链接

    • 环状链接

    • 向首链接

    • 学生选课的网状数据库实例

  • 网状模型的优缺点

    • 优点
      • 能够更为直接地描述现实世界,如一个结点可以有多个双亲
      • 具有良好的性能,存取效率较高
    • 缺点
      • 结构比较复杂,而且随着应用环境的扩大,数据库的结构就变得越来越复杂,不利于最终用户掌握
      • DDL、DML语言复杂,用户不容易使用

关系模型

  • 关系数据库系统采用关系模型作为数据的组织方式

  • 1970年美国IBM公司San Jose研究室的研究员E.F.Codd首次提出了数据库系统的关系模型

  • 计算机厂商新推出的数据库管理系统几乎都支持关系模型

  • 关系模型的数据结构

    • 从用户观点看,关系模型中数据的逻辑结构是一张二维表,它由行和列组成。

      • 关系(Relation):一个关系对应通常说的一张表
      • 元组(Tuple):表中的一行即为一个元组
      • 属性(Attribute):表中的一列即为一个属性,给每一个属性起一个名称即属性名
      • 主码(Key):表中的某个属性组,它可以唯一确定一个元组
      • 域(Domain):属性的取值范围
      • 分量:元组中的一个属性值。
      • 关系模式:对关系的描述,表示为关系名(属性1,属性2,…,属性n)
    • 例如:学生登记表

    • 关系必须是规范化的,满足一定的规范条件

      • 最基本的规范条件:关系的每一个分量必须是一个不可分的数据项,不允许表中还有表

      • 下图中中工资和扣除是可分的数据项,不符合关系模型要求

      • 把关系和现实生活中的表格所使用的术语做一个粗略的对比

        关系术语 一般表格的术语
        关系名 表名
        关系模式 表头(表格的描述)
        关系 (一张)二维表
        元组 记录或行
        属性
        属性名 列名
        属性值 列值
        分量 一条记录中的一个列值
        非规范关系 表中有表(大表中嵌有小表)
    • 关系模型的数据操纵与完整性约束

      • 关系模型的数据操纵主要包括查询、插入、删除和更新数据。
      • 关系的完整性约束条件:实体完整性、参照完整性、用户定义的完整性
      • 数据操作是集合操作,操作对象和操作结果都是关系
      • 存取路径对用户隐蔽,用户只要指出“干什么”,不必详细说明“怎么干”
    • 关系模型的存储结构

      • 表以文件形式存储
      • 有的DBMS一个表对应一个操作系统文件
      • 有的DBMS自己设计文件结构
    • 关系模型的优缺点

      • 优点
        • 建立在严格的数学概念的基础上
        • 概念单一。无论实体还是实体之间的联系都用关系来表示。对数据的检索和更新结果也是关系(即表)。
        • 存取路径对用户透明。具有更高的数据独立性,更好的安全保密性,简化了程序员的工作和数据库开发建立的工作
      • 缺点
        • 存取路径对用户透明导致查询效率往往不如非关系数据模型
        • 为提高性能,必须对用户的查询请求进行优化增加了开发DBMS的难度

数据库系统的结构

  • 从数据库管理系统角度看,数据库系统通常采用三级模式结构,是数据库系统内部的系统结构

  • 从数据库最终用户角度看(数据库系统外部的体系结构),数据库系统的结构分为:

    • 单用户结构
    • 主从式结构
    • 分布式结构
    • 客户-服务器
    • 浏览器-应用服务器/数据库服务器多层结构等

数据库系统模式的概念

  • “型”(type)和“值”(value)的概念

    • 型:对某一类数据的结构和属性的说明
    • 值:是型的一个具体赋值
    • 例如
      • 学生记录型:(学号,姓名,性别,系别,年龄,籍贯)
      • 一个记录值:(900201,李明,男,计算机,22,江苏)
  • 模式(Schema)

    • 数据库逻辑结构和特征的描述
    • 是型的描述,不涉及具体的值
    • 反映的是数据的结构及其联系
    • 模式是相对稳定的
  • 实例(Instance)

    • 是模式的一个具体值
    • 同一个模式可以有很多实例
    • 实例随数据库中的数据的更新而变动
    • 反映数据库某一时刻的状态

数据库系统的三级模式结构

  • 三级模式结构是指数据库系统是由外模式(External Schema)、模式(Schema)和内模式(Internal Schema)三级构成

  • 模式(也称逻辑模式,概念模式)

    • 是数据库中全体数据的逻辑结构和特征的描述
    • 是所有用户的公共数据视图,综合了所有用户的需求
    • 模式的地位:是数据库系统模式结构的中间层
      • 与数据的物理存储细节和硬件环境无关
      • 与具体的应用程序、开发工具及高级程序设计语言无关
    • 一个数据库只有一个模式
    • 定义模式时要考虑
      • 数据的逻辑结构(数据项的名字、类型、取值范围等)
      • 数据之间的联系
      • 数据有关的安全性、完整性要求
    • 举例:以一个学生信息数据库为例,模式就是描述这个数据库中所有学生信息的逻辑结构,包括学生的基本信息(如姓名、性别、年龄、班级等)和课程信息(如课程名称、课程代码、学分等),以及这些信息之间的关系和约束
  • 外模式(也称子模式(subschema)或用户模式)

    • 是数据库用户(包括应用程序员和最终用户)使用的局部数据的逻辑结构和特征的描述
    • 是数据库用户的数据视图,是与某一应用有关的数据的逻辑表示
    • 外模式的地位:介于模式与应用之间
      • 模式与外模式的关系:一对多
        • 外模式通常是模式的子集
        • 一个数据库可以有多个外模式。反映了不同的用户的应用需求、看待数据的方式、对数据保密的要求
        • 对模式中同一数据,在外模式中的结构、类型、长度、保密级别等都可以不同
      • 外模式与应用的关系:一对多
        • 同一外模式也可以为某一用户的多个应用系统所使用
        • 但一个应用程序只能使用一个外模式
    • 外模式的用途
      • 保证数据库安全性的一个有力措施
      • 每个用户只能看见和访问所对应的外模式中的数据
    • 数据库管理系统提供外模式数据定义语言(外模式DDL)来严格地定义外模式。
    • 总的来说外模式描述了数据库的用户视图或外部视图。每个用户或应用程序可能只能看到数据库中的某些部分,外模式定义了用户能够访问和操作的数据视图。
      • 举例:对于学生信息数据库,如果某个学生想要查询自己的信息,那么他所看到的数据库就是外模式,也就是他的数据视图。这个视图中可能只包括学生的基本信息和课程信息,而不包括其他学生的信息。
  • 内模式(也称存储模式(storage schema))

    • 一个数据库只有一个内模式
    • 是数据物理结构和存储方式的描述
    • 是数据在数据库内部的表示方式
      • 记录的存储方式是堆存储还是按照某个(些)属性值的升(降)序存储,或按照属性值聚族(cluster)存储
      • 索引按照什么方式组织,是B+树索引还是hash索引
      • 数据是否压缩存储,是否加密
      • 数据的存储记录结构有何规定,如定长结构或变长结构,一个记录不能跨物理页存储

数据库的二级映像功能与数据独立性

  • 数据库系统的三级模式是对数据的三个抽象级别

  • 数据库管理系统在这三级模式之间提供了两层映像实现这三个抽象层次的联系和转换

    • 外模式/模式映像
    • 模式/内模式映像
  • 这两层映像保证了数据库系统中的数据能够具有较高的逻辑独立性和物理独立性

外模式/模式映象

  • 模式:描述的是数据的全局逻辑结构

  • 外模式:描述的是数据的局部逻辑结构

  • 同一个模式可以有任意多个外模式

  • 每一个外模式,数据库系统都有一个外模式/模式映象,定义外模式与模式之间的对应关系

  • 映象定义通常包含在各自外模式的描述中

  • 为了保证数据的逻辑独立性

    • 当模式改变时(例如增加新的关系、新的属性),数据库管理员修改有关的外模式/模式映象,使外模式保持不变

    • 应用程序是依据数据的外模式编写的,从而应用程序不必修改,保证了数据与程序的逻辑独立性,简称数据的逻辑独立性

模式/内模式映象

  • 模式/内模式映象定义了数据全局逻辑结构与存储结构之间的对应关系。

  • 例如,说明逻辑记录和字段在内部是如何表示的

  • 数据库中模式/内模式映象是唯一的

  • 该映象定义通常包含在模式描述中

  • 为了保证数据的物理独立性

    • 当数据库的存储结构改变了(例如选用了另一种存储结构),数据库管理员修改模式/内模式映象,使模式保持不变

    • 应用程序不受影响。保证了数据与程序的物理独立性,简称数据的物理独立性。

  • 数据库模式

    • 即全局逻辑结构是数据库的中心与关键
    • 独立于数据库的其他层次
    • 设计数据库模式结构时应首先确定数据库的逻辑模式
  • 数据库的内模式

    • 依赖于它的全局逻辑结构
    • 独立于数据库的用户视图,即外模式
    • 独立于具体的存储设备
    • 将全局逻辑结构中所定义的数据结构及其联系按照一定的物理存储策略进行组织,以达到较好的时间与空间效率
  • 数据库的外模式

    • 面向具体的应用程序
    • 定义在逻辑模式之上
    • 独立于存储模式和存储设备
    • 当应用需求发生较大变化,相应外模式不能满足其视图要求时,该外模式就得做相应改动
    • 设计外模式时应充分考虑到应用的扩充性
  • 特定的应用程序

    • 是在外模式描述的数据结构上编制的
    • 依赖于特定的外模式
    • 与数据库的模式和存储结构独立
    • 不同的应用程序有时可以共用同一个外模式
  • 数据库的二级映像

    • 保证了数据库外模式的稳定性
    • 从底层保证了应用程序的稳定性,除非应用需求本身发生变化,否则应用程序一般不需要修改
  • 数据与程序之间的独立性,使得数据的定义和描述可以从应用程序中分离出去

  • 数据的存取由DBMS管理

    • 用户不必考虑存取路径等细节

    • 简化了应用程序的编制

    • 大大减少了应用程序的维护和修改

数据库系统的组成

硬件平台及数据库

  • 数据库系统对硬件资源的要求
    1. 要有足够大的内存,存放操作系统、数据库管理系统的核心模块、数据缓冲区和应用程序
    2. 有足够大的磁盘或磁盘阵列等设备存放数据库,有足够大的磁带(或光盘)作数据备份
    3. 较高的通道能力,提高数据传送率

软件

  • DBMS
  • 支持DBMS运行的操作系统
  • 与数据库接口的高级语言及其编译系统
  • 以DBMS为核心的应用开发工具
  • 为特定应用环境开发的数据库应用系统

人员

  • 数据库管理员

  • 系统分析员和数据库设计人员

  • 应用程序员

  • 用户

  • 不同的人员涉及不同的数据抽象级别,具有不同的数据视图,如下图所示

    • 数据库管理员(DBA)

      • 具体职责:
        1. 决定数据库中的信息内容和结构
        2. 决定数据库的存储结构和存取策略
        3. 定义数据的安全性要求和完整性约束条件
        4. 监控数据库的使用和运行,如系统故障恢复,周期性转储数据、维护日志文件等
        5. 数据库的改进和重组,负责性能监控和调优;定期对数据库进行重组织,以提高系统的性能;需求增加和改变时,数据库须需要重构造
    • 系统分析员和数据库设计人员

      • 系统分析员
        • 负责应用系统的需求分析和规范说明
        • 与用户及DBA协商,确定系统的硬软件配置
        • 参与数据库系统的概要设计
      • 数据库设计人员
        • 参加用户需求调查和系统分析
        • 确定数据库中的数据
        • 设计数据库各级模式
    • 应用程序员

      • 设计和编写应用系统的程序模块
      • 进行调试和安装
    • 用户

      • 用户是指最终用户(End User)。最终用户通过应用系统的用户接口使用数据库。
      1. 偶然用户

        • 不经常访问数据库,但每次访问数据库时往往需要不同的数据库信息
        • 企业或组织机构的高中级管理人员
      2. 简单用户

        • 主要工作是查询和更新数据库
        • 银行的职员、机票预定人员、旅馆总台服务员
      3. 复杂用户

        • 工程师、科学家、经济学家、科技工作者等
        • 直接使用数据库语言访问数据库,甚至能够基于数据库管理系统的API编制自己的应用程序

第二章 关系数据库

关系数据结构及形式化定义

  • 关系数据库系统是支持关系模型的数据库系统。按照数据模型的三个要素,关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。

关系

  • 关系模型只包含单一的数据结构 关系,逻辑结构就是二维表。现实世界的实体以及实体间的各种联系均用关系来表示

  • 域(Domain):一组具有相同数据类型的值的集合

    • 例如:然数、整数、实数、长度小于25字节的字符串集合、{0,1}等,都可以是域
  • 笛卡尔积(Cartesian Product):域上的一种集合运算

    • 给定一组域 \(D_1,D_2,\cdots,D_n\),这些域中可以有相同的。\(D_1,D_2,\cdots,D_n\) 的笛卡尔积为:

      \[D_1\times D_2\times\cdots\times D_n=\{(d_1,d_2,\cdots,d_n)\quad|\quad d_i\in D_i,\quad i=1,2,\cdots,n\} \]

    • 元组(Tuple):笛卡尔积中每一个元素 \((d_1,d_2,\cdots,d_n)\) 叫作一个n元组(n-tuple)或简称元组

    • 分量(Component):笛卡尔积元素 \((d_1,d_2,\cdots,d_n)\) 中的每一个值 \(d_i\) 叫作一个分量

    • 基数(Cardinal number):一个域允许的不同取值个数称为这个域的基数

      • \(D_i(i=1,2,\cdots,n)\) 为有限集,其基数为 \(m_i(i=1,2,\cdots,n)\) ,则 \(D_1\times D_2\times\cdots\times D_n\) 的基数M为

        \[M=\prod_{i=1}^nm_i \]

    • 笛卡尔积的表示方法:笛卡尔积可表示为一个二维表;表中的每行对应一个元组,表中的每列对应一个域

    • 例如给出三个域:

      D1=导师集合 SUPERVISOR={张清玫,刘逸}

      D2=专业集合 SPECIALITY={计算机专业,信息专业}

      D3=研究生集合 POSTGRADUATE={李勇,刘晨,王敏}

      D1×D2×D3的结果为

  • 关系(Relation)

    • D1×D2×...×Dn的子集叫作在域D1,D2,...,Dn上的关系,表示为R(D1,D2,...,Dn),R表示关系的名字,n是关系的目或度(degree)

    • 元组:关系中的每个元素是关系中的元组,通常用t表示。

    • 单元关系与二元关系

      • 当n=1时,称该关系为单元关系(Unary relation)或一元关系
      • 当n=2时,称该关系为二元关系(Binary relation)
    • 关系的表示:关系也是一个二维表,表的每行对应一个元组,表的每列对应一个域

    • 属性(Attribute):关系中不同列可以对应相同的域为了加以区分,必须对每列起一个名字,称为属性。n目关系必有n个属性

      • 候选码(Candidate key):若关系中的某一属性组的值能唯一地标识一个元组,则称该属性组为候选码
      • 全码(All-key):关系模式的所有属性组是这个关系模式的候选码,称为全码
      • 主码(Primary key):若一个关系有多个候选码,则选定其中一个为主码
      • 主属性(Prime attribute):候选码的所有属性称为主属性
      • 不包含在任何侯选码中的属性称为非主属性(Non-Prime attribute)或非码属性(Non-key attribute)
    • 一般来说笛卡尔积的某个子集才有实际含义

    • 三类关系:

      • 基本关系(基本表或基表):实际存在的表,是实际存储数据的逻辑表示
      • 查询表:查询结果对应的表
      • 视图表:由基本表或其他视图表导出的表,是虚表,不对应实际存储的数据
    • 基本关系的性质

      • 列是同质的(Homogeneous),即来自同一个域

      • 不同的列可出自同一个域,其中的每一列称为一个属性,不同的属性要给予不同的属性名

      • 列的顺序无所谓,列的次序可以任意交换

      • 任意两个元组的候选码不能相同,否则就违背了候选码的定义

      • 行的顺序无所谓,行的次序可以任意交换

      • 分量必须取原子值,即每一个分量都是不可分的数据项。这是规范条件中最基本的一条

        • 简而言之,就是不允许表中有表,如下所示

关系模式

  • 关系模式(Relation Schema)是型,关系是值
  • 关系模式是对关系的描述
    • 如指出元组集合的结构,即属性构成、属性来自的域、属性与域之间的映象关系
    • 元组语义以及完整性约束条件
    • 属性间的数据依赖关系集合
  • 关系模式的定义
    • 关系模式可以形式化地表示为 \(R(U,D,DOM,F)\) ,这和关系中的R完全不一样
    • R为关系名,U为组成该关系的属性名集合,D为属性组U中属性所来自的域,DOM为属性向域的映象集合,F为属性间的数据依赖关系集合
      • 如果有一个学生关系,有学号、姓名、入学日期这三个属性,则
        • R=学生
        • U=
        • D=
        • DOM=
        • F=
  • 关系模式是静态的、稳定的
  • 关系是关系模式在某一时刻的状态或内容,因此关系是动态的、随时间不断变化的

关系数据库

  • 在一个给定的应用领域中,所有关系的集合构成一个关系数据库
  • 关系数据库的型与值
    • 型:指的是关系数据库模式,对关系数据库的定义
      • 它包括若干域的定义以及在这些域上定义的若干关系模式
    • 关系数据库的值:关系模式在某一时刻对应的关系的集合,简称为关系数据库

关系模型的存储结构

  • 在关系数据库的物理组织中,有的关系数据库管理系统中一个表对应一个操作系统文件,将物理数据组织交给操作系统完成
  • 有的关系数据库管理系统从操作系统那里申请若干个大的文件,自己划分文件空间,组织表、索引等存储结构,并进行存储管理

关系操作

基本关系操作

  • 常用的关系操作
    • 查询(query):选择(select)、投影(project)、连接(jion)、除(divide)、并(union)、交(intersection)、差(except)
      • 选择、投影、并、差、笛卡尔积是5种基本操作,其他操作可以用基本操作来定义和导出
    • 数据更新:插入(insert)、删除(delete)、修改(updata)
    • 集合操作方式:操作的对象和结果都是集合,一次一集合的方式

关系数据库语言的分类

  • 关系代数语言(relation algebra)
    • 用对关系的运算来表达查询要求
    • 关系代数语言是一种过程化查询语言。过程化语言在编程时必须给出获得结果的操作步骤,即指出“干什么”及“怎么干”的语言
    • 代表:ISBL
  • 关系演算语言(relational calculus)
    • 用谓词来表达查询要求
    • 关系演算语言是一种非过程化查询语言。它只描述所需要的信息,而不给出获得该信息的具体过程
    • 关系演算语言又可分为以下两种
      • 元组关系演算语言
        • 谓词变元的基本对象是元组变量
        • 代表:APLHA, QUEL
      • 域关系演算语言
        • 谓词变元的基本对象是域变量
        • 代表:QBE
  • 具有关系代数和关系演算双重特点的语言
    • 代表:SQL(Structured Query Language)
    • SQL是一种非过程化语言。SQL的操作过程由DBMS自动完成,用户只需提出“做什么”,而不必知道“怎么做”,无需了解存取路径等

关系的完整性

关系的三类完整性约束

  • 实体完整性和参照完整性
    • 关系模型必须满足的完整性约束条件
    • 称为关系的两个不变性,应该由关系系统自动支持
  • 用户定义的完整性:
    • 应用领域需要遵循的约束条件,体现了具体领域中的语义约束

实体完整性

  • 实体完整性规则(Entity Integrity)
    • 若属性(指一个或一组属性)A是基本关系R的主属性,则属性A不能取空值(null value)。所谓空值就是“不知道”或“不存在”或“无意义”的值。
    • 例如有一个学生表,其中学号是主属性。那么,实体完整性就要求每一个学生都必须有一个唯一的学号,并且这个学号不能为空
  • 实体完整性规则的说明
    1. 实体完整性规则是针对基本关系(表)而言的。一个基本表通常对应现实世界的一个实体集。
    2. 现实世界中的实体是可区分的,即它们具有某种唯一性标识。
    3. 关系模型中以主码作为唯一性标识。
    4. 主码中的属性即主属性不能取空值。主属性取空值,就说明存在某个不可标识的实体,即存在不可区分的实体,这与第2点相矛盾,因此这个规则称为实体完整性

参照完整性

关系间的引用

  • 在关系模型中实体及实体间的联系都是用关系来描述的,因此可能存在着关系与关系间的引用
  • 例如有学生实体和专业实体,其关系模型如下
    • 学生(学号,姓名,性别,专业号,年龄)
    • 专业(专业号,专业名)
    • 学生关系引用了专业关系的主码专业号
    • 学生关系中的专业号值必须是确实存在 ,即专业关系中有该专业的记录

外码(Foreign Key)

  • 设F是基本关系R的一个或一组属性,但不是关系R的码。如果F与基本关系S的主码Ks相对应,则称F是基本关系R的外码

  • 基本关系R称为参照关系(Referencing Relation)

  • 基本关系S称为被参照关系(Referenced Relation)或目标关系(Target Relation)

  • 如学生关系的专业号与专业关系的主码专业号相对应

    • 专业号属性是学生关系的外码

    • 专业关系是被参照关系,学生关系为参照关系

  • 关系R和S不一定是不同的关系

    • 如:在学生(学号,姓名,性别,专业号,年龄,班长)关系中,学号是主码,班长是对应班级班长的学号。
    • 即班长是外码,学生关系既是参照关系也是被参照关系
  • 目标关系S的主码Ks和参照关系的外码F必须定义在同一个(或一组)域上

  • 外码并不一定要与相应的主码同名。当外码与相应的主码属于不同关系时,往往取相同的名字,以便于识别

参照完整性规则

  • 参照完整性规则
    • 若属性(或属性组)F是基本关系R的外码,它与基本关系S的主码Ks相对应(基本关系R和S不一定是不同的关系),则对于R中每个元组在F上的值必须为:
      • 或者取空值(F的每个属性值均为空值)
      • 或者等于S中某个元组的主码值
    • 例如学生关系中每个元组的专业号属性只取两类值
      1. 空值,表示尚未给该学生分配专业
      2. 非空值,该值必须是专业关系中某个元组的专业号值

用户定义的完整性

  • 针对某一具体关系数据库的约束条件,反映某一具体应用所涉及的数据必须满足的语义要求
  • 关系模型应提供定义和检验这类完整性的机制,以便用统一的系统的方法处理它们,而不要由应用程序承担这一功能
  • 例:课程(课程号,课程名,学分)
    • “课程号”属性必须取唯一值
    • 非主属性“课程名”也不能取空值
    • “学分”属性只能取值

关系代数

关系数据库中的关系演算的内容暂时不用学习,所以不做介绍

概述

  • 关系代数是一种抽象的查询语言,它用对关系的运算来表达查询

  • 关系代数用到的运算符包括两类:集合运算符和专门的关系运算符,如下图所示

  • 关系代数的运算按运算符的不同可分为传统的集合运算和专门的关系运算两类。

  • 传统的集合运算将关系看成元组的集合,其运算是从关系的“水平”方向,即行的角度来进行

  • 专门的关系运算不仅涉及行,而且涉及列

传统的集合运算

  • 设关系R和关系S

    • 具有相同的目n(即两个关系都有n个属性),且相应的属性取自同一个域
    • t是元组变量,\(t\in R\) 表示t是R的一个元组。
  • 并(Union)

    • 关系R与关系S的并记作

      \[R\cup S=\{t|t\in R\vee t\in S\} \]

    • 结果仍为n目关系,由属于R或属于S的元组组成

  • 差(Difference)

    • 关系R与关系S的差记作

      \[R-S=\{t|t\in R\wedge t\notin S\} \]

    • 结果仍为n目关系,由属于R而不属于S的所有元组组成

  • 交(Intersection)

    • 关系R与关系S的交记作

      \[R\cap S=\{t|t\in R\wedge t\in S\} \]

    • 结果仍为n目关系,由既属于R又属于S的元组组成

    • 关系的交可以用差来表示 \(R\cap S=R-(R-S)\)

  • 笛卡尔积(Cartesian Product)

    • 严格地讲应该是广义的笛卡尔积(Extended Cartesian Product)

    • 假设关系R和关系S如下

      • R:n目关系,k1个元组
      • S:m目关系,k2个元组
    • 关系R和关系S的笛卡儿积为

      \[R\times S=\{\overset{\frown}{t_rt_s}|t_r\in R\wedge t_s\in S\} \]

    • 结果中

      • 列:(n+m)列元组的集合
        • 元组的前n列是关系R的一个元组
        • 后m列是关系S的一个元组
      • 行:k1×k2个元组

专门的关系运算

  • 先引入几个记号

    1. 设关系模式为R(A1,A2,…,An)

      • 它的一个关系设为R
      • \(t\in R\) 表示t是R的一个元组
      • \(t[A_i]\) 则表示元组t中,和属性Ai对应的一个分量
    2. \(A=\{A_{i1},A_{i2},\cdots,A_{ik}\}\) ,其中 \(A_{i1},A_{i2},\cdots,A_{ik}\)\(A_1,A_2,\cdots,A_n\) 中的一部分

      • \(A\) 称为属性列或属性组。

      • \(t[A]=(t[A_{i1}],t[A_{i2}],\cdots,t[A_{ik}])\) 表示元组t在属性列 \(A\) 上的诸分量的集合,换句话说,\(t[A_{i1}],t[A_{i2}],\cdots,t[A_{ik}]\) 是元组 \(t\) 在属性列 \(A\) 上的部分取值。

      • \(\bar{A}\) 表示除了 \(A_{i1}, A_{i2}, \cdots, A_{ik}\) 之外的其他属性,即 \(\bar{A} = \{A_1, A_2, \cdots, A_n\} - \{A_{i1}, A_{i2}, \cdots, A_{ik}\}\)

    3. \(R\)\(n\) 目关系, \(S\)\(m\) 目关系,\(t_r\in R\)\(t_s\in S\)

      • \(\overset{\frown}{t_rt_s}\) 称为元组的连接或元组的串接
      • 它是一个n+m列的元组,前n个分量为R中的一个n元组,后m个分量为S中的一个m元组。
    4. 给定一个关系 \(R(X,Z)\),X和Z为属性组

      • \(t[X]=x\) 时,x在R中的象集(Images Set)\(Z_x=\{t[Z]|t\in R,t[x]=x\}\)
    • 它表示R中属性组 X 上值为 x 的所有元组在 Z 上分量的集合
  • 设有一个学生-课程数据库,包括学生关系Student、课程关系Course和选修关系SC。下面的多个例子都使用这个数据库

选择(Selection)

  • 选择又称为限制(Restriction)

  • 选择运算符的含义

    • 在关系R中选择满足给定条件的多个元组,记作

      \[\sigma_F(R)=\{t|t\in R\wedge F(t)=true\} \]

    • F:选择条件,是一个逻辑表达式,基本形式为:\(X_1\theta Y_1\)

    • 其中 \(\theta\) 表示比较运算符,它可以是 \(\gt,\ge,\lt,\le,=或\neq\) 。X1Y1等是属性名,或为常量,或为简单函数。属性名也可以用它的序号来代替。在基本的选择条件上可以进一步进行逻辑运算,整体如下表

    • 选择运算是从关系R中选取使逻辑表达式F为真的元组,是从行的角度进行的运算

    • 在基本的选择条件上可以进一步进行逻辑运算,即进行求非、与、或运算。

  • 例:查询信息系(IS系)全体学生

    \[\sigma_{Sdept='IS'}(Student)或\sigma_{5='IS'}(Student) \]

    结果为

投影(Projection)

  • 投影运算符的含义

    • 从R中选择出若干属性列组成新的关系,记作

      \[\Pi_A(R)=\{t[A]|t\in R\} \]

    • A:R中的属性列

    • 投影操作主要是从列的角度进行运算

    注意:投影操作会进行去重

    假设有一个关系R包含以下元组:

    R = {(A: 1, B: 2), (A: 1, B: 3), (A: 2, B: 3)}

    如果我们对关系R进行投影操作,选择属性A,那么结果将是:

    π(A, R) =

  • 例:查询学生的姓名和所在系,即求Student关系上学生姓名和所在系两个属性上的投影

    \[\Pi_{Sname,Sdept}(Student)或\Pi_{2,5}(Student) \]

    结果为

连接(Join)

  • 连接也称为 \(\theta\) 连接

  • 连接运算的含义

    • 从两个关系的笛卡尔积中选取属性间满足一定条件的元组,记作

      \[R\underset{A\theta B}{\Join}S=\{\overset{\frown}{t_rt_s}|t_r\in R\wedge t_s\in S\wedge t_r[A]\theta t_s[B]\} \]

    • A和B:分别为R和S上度数相等且可比的属性组

    • θ:比较运算符

    • 连接运算从R和S的广义笛卡尔积R×S中选取,R中A属性组上的值 与 S中B属性组上值 满足比较关系θ的元组

  • 两类常用连接运算

    • 等值连接(equijoin)

      • \(\theta\) 为 = 的连接运算称为等值连接

      • 从关系R与S的广义笛卡尔积中,选取A、B属性值相等的那些元组,即等值连接为

        \[R\underset{A=B}{\Join}S=\{\overset{\frown}{t_rt_s}|t_r\in R\wedge t_s\in S\wedge t_r[A]=t_s[B]\} \]

    • 自然连接(Natural join)

      • 自然连接是一种特殊的等值连接

        • 它要求两个关系中进行比较的分量必须是相同的属性组
        • 在结果中把重复的属性列去掉
      • R和S具有相同的属性组B,U为R和S的全体属性集合

        \[R\Join S=\{\overset{\frown}{t_rt_s}|t_r\in R\wedge t_s\in S\wedge t_r[A]=t_s[B]\} \]

        在执行自然连接后的结果中,重复的属性列只会有一个

  • 例:关系R和关系S 如下所示

    一般连接 \(R\underset{C<E}{\Join}S\) 的结果如下

    等值连接 \(R\underset{R.B=S.B}{\Join}S\) 的结果如下

    自然连接结果如下

  • 两个关系R和S在做自然连接时,R和S中某些元组可能被舍弃,如果把舍弃的元组也保存在结果关系中,对应其他属性上填空值(NULL),这种就叫外连接(OUTER JOIN)。

  • 左外连接:如果只把左边关系R中要舍弃的元组保留就叫做左外连接(LEFT OUTER JOIN或LEFT JOIN)

  • 右外连接:如果只把右边关系S中要舍弃的元组保留就叫做右外连接(RIGHT OUTER JOIN或RIGHT JOIN)。

  • 下图是上面例中关系R和关系S的外连接,左外连接和右外连接

除(Division)

  • 给定关系R(X,Y)和S(Y,Z),其中X,Y,Z为属性组。

    • R中的Y与S中的Y可以有不同的属性名,但必须出自相同的域集

    • R与S的除运算得到一个新的关系P(X),P是R中满足下列条件的元组在 X 属性列上的投影

      • 元组在X上分量值x的象集 \(Y_x\) 包含S在Y上投影的集合,记作:

        \[R\div S=\{t_r[X]|t_r\in R\wedge\Pi_Y(S)\subseteq Y_x\} \]

      • \(Y_x\):x在R中的象集

  • 例:设关系R、S以及R÷S的结果分别如下图

    在关系R中,A可以取四个值{a1,a2,a3,a4}

    • a1的象集为
    • a2的象集为
    • a3的象集为
    • a4的象集为

    S在(B,C)上的投影为{(b1,c2),(b2,c1),(b2,c3)},只有a1的象集包含了S在(B,C)属性组上的投影,所以R÷S=

关系代数其他说明

  • 在8种关系代数运算中,并、差、笛卡儿积、投影和选择5种运算为基本运算;其他三种运算,即交、连接和除,均可以用这5种基本运算来表达。

  • 交运算:\(R\cap S=R-(R-S)\)

  • 连接运算:\(R\underset{A\theta B}{\Join}S=\sigma_{A\theta B}(R\times S)\)

  • 除:\(R(X,Y)\div S(Y,Z)=\Pi_X(R)-\Pi_X(\Pi_X(R)\times\Pi_Y(S)-R)\)

    • 核心思想还是元组在X上分量值x的象集 \(Y_x\) 包含S在Y上投影的集合,下面进行逐步分析:

    • 现在有两个表,SC表和C表,我们通过这两个表辅助分析

    • 首先,通过 \(\Pi_X(R)\)\(\Pi_Y(S)\) 的笛卡尔积 \(\Pi_X(R)\times\Pi_Y(S)\),即S在Y上投影的集合和X上每个分量值 \(x_i\) 进行笛卡尔积。即SC表的sno和C表的cno的笛卡尔积为

    • 接着,从 \(\Pi_X(R)\times\Pi_Y(S)\) 中减去关系R, \(\Pi_X(R)\times\Pi_Y(S)\) 可以看做是分量值 \(x_i\) 最小应该满足的象集,如果R中的某个分量值 \(x_i\) 的象集包含S在Y上投影的集合,那么减去关系R后结果中就没有含对应的分量值 \(x_i\) 的元组。对应的结果为

    • 然后对其在X属性上做投影,就是所有不满足要求的集合,用所有的分量值 \(x_i\) ,即 \(\Pi_X(R)\) 减去不满足要求的集合,就是满足要求的集合,这就是 \(\Pi_X(R)-\Pi_X(\Pi_X(R)\times\Pi_Y(S)-R)\) 的来源。对应的结果为

    在sql中如何表示除法?

    设有:学生选课关系 sc(sno,cno,score),学校课程关系 course(cno,cname)

    求至少选择了C001和C003两门课程的学生学号(其他题目),其对应的关系表达为

    \[\Pi_{sno}(sc\div\Pi_{cno}(\sigma_{cno='C001'\wedge con='C003'}(course))) \]

    select distinct sno from sc A where not exists
    (
     select * from course B where cno in ('C002','C003') and not exists
     (
         select * from sc C where A.sno=C.sno and B.cno=C.cno
     )
    )
    

    SQL语言中没有全称量词,具体实现时可以把带有全称量词的谓词转换为等价的带有存在量词的谓词。解决这类的除法问题一般采用双嵌套not exists来实现带全称量词的查询解决所谓for all的问题。

    • not exists:有数据返回时就返回false,没有数据返回时,返回true

      • 当子查询和主查询有关联条件时,相当于从主查询中去掉子查询的数据;当子查询和主查询无关联条件时,查询结果为空。

      • 这和相关子查询有关,在后面的查询连接中会学到。当子查询和主查询有关联条件时,会进入一个类似于循环的状态,从主查询中拿出一个数据,然后和内查询的关联条件进行比较,并查询出数据;如果没有关联条件,子查询相当于独立的部分,一次性执行结束

    • 例如test数据:

      id name

      1 张三

      2 李四

      select * from test c where not exists
      (select * from test t where t.id= '1' and t.id = c.id)
      

      返回2 李四

    • 因为子查询和主查询有关联条件,先是<1 张三>和子查询进行进行比较,因为查询出了数据,所以经过not exists后返回false,数据被剔除;然后<2 李四>和子查询进行进行比较,查询为为空经过not exists后返回true,数据被展示出来

    • 若查询语句改为

      select * from test c where not exists
      (select * from test t where t.id= '1')
      

      子查询相当于独立的部分,一次性执行结束,返回数据<1 张三>,经过not exists后返回false,导致所有数据都不被展示

    了解了not exists现在解释最上面的除法sql

    select distinct sno from sc A where not exists
    (
     select * from course B where cno in ('C002','C003') and not exists
     (
         select * from sc C where A.sno=C.sno and B.cno=C.cno
     )
    )
    
    • 首先,从sc表中选择所有的学生学号,去除重复的,得到A表
    • 然后,从course表中选择课程号为C002或C003的课程,得到B表
    • 接着,对于每个A表中的学号,在C表检查该学号是否含有B表中所有的课程,如果不含有所有课程那么就将该学号从A表中剔除
      • not exists版本就是:对于每个A表中的学号,在C表对该学号检查,是否有B表中的每一个课程
      • 如果有任一一个课程不存在,经过一次not exists,B就会返回一条数据,导致第二次not exists,返回结果为false,这就说明该学生没有选修该课程,该学号就会从A表中剔除

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

SQL概述

  • SQL(Structured Query Language)结构化查询语言,是关系数据库的标准语言
  • SQL是一个通用的、功能极强的关系数据库语言

SQL的产生与发展

标准 大致页数 发布日期
SQL/86 1986.10
SQL/89(FIPS 127-1) 120页 1989年
SQL/92 622页 1992年
SQL99 1700页 1999年
SQL2003 3600页 2003年
SQL2008 3777页 2009年
SQL2011 2010年

SQL的特点

综合统一

  • 集数据定义语言(DDL),数据操纵语言(DML),数据控制语言(DCL)功能于一体
  • 可以独立完成数据库生命周期中的全部活动:
    • 定义和修改、删除关系模式,定义和删除视图,插入数据,建立数据库
    • 对数据库中的数据进行查询和更新
    • 数据库重构和维护
    • 数据库安全性、完整性控制,以及事务控制
    • 嵌入式SQL和动态SQL定义
  • 用户数据库投入运行后,可根据需要随时逐步修改模式,不影响数据的运行
  • 数据操作符统一,查找、插入、删除、更新等每一种操作都只需一种操作符

高度非过程化

  • 非关系数据模型的数据操纵语言面向过程,必须制定存取路径
  • SQL只要提出做什么,无须了解存取路径
  • 存取路径的选择以及SQL的操作过程由系统自动完成。

面向集合的操作方式

  • 非关系数据模型采用面向记录的操作方式,操作对象是一条记录
  • SQL采用集合操作方式
    • 操作对象、查找结果可以是元组的集合
    • 一次插入、删除、更新操作的对象可以是元组的集合

以同一种语法结构提供多种使用方式

  • SQL是独立的语言
    • 能够独立地用于联机交互的使用方式,即用终端键入命令交互
  • SQL又是嵌入式语言
    • SQL能够嵌入到高级语言(例如C,C++,Java)程序中,供程序员设计程序时使用

语言简洁,易学易用

  • SQL功能极强,完成核心功能只用了9个动词。

SQL的基本概念

  • SQL支持关系数据库三级模式结构
  • 基本表
    • 本身独立存在的表
    • SQL中一个关系就对应一个基本表
    • 一个(或多个)基本表对应一个存储文件
    • 一个表可以带若干索引,索引也存放在存储文件中
  • 存储文件
    • 逻辑结构组成了关系数据库的内模式
    • 物理结构是任意的,对用户透明
  • 视图
    • 从一个或几个基本表导出的表
    • 数据库中只存放视图的定义而不存放视图对应的数据
    • 视图是一个虚表
    • 用户可以在视图上再定义视图

学生-课程数据库

  • 学生-课程模式 S-T:

    • 学生表:Student(Sno,Sname,Ssex,Sage,Sdept)

    • 课程表:Course(Cno,Cname,Cpno,Ccredit)

    • 学生选课表:SC(Sno,Cno,Grade)

数据定义

  • 关系数据库系统支持三级模式结构,其模式、外模式和内模式中的基本对象有模式、表、视图和索引等

    注意:后面所指的模式是基本对象的模式,和前面第一章数据库系统的三级模式结构不一样

    三级模式结构的模式是逻辑模式,是对数据库整体逻辑结构的描述

    基本对象的模式是表、视图和索引等,是对具体数据对象的描述

  • 表(Table)、视图(View)和索引(Index)是数据库中的基本对象,它们存在于模式中,并可以通过外模式提供给用户或应用程序。这些对象用于存储和操作数据,以满足用户的需求。

  • 一个关系数据库管理系统的实例(instance)中可以建立多个数据库,一个数据库中可以建立多个模式,一个模式下通常包括多个表、视图和索引等数据库对象。

模式的定义与删除

定义模式

命令行语法格式中常用符号的含义

尖括号 <> :必选参数

方括号 [] :可选参数

竖线 | :用于分隔多个互斥参数,含义为“或”,使用时只能选择一个

  • 定义模式实际上定义了一个命名空间

    • 在这个空间中可以定义该模式包含的数据库对象,例如基本表、视图、索引等。
  • 在SQL中,模式定义语句如下:

    CREATE SCHEMA <模式名> AUTHORIZATION <用户名>[<表定义子句>|<视图定义子句>|<授权定义子句>]
    
    • 要创建模式,调用该命令的用户必须拥有数据库管理员权限,或者获得了数据库管理员授予的 CREATE SCHEMA 的权限
  • 先看例子1:为用户WANG定义了一个学生-课程模式S-T

    CREATE SCHEMA "S-T" AUTHORIZATION WANG;
    
  • 例子2

    CREATE SCHEMA AUTHORIZATION WANG
    

    如果没有指定<模式名>,那么<模式名>隐含为<用户名>

  • 在CREATE SCHEMA中可以接受CREATE TABLE,CREATE VIEW和GRANT子句

  • 例子3:为用户ZHANG创建了一个模式TEST,并在其中定义了一个表TAB1

    CREATE SCHEMA TEST AUTHORIZATION ZHANG 
    CREATE TABLE TAB1(COL1 SMALLINT, COL2 INT, COL3 CHAR(20), COL4 NUMERIC(10,3), COL5 DECIMAL(5,2));
    

删除模式

  • 在SQL中,删除模式语句如下:

    DROP SCHEMA <模式名> <CASCADE|RESTRICT>
    
    • CASCADE(级联):删除模式的同时把该模式中所有的数据库对象全部删除
    • RESTRICT(限制):如果该模式中定义了下属的数据库对象(如表、视图等),则拒绝该删除语句的执行。当该模式中没有任何下属的对象时才能执行。
  • 下面这个例子会删除模式ZHANG,同时该模式中定义的表TAB1也被删除

    DROP SCHEMA ZHANG CASCADE
    

基本表的定义、删除与修改

定义基本表

  • 创建了一个模式就建立了一个数据库的命名空间,一个框架。在这个空间中首先要定义的是该模式包含的数据库基本表。

  • SQL语言使用CREATE TABLE语句定义基本表,其基本格式如下:

    CREATE TABLE <表名>(
        <列名> <数据类型> [列级完整性约束条件]
        [,<列名> <数据类型> [列级完整性约束条件]]
        ...
        [,<表级完整性约束条件> ]);
    
    • 建表时可以定义相关完整性约束,存储于数据字典。用户操作表数据时,关系数据库管理系统会自动检查是否违反这些约束。
    • 若完整性约束涉及多个属性列,需在表级定义;若仅涉及单个属性列,可自由选择表级或列级定义。
  • 例1:建立学生表Student,学号Sno是主码,姓名取值唯一

    CREATE TABLE Student (
        Sno CHAR(9) PRIMARY KEY,		/*列级完整性约束条件*/
        Sname CHAR(20) UNIQUE,			/*Sname取唯一值*/
        Ssex CHAR(2),
        Sage SMALLINT,
        Sdept CHAR(20)
    );
    
  • 例2:建立一个课程表Course

    CREATE TABLE Course (
        Cno CHAR(4) CONSTRAINT 约束名 PRIMARY KEY, /*同时添加约束名,可以方便修改*/
        Cname CHAR(40) NOT NULL,		/*列级完整性约束条件,Cname不能取空值*/
        Cpno CHAR(4),
        Ccredit SMALLINT,
        /*表级完整性约束条件,Cpno是外码,被参照表是Course,被参照列是Cno*/
        FOREIGN KEY (Cpno) REFERENCES Course(Cno)
    );
    
  • 例3:建立一个学生选课表SC

    CREATE TABLE SC (
        Sno CHAR(9),
        Cno CHAR(4),
        Grade SMALLINT,
        PRIMARY KEY (Sno, Cno),/*主码由两个属性构成,必须作为表级完整性进行定义*/
        /*表级完整性约束条件,Sno是外码,被参照表是Student */
        FOREIGN KEY (Sno) REFERENCES Student(Sno),
        /* 表级完整性约束条件, Cno是外码,被参照表是Course*/
        FOREIGN KEY (Cno) REFERENCES Course(Cno)
    );
    

数据类型

  • SQL中域的概念用数据类型来实现

  • 定义表的属性时需要指明其数据类型及长度,数据类型是选取要考虑下面几个方面

    • 选用哪种数据类型
    • 取值范围
    • 要做哪些运算
  • 下图为常用数据类型

模式与表

  • 每一个基本表都属于某一个模式,一个模式包含多个基本表

  • 定义基本表所属模式

    • 方法一:在表名中明确地呈现模式名称

      Create table "S-T".Student(......);	/*模式名为 S-T*/
      Create table "S-T".Cource(......);
      Create table "S-T".SC(......);
      
    • 方法二:在创建模式语句中同时创建表

    • 方法三:设置所属的模式

  • 在创建基本表(以及其他数据库对象)时,如果没有指定模式,系统会采取以下操作

    1. 根据搜索路径来确定该对象所属的模式

    2. RDBMS会使用模式列表中第一个存在的模式作为数据库对象的模式名。如果搜索路径中的模式名都不存在,系统将给出错误

    3. 可以通过命令 SHOW search_path; 可以显示当前的搜索路径

    4. 搜索路径的当前默认值是:$user, PUBLIC。这意味着系统首先搜索与用户名相同的模式名,如果该模式名不存在,则使用PUBLIC模式

    5. DBA用户可以设置搜索路径,然后定义基本表。例如:

      SET search_path TO "S-T", PUBLIC;
      Create table Student(......);
      
      • 这样的操作会创建一个名为S-T.Student的基本表
      • 因为RDBMS发现搜索路径中第一个模式名S-T存在,所以它将该模式作为基本表Student所属的模式

修改基本表

  • SQL语言用ALTER TABLE语句修改基本表,其一般格式为

    ALTER TABLE <表名>
    [ADD [COLUMN] <新列名> <数据类型> [完整性约束]]
    [ADD CONSTRAINT <完整性约束名>...]
    [DROP [COLUMN] <列名> [CASCADE|RESTRICT]]
    [DROP CONSTRAINT <完整性约束名> [CASCADE|RESTRICT]]
    [ALTER COLUMN <列名> <新的列名|数据类型>];
    
    • 使用 ADD 子句可以增加新列、新的列级完整性约束条件以及新的表级完整性约束条件
    • 通过 DROP COLUMN 子句,可以删除表中的列。如果指定了 CASCADE 短语,系统将自动删除引用了该列的其他对象,例如视图。若指定了 RESTRICT 短语,那么如果该列被其他对象引用,RDBMS 将拒绝删除该列
    • 使用 DROP CONSTRAINT 子句可以删除指定的完整性约束条件
    • 利用 ALTER COLUMN 子句可以修改原有的列定义,包括更改列名和数据类型
  • 例1:向Student表增加入学时间列,其数据类型为日期型

    ALTER TABLE Student ADD S_entrance DATE;
    
    • 不论基本表中原来是否已有数据,新增加的列一律为空值
  • 例2:将年龄的数据类型由字符型(假设原来的数据类型是字符型)改为整数

    ALTER TABLE Student ALTER COLUMN Sage INT;
    
  • 例3:增加课程名称必须取唯一值的约束条件

    ALTER TABLE Course ADD UNIQUE(Cname);
    

删除基本表

  • 删除基本表一般格式为:

    DROP TABLE <表名> [RESTRICT|CASCADE];
    
    • RESTRICT:删除表是有限制的
      • 删除基本表时,需确保其不被其他表的约束、视图、触发器、存储过程或函数等引用
      • 如果存在依赖该表的对象,则此表不能被删除
    • CASCADE:删除该表没有限制
      • 在删除基本表的同时,相关的依赖对象一起删除
    • 默认情况是RESTRICT
  • 例1:删除Student表

    DROP TABLE Student CASCADE;
    
    • 基本表定义被删除,数据被删除
    • 表上建立的索引、视图、触发器等一般也将被删除
  • 例2:若表上建有视图,选择RESTRICT时表不能删除

    CREATE VIEW IS_Student AS
    SELECT Sno, Sname, Sage
    FROM Student
    WHERE Sdept = 'IS';
    
    DROP TABLE Student RESTRICT;
    --ERROR: cannot drop table Student because other objects depend on it
    
  • 例3:如果选择CASCADE时可以删除表,视图也自动被删除

    DROP TABLE Student CASCADE;
    --NOTICE: drop cascades to view IS_Student
    SELECT * FROM IS_Student;
    --ERROR: relation "IS_Student" does not exist
    
  • DROP TABLE时,SQL99与3个RDBMS的处理策略比较

    • R表示RESTRICT,C表示CASCADE
    • '×'表示不能删除基本表,'√'表示能删除基本表,‘保留’表示删除基本表后,还保留依赖对象

索引的建立与删除

索引

  • 建立索引的目的:加快查询速度
    • 谁可以建立索引
      • DBA或表的属主(即建立表的人)
      • DBMS一般会自动建立以下列上的索引
        • PRIMARY KEY
        • UNIQUE
    • 谁维护索引
      • DBMS自动完成
    • 使用索引
      • DBMS自动选择是否使用索引以及使用哪些索引
  • RDBMS中索引一般采用B+树、HASH索引来实现
    • B+树索引具有动态平衡的优点
    • HASH索引具有查找速度快的特点
  • 采用B+树,还是HASH索引则由具体的RDBMS来决定
  • 索引是关系数据库的内部实现技术,属于内模式的范畴
  • CREATE INDEX语句定义索引时,可以定义索引是唯一索引、非唯一索引或聚簇索引

建立索引

  • 在SQL语言中,建立索引使用CREATE INDEX语句,其一般格式为

    CREATE [UNIQUE] [CLUSTER] INDEX <索引名> 
    ON <表名>(<列名>[<次序>][,<列名>[<次序>]]...);
    
    • <表名> 是要建索引的基本表的名字
    • 索引可以建立在该表的一列或多列上,各列之间用逗号分隔
    • 每个 <列名> 后面还可以用 <次序> 指定索引值的排列次序,可选 ASC(升序) 或 DESC(降序),默认值为 ASC
    • UNIQUE 表明此索引的每一个索引值只对应唯一的数据记录
    • CLUSTER 表示要建立的索引是聚簇索引
  • 例1:在Student表的Sname(姓名)列上建立一个聚簇索引

    CREATE CLUSTER INDEX Stusname ON Student(Sname);
    
    • 在最经常查询的列上建立聚簇索引以提高查询效率
    • 一个基本表上最多只能建立一个聚簇索引
    • 经常更新的列不宜建立聚簇索引
  • 例2:为学生-课程数据库中的Student,Course,SC三个表建立索引

    CREATE UNIQUE INDEX Stusno ON Student(Sno);
    CREATE UNIQUE INDEX Coucno ON Course(Cno);
    CREATE UNIQUE INDEX SCno ON SC(Sno ASC,Cno DESC);
    
    • Student表按学号升序建唯一索引
    • Course表按课程号升序建唯一索引
    • SC表按学号升序和课程号降序建唯一索引

修改索引

  • 当数据增、删、改频繁时,维护索引会花费大量时间,降低查询效率。此时,可以删除不必要的索引以优化系统

  • 对于已经建立的索引,如果需要对其重新命名,其一般格式为

    ALTER INDEX <旧索引名> RENAME TO <新索引名>;
    

删除索引

  • 在SQL中,删除索引使用DROP INDEX语句,其一般格式为

    DROP INDEX <索引名>;
    
  • 删除索引时,系统会从数据字典中删去有关该索引的描述

  • 例1:删除Student表的Stusname索引

    DROP INDEX Stusname;
    

数据查询

  • 查询语句一般为:

    SELECT [ALL|DISTINCT] <目标列表达式> [,<目标列表达式>]...
    FROM <表名或视图名> [,<表名或视图名>...] | (<SELECT 语句> [AS] <别名>)
    [WHERE <条件表达式>]
    [GROUP BY <列名1>[HAVING <条件表达式>]]
    [ORDER BY <列名2> [ASC|DESC]];
    

    sql的执行顺序为

    1. FROM 子句:根据 FROM 子句中指定的表,获取所有涉及的表数据
    2. JOIN 子句:如果查询中包含 JOIN 子句,那么根据 JOIN 条件连接相关的表
    3. WHERE 子句:如果查询中包含 WHERE 子句,那么根据 WHERE 子句中的条件筛选数据
    4. GROUP BY 子句:如果查询中包含 GROUP BY 子句,按照 <列名1> 进行分组
    5. HAVING 子句:如果查询中包含 HAVING 子句,那么对分组后的数据进行筛选
    6. SELECT 子句:最后,根据 SELECT 子句中指定的列,从结果表中选择需要的列。
    7. DISTINCT 关键字:如果查询中包含 DISTINCT 关键字,那么去除结果集中的重复记录,DISTINCT 操作在 SELECT 子句之后执行
    8. ORDER BY 子句:对结果集进行排序。根据 ORDER BY 子句指定的列和顺序对结果集进行排序
    9. LIMIT 和 OFFSET 子句:如果查询中包含 LIMIT 和 OFFSET 子句,那么限制结果集的数量并实现分页功能
  • 下面以学生-课程数据库为例说明SELECT语句的各种用法

单表查询

  • 单表查询是指查询仅涉及一个表

选择表中的若干列

查询指定列
  • 例1:查询全体学生的学号与姓名

    SELECT Sno, Sname
    FROM Student;
    
  • 例2:查询全体学生的姓名、学号、所在系

    SELECT Sname, Sno, Sdept
    FROM Student;
    
查询全部列
  • 选出所有属性列有两种方法:

    1. 在SELECT关键字后面列出所有列名
    2. 将<目标列表达式>指定为 *
  • 例3:查询全体学生的详细记录

    SELECT Sno, Sname, Ssex, Sage, Sdept
    FROM Student;
    

    等价于

    SELECT *
    FROM Student;
    
查询经过计算的值
  • SELECT子句的<目标列表达式>可以为:

    • 算术表达式
    • 字符串常量
    • 函数
    • 列别名
  • 例4:查全体学生的姓名及其出生年份

    SELECT Sname, 2004-Sage    /*假定当年的年份为2004年*/
    FROM Student;
    
  • 例5:查询全体学生的姓名、出生年份和所有系,要求用小写字母表示所有系名

    SELECT Sname NAME, 'Year of Birth:' BIRTH,
        2004 - Sage BIRTHDAY, ISLOWER(Sdept) DEPARTMENT
    FROM Student;
    

    结果为:

选择表中的若干元组

消除取值重复的行
  • 如果没有指定DISTINCT关键词,则缺省为ALL

  • 例6:查询选修了课程的学生学号

    SELECT Sno FROM SC;
    

    等价于

    SELECT ALL Sno FROM SC;
    
  • 执行上面的SELECT语句后,结果为:

  • 指定DISTINCT关键词,去掉表中重复的行

    SELECT DISTINCT Sno FROM SC;
    

    执行结果:

查询满足条件的元组
  • 查询满足指定条件的元组可以通过WHERE子句实现,常用的查询条件如下表

  • 比较大小

    • 例7:查询计算机科学系全体学生的名单

      SELECT Sname
      FROM Student
      WHERE Sdept = 'CS';
      
    • 关系数据库管理系统执行查询的过程可能是:全表扫描 Student 表,如果 Sdept 列的值等于'CS',则输出 Sname 列的值。如果不等于,则跳过。重复此过程,直至处理完所有元组。

    • 如果学校有数万学生,计算机系的学生约占 5%,可以在 Student 表的 Sdept 列上建立索引。系统会利用索引找出 Sdept='CS'的元组,从中取出 Sname 列值形成结果关系,从而避免全表扫描,提高查询速度。但如果学生数量较少,索引查找可能无法提高效率,系统仍会使用全表扫描。这是由查询优化器根据规则或估计的执行代价来决定的。

    • 例8:查询所有年龄在20岁以下的学生姓名及其年龄

      SELECT Sname, Sage
      FROM Student
      WHERE Sage < 20;
      
    • 例9:查询考试成绩有不及格的学生的学号

      SELECT DISTINCT Sno
      FROM SC
      WHERE Grade < 60;
      
  • 确定范围

    • 例10:查询年龄在20~23岁(包括20岁和23岁)之间的学生的姓名、系别和年龄

      SELECT Sname, Sdept, Sage
      FROM Student
      WHERE Sage BETWEEN 20 AND 23;
      
    • 例11:查询年龄不在20~23岁之间的学生姓名、系别和年龄

      SELECT Sname, Sdept, Sage
      FROM Student
      WHERE Sage NOT BETWEEN 20 AND 23;
      
  • 确定集合

    • 例12:查询信息系(IS)、数学系(MA)和计算机科学系(CS)学生的姓名和性别

      SELECT Sname, Ssex
      FROM Student
      WHERE Sdept IN ('IS', 'MA', 'CS');
      
    • 例13:查询既不是信息系、数学系,也不是计算机科学系的学生的姓名和性别

      SELECT Sname, Ssex
      FROM Student
      WHERE Sdept NOT IN ('IS', 'MA', 'CS');
      
  • 字符匹配

    • 谓词格式:[NOT] LIKE '<匹配串>' [ESCAPE '<换码字符>']
      • % 代表任意长度(长度可以为0)的字符串
      • _ 代表任意单个字符
      • ESCAPE '<换码字符>' :如在匹配串中含有 _ 就需要转换,使用 ESCAPE '\' 表示 \ 为换码字符,这样匹配串中紧跟在 \ 后面的字符 _ 就不再有通配符的含义
    1. 匹配串为固定字符串

      • 例14:查询学号为200215121的学生的详细情况

        SELECT *
        FROM Student
        WHERE Sno LIKE '200215121';
        

        等价于

        SELECT *
        FROM Student
        WHERE Sno = '200215121';
        
    2. 匹配串为含通配符的字符串

      • 例15:查询所有姓刘学生的姓名、学号和性别

        SELECT Sname, Sno, Ssex
        FROM Student
        WHERE Sname LIKE '刘%';
        
      • 例16:查询姓"欧阳"且全名为三个汉字的学生的姓名

        SELECT Sname
        FROM Student
        WHERE Sname LIKE '欧阳__';
        
      • 例17:查询名字中第2个字为"阳"字的学生的姓名和学号

        SELECT Sname, Sno
        FROM Student
        WHERE Sname LIKE '__阳%';
        
      • 例18:查询所有不姓刘的学生姓名

        SELECT Sname, Sno, Ssex
        FROM Student
        WHERE Sname NOT LIKE '刘%';
        
    3. 使用换码字符将通配符转义为普通字符

      • 例19:查询DB_Design课程的课程号和学分

        SELECT Cno, Ccredit
        FROM Course
        WHERE Cname LIKE 'DB\_Design' ESCAPE '\';
        
      • 例20:查询以"DB_"开头,且倒数第3个字符为i的课程的详细情况

        SELECT *
        FROM Course
        WHERE Cname LIKE 'DB\_%i_ _' ESCAPE '\';
        
    4. 涉及空值的查询

      • 例21:某些学生选修课程后没有参加考试,所以有选课记录,但没有考试成绩。查询缺少成绩的学生的学号和相应的课程号

        SELECT Sno, Cno
        FROM SC
        WHERE Grade IS NULL;
        
      • “IS” 不能用 “=” 代替

      • 例22:查所有有成绩的学生学号和课程号

        SELECT Sno, Cno
        FROM SC
        WHERE Grade IS NOT NULL;
        
    5. 多重条件查询

      • 逻辑运算符:AND和OR来联结多个查询条件

        • AND的优先级高于OR
        • 可以用括号改变优先级
      • 例23:查询计算机系年龄在20岁以下的学生姓名

        SELECT Sname
        FROM Student
        WHERE Sdept='CS' AND Sage<20;
        
      • 改写例12

        SELECT Sname, Ssex
        FROM  Student
        WHERE Sdept='IS' OR Sdept='MA' OR Sdept='CS';
        

ORDER BY子句

  • ORDER BY子句:可以按一个或多个属性列排序。升序:ASC;降序:DESC;缺省值为升序

  • 当排序列含空值时

    • ASC:排序列为空值的元组最后显示
    • DESC:排序列为空值的元组最先显示
  • 例24:查询选修了3号课程的学生的学号及其成绩,查询结果按分数降序排列

    SELECT Sno, Grade
    FROM  SC
    WHERE  Cno='3'
    ORDER BY Grade DESC;
    
  • 例25:查询全体学生情况,查询结果按所在系的系号升序排列,同一系中的学生按年龄降序排列

    SELECT  *
    FROM  Student
    ORDER BY Sdept, Sage DESC;
    

聚集函数

  • 聚集函数主要有以下几种

    • 计数

      • COUNT([DISTINCT|ALL] *)
      • COUNT([DISTINCT|ALL] <列名>)
    • 计算总和

      • SUM([DISTINCT|ALL] <列名>)
    • 计算平均值

      • AVG([DISTINCT|ALL] <列名>)
    • 最大最小值

      • MAX([DISTINCT|ALL] <列名>)
      • MIN([DISTINCT|ALL] <列名>)
    • ALL为默认值

  • 例26:查询学生总人数

    SELECT COUNT(*)
    FROM  Student;
    
  • 例27:查询选修了课程的学生人数

    SELECT COUNT(DISTINCT Sno)
    FROM SC;
    
  • 例28:计算1号课程的学生平均成绩

    SELECT AVG(Grade)
    FROM SC
    WHERE Cno='1';
    
  • 例29:查询选修1号课程的学生最高分数

    SELECT MAX(Grade)
    FROM SC
    WHER Cno='1';
    
  • 例30:查询学生200215012选修课程的总学分数

    SELECT SUM(Ccredit)
    FROM  SC, Course
    WHER Sno='200215012' AND SC.Cno=Course.Cno; 
    
  • 当聚集函数遇到空值时,除 COUNT(*) 外,都跳过空值而只处理非空值COUNT(*) 是对元组进行计数,某个元组的一个或部分列取空值不影响COUNT的统计结果。

注意:WHERE子句中是不能用聚集函数作为条件表达式的。聚集函数只能用SELECT子句和GROUP BY中的HAVING子句,这跟SQL语句的执行顺序有关

GROUP BY子句

  • GROUP BY子句作用是按指定的一列或多列值分组,值相等的为一组

  • 使用GROUP BY子句后可以细化聚集函数的作用对象

    • 未对查询结果分组,聚集函数将作用于整个查询结果
    • 对查询结果分组后,聚集函数将分别作用于每个组
  • 例31:求各个课程号及相应的选课人数

    SELECT Cno, COUNT(Sno)
    FROM SC
    GROUP BY Cno;
    

    查询结果为

  • 例32:查询选修了3门以上课程的学生学号

    SELECT Sno
    FROM SC
    GROUP BY Sno
    HAVING COUNT(*)>3;
    
  • HAVING短语与WHERE子句的区别

    • 作用对象不同
    • WHERE子句作用于基表或视图,从中选择满足条件的元组
    • HAVING短语作用于组,从中选择满足条件的组

连接查询

  • 连接查询:同时涉及多个表的查询

  • 连接条件或连接谓词:用来连接两个表的条件,一般格式:

    [<表名1>.]<列名1> <比较运算符> [<表名2>.]<列名2>

    [<表名1>.]<列名1> BETWEEN [<表名2>.]<列名2> AND [<表名2>.]<列名3>

  • 连接字段:连接谓词中的列名称

  • 连接条件中的各连接字段类型必须是可比的,但名字不必是相同的

  • 连接操作的执行过程

    • 嵌套循环法(NESTED-LOOP)
      • 首先在表1中找到第一个元组,然后从头开始扫描表2,逐一查找满足连接件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组
      • 表2全部查找完后,再找表1中第二个元组,然后再从头开始扫描表2,逐一查找满足连接条件的元组,找到后就将表1中的第二个元组与该元组拼接起来,形成结果表中一个元组
      • 重复上述操作,直到表1中的全部元组都处理完毕
    • 排序合并法(SORT-MERGE)
      • 常用于=连接
      • 首先按连接属性对表1和表2排序
      • 对表1的第一个元组,从头开始扫描表2,顺序查找满足连接条件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组。当遇到表2中第一条大于表1连接字段值的元组时,对表2的查询不再继续
      • 找到表1的第二条元组,然后从刚才的中断点处继续顺序扫描表2,查找满足连接条件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组。直接遇到表2中大于表1连接字段值的元组时,对表2的查询不再继续
      • 重复上述操作,直到表1或表2中的全部元组都处理完毕为止
    • 索引连接(INDEX-JOIN)
      • 对表2按连接字段建立索引
      • 对表1中的每个元组,依次根据其连接字段值查询表2的索引,从中找到满足条件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组

等值与非等值连接查询

  • 等值连接:连接运算符为 =

  • 例33:查询每个学生及其选修课程的情况

    SELECT Student.*, SC.*
    FROM Student, SC
    WHERE Student.Sno = SC.Sno;
    

    结果为

  • 自然连接:在等值连接中把目标列中重复的属性列去掉

  • 例34:对例33用自然连接完成

    SELECT *
    FROM Student natural join SC;
    

自身连接

  • 自身连接:一个表与其自己进行连接

  • 需要给表起别名以示区别

  • 由于所有属性名都是同名属性,因此必须使用别名前缀

  • 例35:查询每一门课的间接先修课即先修课的先修课

    SELECT FIRST.Cno, SECOND.Cpno
    FROM Course FIRST, Course SECOND
    WHERE FIRST.Cpno = SECOND.Cno;
    

    查询结果:

外连接

  • 外连接与普通连接的区别

    • 普通连接操作只输出满足连接条件的元组
    • 外连接操作以指定表为连接主体,将主体表中不满足连接条件的元组一并输出
  • 例36:改写例33,查询每个学生及其选修课程的情况

    SELECT Student.Sno, Sname, Ssex, Sage, Sdept, Cno, Grade
    FROM Student LEFT OUT JOIN SC ON (Student.Sno=SC.Sno);
    

    执行结果:

  • 左外连接

    • 列出左边关系(如本例Student)中所有的元组
  • 右外连接

    • 列出右边关系中所有的元组

其他连接方式

参考内连接、外连接、左连接、右连接、全连接

  • 还有一个笛卡儿积(Cartesian Product)

  • 例:有两个关系S(A,B,C,D)和T(C,D,E,F),求 \(\Pi_{C,D}(S)\times T\) 的SQL表达

    SELECT S1.C, S1.D, T.*
    FROM (SELECT DISTINCT C, D FROM S) AS S1
    CROSS JOIN T;
    

复合条件连接

  • 复合条件连接:WHERE子句中含多个连接条件

  • 例37:查询选修2号课程且成绩在90分以上的所有学生

    SELECT Student.Sno, Sname
    FROM Student, SC
    WHERE Student.Sno = SC.Sno AND		/* 连接谓词*/
          SC.Cno = '2' AND SC.Grade > 90; /* 其他限定条件 */
    
  • 例38:查询每个学生的学号、姓名、选修的课程名及成绩

    SELECT Student.Sno, Sname, Cname, Grade
    FROM Student, SC, Course		/*多表连接*/
    WHERE Student.Sno = SC.Sno and SC.Cno = Course.Cno;
    

嵌套查询

  • 嵌套查询概述

    • 一个SELECT-FROM-WHERE语句称为一个查询块
    • 将一个查询块嵌套在另一个查询块的WHERE子句或HAVING短语的条件中的查询称为嵌套查询
  • 例如

    SELECT Sname		/*外层查询/父查询*/
    FROM Student
    WHERE Sno IN (
              SELECT Sno	/*内层查询/子查询*/
              FROM SC
              WHERE Cno = '2');
    
  • 子查询的限制:不能使用ORDER BY子句

  • 层层嵌套方式反映了SQL语言的结构化

  • 有些嵌套查询可以用连接运算替代

  • 嵌套查询求解方法

    • 不相关子查询:子查询的查询条件不依赖于父查询
      • 由里向外逐层处理。即每个子查询在上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件
    • 相关子查询:子查询的查询条件依赖于父查询
      • 首先取外层查询中表的第一个元组,根据它与内层查询相关的属性值处理内层查询,若WHERE子句返回值为真,则取此元组放入结果表
      • 然后再取外层表的下一个元组
      • 重复这一过程,直至外层表全部检查完为止

带有IN谓词的子查询

  • 例39:查询与“刘晨”在同一个系学习的学生

    1. 确定“刘晨”所在系名

      SELECT Sdept 
      FROM Student 
      WHERE Sname = '刘晨';
      

      结果为:CS

    2. 查找所有在CS系学习的学生

      SELECT Sno, Sname, Sdept
      FROM Student
      WHERE Sdept = 'CS';
      

      结果为:

    3. 将第一步查询嵌入到第二步查询的条件中

      SELECT Sno, Sname, Sdept
      FROM Student
      WHERE Sdept IN (
                SELECT Sdept
                FROM Student
                WHERE Sname = '刘晨');
      

      此查询为不相关子查询

  • 用自身连接完成例39查询要求

    SELECT S1.Sno, S1.Sname, S1.Sdept
    FROM Student S1, Student S2
    WHERE S1.Sdept = S2.Sdept AND S2.Sname = '刘晨';
    
  • 例40:查询选修了课程名为“信息系统”的学生学号和姓名

    SELECT Sno, Sname 				③ 最后在Student关系中取出Sno和Sname
    FROM Student
    WHERE Sno IN (
              SELECT Sno 			② 然后在SC关系中找出选修了3号课程的学生学号
              FROM SC
              WHERE Cno IN (
                        SELECT Cno	① 首先在Course关系中找出“信息系统”的课程号,为3号
                        FROM Course 
                        WHERE Cname = '信息系统'
                   )
         );
    

    用连接查询实现

    SELECT Sno, Sname
    FROM Student, SC, Course
    WHERE Student.Sno = SC.Sno
         AND SC.Cno = Course.Cno
         AND Course.Cname = '信息系统';
    

带有比较运算符的子查询

  • 当能确切知道内层查询返回单值时,可用比较运算符(>,<,=,>=,<=,!=或< >)

  • 与ANY或ALL谓词配合使用

  • 例:假设一个学生只可能在一个系学习,并且必须属于一个系,如在例39中,查询与“刘晨”在同一个系学习的学生,可以用=代替IN:

    SELECT Sno, Sname, Sdept
    FROM Student
    WHERE Sdept = (
              SELECT Sdept
              FROM Student
              WHERE Sname = '刘晨' );
    
  • 例41:找出每个学生超过他选修课程平均成绩的课程号

    SELECT Sno, Cno
    FROM SC x
    WHERE Grade >=(
              SELECT AVG(Grade)
              FROM SC y
              WHERE y.Sno = x.Sno );
    

    此查询为相关子查询

    • 可能的执行过程:

      1. 从外层查询中取出SC的一个元组x,将元组x的Sno值(200215121)传送给内层查询

        SELECT AVG(Grade)
        FROM SC y
        WHERE y.Sno = '200215121';
        
      2. 执行内层查询,得到值88(近似值),用该值代替内层查询,得到外层查询

        SELECT Sno, Cno
        FROM SC x
        WHERE Grade >= 88;
        
      3. 执行这个查询,得到

        (200215121, 1)

        (200215121, 3)

      4. 外层查询取出下一个元组重复做上述1至3步骤,直到外层的SC元组全部处理完毕。结果为

        (200215121, 1)

        (200215121, 3)

        (200215122, 2)

带有ANY(SOME)或ALL谓词的子查询

  • 谓词语义

    • ANY:任意一个值
    • ALL:所有值
  • 需要配合使用比较运算符

  • 例42:查询其他系中比计算机科学某一学生年龄小的学生姓名和年龄

    SELECT Sname, Sage
    FROM Student
    WHERE Sage < ANY (
              SELECT Sage
              FROM Student
              WHERE Sdept = 'CS')
         AND Sdept <> 'CS';				/*父查询块中的条件 */
    

    结果如下:

  • 执行过程:

    1. RDBMS执行此查询时,首先处理子查询,找出CS系中所有学生的年龄,构成一个集合(20,19)
    2. 处理父查询,找所有不是CS系且年龄小于 20 或 19的学生
  • 用聚集函数实现例42

    SELECT Sname, Sage
    FROM Student
    WHERE Sage < (
              SELECT MAX(Sage)
              FROM Student
              WHERE Sdept = 'CS')
         AND Sdept <> 'CS';
    
  • 例43:查询其他系中比计算机科学系所有学生年龄都小的学生姓名及年龄

    • 方法一:用ALL谓词

      SELECT Sname, Sage
      FROM Student
      WHERE Sage < ALL (
                SELECT Sage
                FROM Student
                WHERE Sdept = 'CS'     )
           AND Sdept <> 'CS';
      
    • 方法二:用聚集函数

      SELECT Sname, Sage
      FROM Student
      WHERE Sage < (
                SELECT MIN(Sage)
                FROM Student
                WHERE Sdept = 'CS'     )
           AND Sdept <> 'CS';
      
  • ANY(或SOME),ALL谓词与聚集函数、IN谓词的等价转换关系

带有EXISTS谓词的子查询

  • EXISTS谓词

    • 存在量词 \(\exist\)
    • 带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”
      • 若内层查询结果非空,则外层的WHERE子句返回真值
      • 若内层查询结果为空,则外层的WHERE子句返回假值
    • 由EXISTS引出的子查询,其目标列表达式通常都用*,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义
  • NOT EXISTS谓词

    • 若内层查询结果非空,则外层的WHERE子句返回假值
    • 若内层查询结果为空,则外层的WHERE子句返回真值
  • 例44:查询所有选修了1号课程的学生姓名

    • 思路分析:
      • 本查询涉及Student和SC关系
      • 在Student中依次取每个元组的Sno值,用此值去检查SC关系
      • 若SC中存在这样的元组,其Sno值等于此Student.Sno值,并且其Cno= '1',则取此Student.Sname送入结果关系
    SELECT Sname
    FROM Student
    WHERE EXISTS (
              SELECT *
              FROM SC
              WHERE Sno = Student.Sno AND Cno = '1' );
    
  • 用连接运算

    SELECT Sname
    FROM Student, SC
    WHERE Student.Sno = SC.Sno AND SC.Cno = '1';
    
  • 不同形式的查询间的替换

    • 一些带EXISTS或NOT EXISTS谓词的子查询不能被其他形式的子查询等价替换
    • 所有带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换
  • 用EXISTS/NOT EXISTS实现全称量词(难点)

    • SQL语言中没有全称量词 \(\forall\)(For all)

    • 可以把带有全称量词的谓词转换为等价的带有存在量词的谓词

      \[(\forall x)P\equiv\neg(\exist x(\neg P)) \]

      • (∀x)的意思是对于所有x,P为真
      • ¬(∃x(¬P))的意思是不存在一个x,使得P不为真。
  • 例:改写例39,查询与“刘晨”在同一个系学习的学生。可以用带EXISTS谓词的子查询替换

    SELECT Sno, Sname, Sdept
    FROM Student S1
    WHERE EXISTS (
              SELECT *
              FROM Student S2
              WHERE S2.Sdept = S1.Sdept AND S2.Sname = '刘晨');
    
  • 例46:查询选修了全部课程的学生姓名,即没有一门课程是他不选修的

    SELECT Sname
    FROM Student
    WHERE NO EXISTS (
              SELECT *
              FROM Course
              WHERE NOT EXISTS(
                        SELECT *
                        FROM SC
                        WHERE Sno = Student.Sno
                             AND Cno = Course.Cno
                   )
         );
    
  • 用EXISTS/NOT EXISTS实现逻辑蕴函(难点)

    • SQL语言中没有蕴函(Implication)逻辑运算

    • 可以利用谓词演算将逻辑蕴函谓词等价转换为:

      \[p\rightarrow q\equiv\neg p\vee q \]

  • 例47:查询至少选修了学生200215122选修的全部课程的学生号码

    • 解题思路

      • 用逻辑蕴函表达:查询学号为x的学生,对所有的课程y,只要200215122学生选修了课程y,则x也选修了y

      • 形式化表示:

        • 用P表示谓词:学生200215122选修了课程y
        • 用q表示谓词:学生x选修了课程y
        • 则上述查询为:\((\forall y)p\rightarrow q\)
      • 等价变换:

        \[\begin{aligned} (\forall y)p\rightarrow q&\equiv\neg(\exist y(\neg(p\rightarrow q)))\\ &\equiv\neg(\exist y(\neg(\neg p\vee q)))\\ &\equiv\neg(\exist y(p\wedge\neg q)) \end{aligned} \]

      • 变换后语义:不存在这样的课程y,学生201215122选修了y,而学生x没有选

      用NOT EXISTS谓词表示:

      SELECT DISTINCT Sno
      FROM SC SCX
      WHERE NOT EXISTS (
                SELECT *
                FROM SC SCY
                WHERE SCY.Sno = '200215122'
                     AND NOT EXISTS (
                          SELECT *
                          FROM SC SCZ
                          WHERE SCZ.Sno = SCX.Sno
                               AND SCZ.Cno = SCY.Cno
                     )
           );
      

集合查询

  • 集合操作的种类

    • 并操作UNION
    • 交操作INTERSECT
    • 差操作EXCEPT
  • 参加集合操作的各查询结果的列数必须相同;对应项的数据类型也必须相同

  • 例48:查询计算机科学系的学生及年龄不大于19岁的学生

    SELECT *
    FROM Student
    WHERE Sdept = 'CS'
    UNION
    SELECT *
    FROM Student
    WHERE Sage <= 19;
    
    • UNION:将多个查询结果合并起来时,系统自动去掉重复元组。
    • UNION ALL:将多个查询结果合并起来时,保留重复元组

    也可以用以下方式表示

    SELECT DISTINCT *
    FROM Student
    WHERE Sdept = 'CS' OR Sage <= 19;
    
  • 例49:查询选修了课程1或者选修了课程2的学生

    SELECT Sno
    FROM SC
    WHERE Cno = '1'
    UNION
    SELECT Sno
    FROM SC
    WHERE Cno = '2';
    
  • 例50:查询计算机科学系的学生与年龄不大于19岁的学生的交集

    SELECT *
    FROM Student
    WHERE Sdept = 'CS' 
    INTERSECT
    SELECT *
    FROM Student
    WHERE Sage <= 19;
    

    实际上就是查询计算机科学系中年龄不大于19岁的学生

    SELECT *
    FROM Student
    WHERE Sdept = 'CS' AND Sage <= 19;
    
  • 例51:查询选修课程1的学生集合与选修课程2的学生集合的交集

    SELECT Sno
    FROM SC
    WHERE Cno = '1'
    INTERSECT
    SELECT Sno
    FROM SC
    WHERE Cno = '2';
    

    实际上是查询既选修了课程1又选修了课程2的学生

    SELECT Sno
    FROM SC
    WHERE Cno = '1'
         AND Sno IN (
              SELECT Sno
              FROM SC
              WHERE Cno = '2'
         );
    
  • 例52:查询计算机科学系的学生与年龄不大于19岁的学生的差集

    SELECT *
    FROM Student
    WHERE Sdept = 'CS'
    EXCEPT
    SELECT *
    FROM Student
    WHERE Sage <= 19;
    

    实际上是查询计算机科学系中年龄大于19岁的学生

    SELECT *
    FROM Student
    WHERE Sdept = 'CS' AND Sage > 19;
    

基于派生表的查询

  • 子查询不仅可以出现在WHERE子句中,还可以出现在FROM子句中,这时子查询生成的临时派生表(derived table)成为主查询的查询对象。

  • 例如,找出每个学生超过他自己选修课程平均成绩的课程号

    SELECT Sno, Cno
    FROM SC, (SELECT Sno, Avg(Grade)
              FROM SC
              GROUP BY Sno
         ) AS Avg_sc(avg_sno, avg_grade)
    WHERE
         SC.Sno = Avg_sc.avg_sno and SC.Grade >= Avg_sc.avg_grade
    
  • 如果子查询中没有聚集函数,派生表可以不指定属性列,子查询SELECT子句后面的名为其默认属性

  • 通过FROM子句生成派生表时,AS关键字可以省略,但必须为派生关系指定一个别名

  • SELECT语句的一般格式

    SELECT [ALL|DISTINCT]<目标列表达式>[别名][,<目标列表达式>[别名]]...
    FROM <表名或视图名>[别名][,<表名或视图名>[别名]]...|(<SELECT 语句>) [AS] <别名>
    [WHERE <条件表达式>]
    [GROUP BY <列名1> [HAVING <条件表达式>]]
    [ORDER BY <列名2> [ASC|DESC]];
    

数据更新

插入数据

  • 两种插入数据方式
    1. 插入元组
    2. 插入子查询结果:可以一次插入多个元组

插入元组

  • 语句格式为

    INSERT INTO <表名> [(<属性列1>[,<属性列2>]...)]
    VALUES (<常量1> [,<常量2>]...)
    
  • 功能:将新元组插入指定表中

  • INTO子句

    • 属性列的顺序可与表定义中的顺序不一致
    • 没有指定属性列,则新插入的元组必须在每个属性列上有值
    • 指定部分属性列,则新元组在这些列上将取空值
  • VALUES子句:提供的值必须与INTO子句匹配

  • 例1:将一个新学生元组(学号:200215128;姓名:陈冬;性别:男;所在系:IS;年龄:18岁)插入到Student表中

    INSERT INTO Student (Sno, Sname, Ssex, Sdept, Sage)
    VALUES ('200215128', '陈冬', '男', 'IS', 18);
    
  • 例2:将学生张成民的信息插入到Student表中

    INSERT INTO Student
    VALUES ('200215126', '张成民', '男', 18, 'CS');
    

    如果没有指定属性名,表示新元组需要为表的所有列赋值,列的顺序与创建表时相同。

  • 例3:插入一条选课记录('200215128','1')

    INSERT INTO SC(Sno, Cno)
    VALUES ('200215128', '1');
    

    RDBMS将在新插入记录的Grade列上自动地赋空值,或者:

    INSERT INTO SC
    VALUES ('200215128', '1', NULL);
    

插入子查询结果

  • 子查询可以嵌套在INSERT语句中用以生成要插入的批量数据

  • 语句格式

    INSERT INTO <表名> [(<属性列1>[,<属性列2>...])]
    子查询;
    
    • 功能:将子查询结果插入指定表中
  • 例4:对每一个系,求学生的平均年龄,并把结果存入数据库

    • 第一步:建表

      CREATE TABLE Dept_age (
           Sdept CHAR(15)    /* 系名*/
           Avg_age SMALLINT  /*学生平均年龄*/
      );
      
    • 第二步:插入数据

      INSERT INTO Dept_age(Sdept, Avg_age)
      SELECT Sdept, AVG(Sage)
      FROM Student
      GROUP BY Sdept;
      
  • RDBMS在执行插入语句时会检查所插元组是否破坏表上已定义的完整性规则

修改数据

  • 语句格式

    UPDATE <表名>
    SET <列名>=<表达式> [,<列名>=<表达式>]...
    [WHERE <条件>];
    
    • 功能:修改指定表中满足WHERE子句条件的元组
  • 修改某一个元组的值

  • 例5:将学生200215121的年龄改为22岁

    UPDATE Student
    SET Sage = 22
    WHERE Sno = '200215121';
    
  • 修改多个元组的值

  • 例6:将所有学生的年龄增加1岁

    UPDATE Student
    SET Sage = Sage+1;
    
  • 带子查询的修改语句

  • 例7:将计算机科学系全体学生的成绩置零

    UPDATE SC
    SET Grade = 0
    WHERE Sno in (
              SELECT Sno
              FROM Student
              WHERE Sdept = 'CS');
    

删除数据

  • 语句格式

    DELETE FROM <表名>
    [WHERE <条件>];
    
  • 删除某一个元组的值

  • 例8:删除学号为200215128的学生记录

    DELETE FROM Student
    WHERE Sno = '200215128';
    
  • 删除多个元组的值

  • 例9:删除所有的学生选课记录

    DELETE
    FROM SC;
    
  • 带子查询的删除语句

  • 例10:删除计算机科学系所有学生的选课记录

    DELETE FROM SC
    WHERE Sno in (
              SELECT Sno
              FROM Student
              WHERE Sdept = 'CS');
    

空值的处理

控值的判断

  • 判断一个属性的值是否为空值,用IS NULL或IS NOT NULL来表示

  • 例1:从Student表中找出漏填了数据的学生信息

    SELECT *
    FROM Student
    WHERE Sname IS NULL OR Ssex IS NULL OR Sage IS NULL OR Sdept IS NULL;
    

空值的约束条件

  • 属性定义(或者域定义)中有NOT NULL约束条件的不能取空值,加了UNIQUE限制的属性不能取空值,码属性不能取空值

空值的算术运算、比较运算和逻辑运算

  • 空值与另一个值(包括另一个空值)的算术运算的结果为空值,空值与另一个值(包括另一个空值)的比较运算的结果为UNKNOWN。

  • 有了UNKNOWN后,传统的逻辑运算中二值(TRUE,FALSE)逻辑就扩展成了三值逻辑。

  • AND、OR、NOT的真值表如下表所示,其中T表示TRUE,F表示FALSE,U表示UNKNOWN

    在聚合函数中对null的处理:Oracle与Mysql中 count()、sum()、avg() 计算过程中对null的计算

  • 例2:选出选修1号课程的不及格的学生以及缺考的学生

    SELECT SnoFROM SC
    WHERE Cno = '1' AND( Grade < 60 OR Grade IS NULL);
    

视图

  • 视图的特点
    • 视图是一个虚表,是从一个或几个基本表(或视图)导出的表
    • 只存放视图的定义,不存放视图对应的数据
    • 基表中的数据发生变化,从视图中查询出的数据也随之改变

定义视图

建立视图

  • 语句格式

    CREATE VIEW <视图名> [(<列名> [,<列名>]...)]
    AS <子查询>
    [WITH CHECK OPTION];
    
    • WITH CHECK OPTION表示对视图进行UPDATE、INSERT和DELETE操作时保证更新、插入或删除的行满足视图定义中的谓词条件(即子查询中的条件表达式)

    • 子查询一般不允许含有ORDER BY子句和DISTINCT短语

    • 如果需要对视图的结果进行排序或去重,可以在使用视图的查询语句中使用ORDER BY和DISTINCT子句

      SELECT * FROM student_view
      ORDER BY age DESC;
      
  • RDBMS执行CREATE VIEW语句时只是把视图定义存入数据字典,并不执行其中的SELECT语句。

  • 在对视图查询时,按视图的定义从基本表中将数据查出。

  • 例1:建立信息系学生的视图

    CREATE VIEW IS_Student AS
    SELECT Sno, Sname, Sage
    FROM Student
    WHERE Sdept = 'IS';
    
  • 例2:建立信息系学生的视图,并要求进行修改和插入操作时仍需保证该视图只有信息系的学生

    CREATE VIEW IS_Student AS
    SELECT Sno, Sname, Sage
    FROM Student
    WHERE Sdept = 'IS'
    WITH CHECK OPTION;
    
  • 对IS_Student视图的更新操作:

    • 修改操作:自动加上Sdept='IS'的条件
    • 删除操作:自动加上Sdept='IS'的条件
    • 插入操作:自动检查Sdept属性值是否为'IS'
      • 如果不是,则拒绝该插入操作
      • 如果没有提供Sdept属性值,则自动定义Sdept为'IS'
  • 基于多个基表的视图

  • 例3:建立信息系选修了1号课程的学生视图

    CREATE VIEW IS_S1(Sno, Sname, Grade) AS
    SELECT Student.Sno, Sname, Grade
    FROM Student, SC
    WHERE Sdept = 'IS' AND Student.Sno = SC.Sno AND SC.Cno = '1';
    
  • 基于视图的视图

  • 例4:建立信息系选修了1号课程且成绩在90分以上的学生的视图

    CREATE VIEW IS_S2 AS
    SELECT Sno, Sname, Grade
    FROM IS_S1
    WHERE Grade >= 90;
    
  • 带表达式的视图

  • 例5:定义一个反映学生出生年份的视图

    CREATE VIEW BT_S(Sno, Sname, Sbirth) AS
    SELECT Sno, Sname, 2000 - Sage
    FROM Student;
    
  • 分组视图

  • 例6:将学生的学号及他的平均成绩定义为一个视图

    CREAT VIEW S_G(Sno, Gavg) AS
    SELECT Sno, AVG(Grade)
    FROM SC
    GROUP BY Sno;
    

    CREATE VIEW中必须明确定义组成S_G视图的各个属性列名

  • 不指定属性列

  • 例7:将Student表中所有女生记录定义为一个视图

    CREATE VIEW F_Student(F_Sno, name, sex, age, dept) AS
    SELECT *
    FROM Student
    WHERE Ssex = '女';
    
    • 缺点:修改基表Student的结构后,Student表与F_Student视图的映象关系被破坏,导致该视图不能正确工作,需要删除重建

删除视图

  • 语句的格式:

    DROP VIEW <视图名>;
    
  • 该语句从数据字典中删除指定的视图定义

  • 如果该视图上还导出了其他视图,使用CASCADE级联删除语句,把该视图和由它导出的所有视图一起删除

  • 删除基表时,由该基表导出的所有视图定义都必须显式地使用DROP VIEW语句删除

  • 例8:删除视图BT_S和视图IS_S1

    DROP VIEW BT_S;
    DROP VIEW IS_S1;
    
  • 由于IS_S1视图上还导出了IS_S2视图,所以该语句被拒绝执行,使用级联删除

    DROP VIEW IS_S1 CASCADE;
    

查询视图

  • 视图定义后,用户就可以像对基本表一样对视图进行查询了

  • RDBMS实现视图查询的方法:视图消解法(View Resolution)

    • 首先进行有效性检查,检查涉及的表、视图等是否存在
    • 然后转换成等价的对基本表的查询
    • 最后执行修正后的查询
  • 例9:在信息系学生的视图中找出年龄小于20岁的学生(IS_Student参见视图定义例2)

    SELECT Sno, Sage
    FROM IS_Student
    WHERE Sage < 20;
    

    视图消解转换后的查询语句为:

    SELECT Sno, Sage
    FROM Student
    WHERE Sdept = 'IS' AND Sage < 20;
    
  • 例10:查询选修了1号课程的信息系学生

    SELECT IS_Student.Sno, Sname
    FROM IS_Student, SC
    WHERE IS_Student.Sno = SC.Sno AND SC.Cno = '1';
    
  • 视图消解法的局限:有些情况下,视图消解法不能生成正确查询

  • 例11:在S_G视图中查询平均成绩在90分以上的学生学号和平均成绩

    SELECT *
    FROM S_G
    WHERE Gavg >= 90;
    

    S_G视图的子查询定义:

    CREATE VIEW S_G (Sno, Gavg) AS
    SELECT Sno, AVG(Grade)
    FROM SC
    GROUP BY Sno;
    

    查询转换后为:

    SELECT Sno, AVG(Grade)
    FROM SC
    WHERE AVG(Grade) >= 90
    GROUP BY Sno;
    

    WHERE子句不能用聚集函数,因此出现语法错误。正确转换的查询语句应该是

    SELECT Sno, AVG(Grade)
    FROM SC
    GROUP BY Sno
    HAVING AVG(Grade) >= 90;
    
  • 因此这类查询应该直接对基本表进行

    SELECT *
    FROM (SELECT Sno, AVG(Grade)
          FROM SConu
          GROUP BY Sno) ASS_G(Sno, Gavg)
    WHERE Gavg >= 90;
    

更新视图

  • 由于视图是不实际存储数据的虚表,因此对视图的更新最终要转换为对基本表的更新

  • 为防止用户通过视图更新,对不属于视图范围内的基本表数据进行操作,可在定义视图时加上WITH CHECK OPTION子句,这样如果不满足条件则拒绝执行该操作。

  • 例12:将信息系学生视图IS_Student中学号200215122的学生姓名改为“刘辰”

    UPDATE IS_Student
    SET Sname = '刘辰'
    WHERE Sno = '200215122';
    

    转换后的语句:

    UPDATE Student
    SET Sname = '刘辰'
    WHERE Sno = '200215122' AND Sdept = 'IS';
    
  • 例13:向信息系学生视图IS_S中插入一个新的学生记录:200215129,赵新,20岁

    INSERT INTO IS_Student
    VALUES ('95029', '赵新', 20);
    

    转换为对基本表的更新:

    INSERT INTO Student(Sno, Sname, Sage, Sdept)
    VALUES ('200215129', '赵新', 20, 'IS');
    
  • 例14:删除信息系学生视图IS_Student中学号为200215129的记录

    DELETE FROM IS_Student
    WHERE Sno = '200215129';
    

    转换为对基本表的更新:

    DELETE FROM Student
    WHERE Sno = '200215129' AND Sdept = 'IS';
    
  • 更新视图的限制:一些视图是不可更新的,因为对这些视图的更新不能唯一地有意义地转换成对相应基本表的更新

  • 例:视图S_G为不可更新视图

    UPDATE S_G
    SET Gavg = 90
    WHERE Sno = '200215121';
    

    这个对视图的更新无法转换成对基本表SC的更新

  • 一般来说,允许对行列子集视图进行更新

    • 行列子集视图是从单个基本表派生的视图,仅删除了特定行和列,保留了主码。IS_Student视图就是一个行列子集视图
  • 对其他类型视图的更新不同系统有不同限制

视图的作用

  1. 视图能够简化用户的操作
    • 视图机制使用户可以将注意力集中在所关心的数据上
    • 例如,视图可以隐藏表与表之间的连接操作,让用户只需进行简单查询,而无需了解虚拟表的创建细节。
  2. 视图使用户能以多种角度看待同一数据
    • 视图允许不同用户以各自方式查看相同数据,对于多种用户共享同一数据库很重要
  3. 视图对重构数据库提供了一定程度的逻辑独立性
  4. 视图能够对机密数据提供安全保护
    • 视图机制可用于在数据库应用系统中为不同用户定义不同视图,确保机密数据不会显示在不应该看到的用户视图上。
  5. 适当的利用视图可以更清晰的表达查询

第四章 数据库安全性

数据库安全性概述

数据库的不安全因素

  1. 非授权用户对数据库的恶意存取和破坏
    • 黑客在用户访问数据库时窃取用户名和密码,然后假冒用户偷取、修改甚至破坏用户数据
    • DBMS提供的安全措施主要包括用户身份鉴别、存取控制和视图等技术
  2. 数据库中重要或敏感的数据被泄露
    • 黑客采用各种手段盗取数据库的机密信息
    • DBMS提供的主要技术有强制存取控制、数据加密存储和加密传输等
    • 分析审计日志,进行防范
  3. 安全环境的脆弱性
    • 数据库的安全性与计算机系统的安全性紧密联系
      • 计算机硬件、操作系统、网络系统等的安全性
    • 建立一套可信(Trusted)计算机系统的概念和标准

安全标准简介

  • 1985年美国国防部(Department of Defense,DoD)正式颁布《DoD可信计算机系统评估准则》(Trusted Computer System Evaluation Criteria,简称TCSEC或DoD85)

    • 不同国家建立在TCSEC概念上的评估准则
    • 欧洲的信息技术安全评估准则(ITSEC)
    • 加拿大的可信计算机产品评估准则(CTCPEC)
    • 美国的信息技术安全联邦标准(FC)
  • 1993年,CTCPEC、FC、TCSEC和ITSEC联合行动,解决原标准中概念和技术上的差异,称为通用准则(Common Criteria,CC)项目

    • 1999年CC V2.1版被ISO采用为国际标准
    • 2001年CC V2.1版被我国采用为国家标准
    • 目前CC已基本取代了TCSEC,成为评估信息产品安全性的主要标准
  • 信息安全标准的发展历史如下图

  • 1991年4月美国NCSC(国家计算机安全中心)颁布了《可信计算机系统评估标准关于可信数据库系统的解释》(TCSEC/Trusted Database Interpretation,简称TCSEC/TDI)

    • TCSEC/TDI又称紫皮书,它将TCSEC扩展到数据库管理系统

    • TCSEC/TDI中定义了数据库管理系统的设计与实现中需满足和用以进行安全性级别评估的标准

    • TCSEC/TDI标准的基本内容

      • TCSEC/TDI,从四个方面来描述安全性级别划分的指标:安全策略、责任、保证、文档
    • TCSEC/TDI安全级别划分如下图

      • 四组(division)七个等级:D、C(C1,C2)、B(B1,B2,B3)、A(A1),按系统可靠或可信程度逐渐增高
      • 各安全级别之间具有一种偏序向下兼容的关系,即较高安全性级别提供的安全保护要包含较低级别的所有保护要求,同时提供更多或更完善的保护能力
      • D级:将一切不符合更高标准的系统均归于D组
        • 典型例子:DOS是安全标准为D的操作系统
        • DOS在安全性方面几乎没有什么专门的机制来保障
      • C1级:非常初级的自主安全保护
        • 能够实现对用户和数据的分离,进行自主存取控制(DAC),保护或限制用户权限的传播
        • 现有的商业系统稍作改进即可满足
      • C2级:安全产品的最低档次
        • 提供受控的存取保护,将C1级的DAC进一步细化,以个人身份注册负责,并实施审计和资源隔离
        • 达到C2级的产品在其名称中往往不突出“安全”(Security)这一特色
        • 典型例子:Windows 2000,Oracle 7
      • B1级:标记安全保护。“安全”(Security)或“可信的”(Trusted)产品。
        • 对系统的数据加以标记,对标记的主体和客体实施强制存取控制(MAC)、审计等安全机制
        • B1级典型例子:
          • 操作系统:惠普公司的HP-UX BLS release 9.09+
          • 数据库:Oracle公司的Trusted Oracle 7,Sybase公司的Secure SQL Server version 11.0.6
      • B2级:结构化保护。建立形式化的安全策略模型并对系统内的所有主体和客体实施DAC和MAC
      • B3级:安全域。该级的TCB(Trusted Computing Base)必须满足访问监控器的要求,审计跟踪能力更强,并提供系统恢复过程
      • A1级:验证设计,即提供B3级保护的同时给出系统的形式化设计说明和验证以确信各安全保护真正实现
    • CC提出国际公认的表述信息技术安全性的结构,即把信息产品的安全要求分为安全功能要求和安全保证要求

      • CC文本组成

        • 简介和一般模型:有关术语、基本概念和一般模型以及与评估有关的一些框架
        • 安全功能要求:列出了一系列类、子类和组件
        • 安全保证要求:列出了一系列保证类、子类和组件;提出了评估保证级(Evaluation Assurance Level,EAL),从EAL1至EAL7共分为七级
      • CC评估保证级(EAL)划分如下图

数据库安全性控制

  • 非法使用数据库的情况

    • 编写合法程序绕过数据库管理系统及其授权机制
    • 直接或编写应用程序执行非授权操作
    • 通过多次合法查询数据库从中推导出一些保密数据
  • 计算机系统中,安全措施是一级一级层层设置,如下图是计算机系统的安全模

    • 系统根据用户标识鉴定用户身份,合法用户才准许进入计算机系统
    • 数据库管理系统还要进行存取控制,只允许用户执行合法操作
    • 操作系统有自己的保护措施
    • 数据以密码形式存储到数据库中
  • 数据库管理系统安全性控制模型如下图

    • 存取控制流程
      • 首先,数据库管理系统对提出SQL访问请求的数据库用户进行身份鉴别,防止不可信用户使用系统
      • 然后,在SQL处理层进行自主存取控制和强制存取控制,进一步可以进行推理控制
      • 还可以对用户访问行为和系统关键操作进行审计,对异常用户行为进行简单入侵检测
      • 在数据存储层,还存储与安全有关的标记和信息(称为安全数据),提供存储加密功能等

用户身份鉴别

  • 用户身份鉴别系统提供的最外层安全保护措施
  • 每个用户标识由用户名和用户标识号组成(用户标识号在系统整个生命周期内唯一),每次进入系统时,由系统进行核对,通过鉴定后才提供使用数据库管理系统的权限。
  • 用户身份鉴别的方法
    1. 静态口令鉴别:静态口令一般由用户自己设定,这些口令是静态不变的
    2. 动态口令鉴别:口令是动态变化的,每次鉴别时均需使用动态产生的新口令登录数据库管理系统,即采用一次一密的方法
    3. 生物特征鉴别:通过生物特征进行认证的技术,生物特征如指纹、虹膜和掌纹等
    4. 智能卡鉴别:智能卡是一种不可复制的硬件,内置集成电路的芯片,具有硬件加密功能

存取控制

  • 存取控制机制组成
    • 定义用户权限,并将用户权限登记到数据字典中
      • 用户对某一数据对象的操作权力称为权限
      • DBMS提供适当的语言来定义用户权限,存放在数据字典中,称做安全规则或授权规则
    • 合法权限检查
      • 用户发出存取数据库操作请求后,DBMS查找数据字典,进行合法权限检查
  • 用户权限定义和合法权检查机制一起组成了数据库管理系统的存取控制子系统
  • 常用存取控制方法
    • 自主存取控制(Discretionary Access Control,简称DAC)
      • C2级的数据库管理系统支持DAC
      • 用户对不同的数据对象有不同的存取权限
      • 不同的用户对同一对象也有不同的权限
      • 用户还可将其拥有的存取权限转授给其他用户
    • 强制存取控制(Mandatory Access Control,简称MAC)
      • B1级的数据库管理系统支持MAC
      • 每一个数据对象被标以一定的密级
      • 每一个用户也被授予某一个级别的许可证
      • 对于任意一个对象,只有具有合法许可证的用户才可以存取

自主存取控制方法

  • 通过SQL的GRANT语句和REVOKE语句实现

  • 用户权限组成:数据对象、操作类型

  • 定义用户存取权限:定义用户可以在哪些数据库对象上进行哪些类型的操作

  • 定义存取权限称为授权(authorization)

  • 关系数据库系统中存取控制对象图如下图

授权:授予与回收

GRANT

  • GRANT语句的一般格式

    GRANT <权限>[,<权限>]... 
    ON <对象类型> <对象名>[,<对象类型> <对象名>]...
    TO <用户>[,<用户>]...
    [WITH GRANT OPTION];
    
  • 语义:将对指定操作对象的指定操作权限授予指定的用户

  • 发出GRANT语句的有:数据库管理员、数据库对象创建者(即属主Owner)、拥有该权限的用户

  • 按受权限的用户可以是一个或多个具体用户,也可以是PUBLIC(即全体用户)

  • 如果指定WITH GRANT OPTION子句,则可以再授予;没有指定则不能传播该权限

  • 不允许循环授权

  • 例1:把查询Student表权限授给用户U1

    GRANT SELECT 
    ON TABLE Student 
    TO U1;
    
  • 例2:把对Student表和Course表的全部权限授予用户U2和U3

    GRANT ALL PRIVILEGES 
    ON TABLE Student, Course 
    TO U2, U3;
    
  • 例3:把对表SC的查询权限授予所有用户

    GRANT SELECT 
    ON TABLE SC 
    TO PUBLIC;
    
  • 例4:把查询Student表和修改学生学号的权限授给用户U4

    GRANT UPDATE(Sno), SELECT 
    ON TABLE Student 
    TO U4;
    
  • 对属性列的授权时必须明确指出相应属性列名

  • 例5:把对表SC的INSERT权限授予U5用户,并允许他再将此权限授予其他用户

    GRANT INSERT 
    ON TABLE SC 
    TO U5
    WITH GRANT OPTION;
    
  • 执行例5后,U5有了对表SC的INSERT权限,还可以传播此权限

  • 例6:U5将此权限授予U6

    GRANT INSERT
    ON TABLE SC
    TO U6
    WITH GRANT OPTION;
    
  • 例7:U6将此权限授予U7

    GRANT INSERT 
    ON TABLE SC 
    TO U7;
    
  • 执行了例1~例7语句后学生-课程数据库中的用户权限定义表

REVOKE

  • 授予的权限可以由数据库管理员或其他授权者用REVOKE语句收回

  • REVOKE语句的一般格式为:

    REVOKE <权限>[,<权限>]... 
    ON <对象类型> <对象名>[,<对象类型> <对象名>]...
    FROM <用户>[,<用户>]...[CASCADE | RESTRICT];
    
  • 例8:把用户U4修改学生学号的权限收回

    REVOKE UPDATE(Sno)
    ON TABLE Student 
    FROM U4;
    
  • 例9:收回所有用户对表SC的查询权限

    REVOKE SELECT 
    ON TABLE SC 
    FROM PUBLIC;
    
  • 例10:把用户U5对SC表的INSERT权限收回

    REVOKE INSERT 
    ON TABLE SC 
    FROM U5 CASCADE;
    
  • 将用户U5的INSERT权限收回的时候应该使用CASCADE,否则拒绝执行该语句

  • 如果U6或U7还从其他用户处获得对SC表的INSERT权限,则他们仍具有此权限,系统只收回直接或间接从U5处获得的权限

  • 执行例8~10语句后学生-课程数据库中的用户权限定义表

小结:SQL灵活的授权机制

  • 数据库管理员:
    • 拥有所有对象的所有权限
    • 根据实际情况不同的权限授予不同的用户
  • 用户:
    • 拥有自己建立的对象的全部的操作权限
    • 可以使用GRANT,把权限授予其他用户
  • 被授权的用户
    • 如果具有“继续授权”的许可,可以把获得的权限再授予其他用户
  • 所有授予出去的权力在必要时又都可用REVOKE语句收回

创建数据库模式的权限

  • 对创建数据库模式类的数据库对象的授权则由数据库管理员在创建用户时实现

  • CREATE USER语句格式

    CREATE USER <username> [WITH][DBA|RESOURCE|CONNECT];
    

    注:CREATE USER不是SQL标准,各个系统的实现相差甚远

  • CREATE USER语句格式说明

    • 只有超级用户能创建新用户
    • 新用户可选权限:CONNECT(只能登录)、RESOURCE(能创建基本表和视图)、DBA(全权限,包括创建新用户、模式和基本表视图等)
    • 未指定权限的用户默认拥有CONNECT权限
    • CONNECT权限的用户只能登录数据库,不能创建新用户、模式或基本表
    • RESOURCE权限的用户能创建基本表和视图,但不能创建模式或新用户
    • DBA权限的用户是超级用户,可执行所有操作并授权给一般用户

数据库角色

  • 数据库角色是被命名的一组与数据库操作相关的权限,角色是权限的集合

    • 可以为一组具有相同权限的用户创建一个角色,用来简化授权的过程
  • 角色的创建

    CREATE ROLE <角色名>;
    
  • 给角色授权

    GRANT <权限>[,<权限>]...
    ON <对象类型>对象名  
    TO <角色>[,<角色>]...;
    
  • 将一个角色授予其他的角色或用户

    GRANT <角色1>[,<角色2>]...
    TO <角色3>[,<用户1>]...
    [WITH ADMIN OPTION];
    
    • 该语句把角色授予某用户,或授予另一个角色
    • 授予者是角色的创建者或拥有在这个角色上的ADMIN OPTION
    • 指定了WITH ADMIN OPTION子句,则获得某种权限的角色或用户还可以把这种权限授予其他角色
    • 一个角色的权限包含:直接授予这个角色的全部权限加上其他角色授予这个角色的全部权限
  • 角色权限的收回

    REVOKE <权限>[,<权限>]...
    ON <对象类型> <对象名>
    FROM <角色>[,<角色>]...;
    
    • 用户可以回收角色的权限,从而修改角色拥有的权限
    • REVOKE执行者是:角色的创建者,或者拥有这个(些)角色上的ADMIN OPTION
  • 例11:通过角色来实现将一组权限授予一个用户

    1. 首先创建一个角色R1

      CREATE  ROLE  R1;
      
    2. 然后使用GRANT语句,使角色R1拥有Student表的SELECT、UPDATE、INSERT权限

      GRANT SELECT, UPDATE, INSERT
      ON TABLE Student
      TO R1;
      
    3. 将这个角色授予王平,张明,赵玲。使他们具有角色R1所包含的全部权限

      GRANT  R1 
      TO 王平, 张明, 赵玲;
      
    4. 可以一次性通过R1来回收王平的这3个权限

      REVOKE  R1 
      FROM 王平;
      
  • 例12:角色的权限修改

    GRANT DELETE 
    ON TABLE Student
    TO R1;
    

    使角色R1在原来的基础上增加了Student表的DELETE权限

  • 角色的权限被修改,那么拥有该角色的用户通常会受到影响

  • 例13:使R1减少了SELECT权限

    REVOKE SELECT 
    ON TABLE Student
    FROM  R1;
    

强制存取控制方法

  • 自主存取控制方法可能存在数据的“无意泄露”,例如,甲将部分数据权限授权给乙,意图仅允许乙操作这些数据。但甲无法确保乙不会备份数据并在未经同意的情况下传播副本。

  • 原因:这种机制仅仅通过对数据的存取权限来进行安全控制,而数据本身并无安全性标记

  • 解决:对系统控制下的所有主客体实施强制存取控制策略

  • 强制存取控制(MAC)

    • 保证更高程度的安全性
    • 用户不能直接感知或进行控制
    • 适用于对数据有严格而固定密级分类的部门,如军事部门或政府部门
  • 在强制存取控制中,数据库管理系统所管理的全部实体被分为主体和客体两大类

    • 主体是系统中的活动实体
      • 数据库管理系统所管理的实际用户
      • 代表用户的各进程
    • 客体是系统中的被动实体,受主体操纵
      • 文件、基本表、索引、视图
  • 敏感度标记(Label)

    • 对于主体和客体,DBMS为它们每个实例(值)指派一个敏感度标记(Label)
    • 敏感度标记分成若干级别
      • 绝密(Top Secret,TS)
      • 机密(Secret,S)
      • 可信(Confidential,C)
      • 公开(Public,P)
      • TS>=S>=C>=P
    • 主体的敏感度标记称为许可证级别(Clearance Level)
    • 客体的敏感度标记称为密级(Classification Level)
  • 强制存取控制规则

    1. 仅当主体的许可证级别大于或等于客体的密级时,该主体才能读取相应的客体
    2. 仅当主体的许可证级别小于或等于客体的密级时,该主体才能写相应的客体
      • 2的作用是防止数据不向下传播扩散,只向上保留
      • 例如:一个TS密级的主体不能将一个TS密级的数据降低为P并写回,否则就谁都能读取这个TS密级的数据了
  • 强制存取控制(MAC)是对数据本身进行密级标记,无论数据如何复制,标记与数据是一个不可分的整体,只有符合密级标记要求的用户才可以操纵数据。

  • MAC和DAC的联系

    • 实现MAC时要首先实现DAC,因为较高安全性级别提供的安全保护要包含较低级别的所有保护

    • DAC与MAC共同构成数据库管理系统的安全机制,如下图

    • 先进行DAC检查,通过DAC检查的数据对象再由系统进行MAC检查,只有通过MAC检查的数据对象方可存取

视图机制

  • 把要保密的数据对无权存取这些数据的用户隐藏起来,对数据提供一定程度的安全保护

  • 间接地实现支持存取谓词的用户权限定义

  • 例14:建立计算机系学生的视图,把对该视图的SELECT权限授于王平,把该视图上的所有操作权限授于张明

    • 先建立计算机系学生的视图CS_Student

      CREATE VIEW CS_Student
      AS 
      SELECT *
      FROM Student
      WHERE Sdept='CS';
      
    • 在视图上进一步定义存取权限

      GRANT SELECT
      ON CS_Student  
      TO 王平;
      GRANT ALL PRIVILEGES
      ON CS_Student  
      TO 张明; 
      

审计(Audit)

  • 什么是审计
    • 启用一个专用的审计日志(Audit Log)将用户对数据库的所有操作记录在上面
    • 审计员利用审计日志监控数据库中的各种行为,找出非法存取数据的人、时间和内容
    • C2以上安全级别的DBMS必须具有审计功能
  • 审计功能的可选性
    • 审计很费时间和空间
    • DBA可以根据应用对安全性的要求,灵活地打开或关闭审计功能
    • 审计功能主要用于安全性要求较高的部门
  • 审计事件包括服务器事件、系统权限、语句事件、模式对象事件,以及用户鉴别、自主访问控制和强制访问控制事件。简而言之,它可以审计普通和特权用户的行为、各种表操作、身份鉴别、自主和强制访问控制等操作,无论成功或失败。

审计事件

  • 服务器事件:审计数据库服务器发生的事件,包含数据库服务器的启动、停止等

  • 系统权限:对系统拥有的结构或模式对象进行操作的审计,要求该操作的权限是通过系统权限获得的

  • 语句事件:对SQL语句,如DDL、DML、DQL及DCL语句的审计

  • 模式对象事件:对特定模式对象上进行的SELECT或DML操作的审计

    模式对象包括表、视图、存储过程、函数等

    模式对象不包括依附于表的索引、约束、触发器、分区表等

审计功能

  • 基本功能:提供多种审计查阅方式
  • 多套审计规则:一般在初始化设定
  • 提供审计分析和报表功能
  • 审计日志管理功能:防止审计员误删审计记录,审计日志必须先转储后删除;对转储的审计记录文件提供完整性和保密性保护;只允许审计员查阅和转储审计记录,不允许任何用户新增和修改审计记录等
  • 提供查询审计设置及审计记录信息的专门视图

AUDIT语句和NOAUDIT语句

  • AUDIT语句:设置审计功能

  • NOAUDIT语句:取消审计功能

  • 用户级审计

    • 任何用户可设置的审计
    • 主要是用户针对自己创建的数据库表和视图进行审计
  • 系统级审计

    • 只能由数据库管理员设置
    • 监测成功或失败的登录要求、监测授权和收回操作以及其他数据库级权限下的操作
  • 例15:对修改SC表结构或修改SC表数据的操作进行审计

    AUDIT ALTER,UPDATE  
    ON SC;
    
  • 例16:取消对SC表的一切审计

    NOAUDIT ALTER,UPDATE  
    ON SC;
    

数据加密

  • 数据加密是防止数据库中数据在存储和传输中失密的有效手段
  • 加密的基本思想:根据一定的算法将原始数据--明文(Plain text)变换为不可直接识别的格式­--密文(Cipher text)
  • 加密方法有存储加密和传输加密

存储加密

在加密领域,透明是指加密过程中,数据的加密和解密操作对用户来说是可见的或可控的。

  • 透明存储加密:内核级加密保护方式,对用户完全透明
    • 将数据在写到磁盘时对数据进行加密,授权用户读取数据时再对其进行解密
    • 数据库的应用程序不需要做任何修改,只需在创建表语句中说明需加密的字段即可
    • 内核级加密方法:性能较好,安全完备性较高
  • 非透明存储加密:通过多个加密函数实现

传输加密

  • 链路加密

    • 在链路层进行加密
    • 传输信息由报头和报文两部分组成
    • 报文和报头均加密
  • 端到端加密

    • 在发送端加密,接收端解密
    • 只加密报文不加密报头
    • 所需密码设备数量相对较少,容易被非法监听者发现并从中获取敏感信息
  • 数据库管理系统可信传输示意图

    • 基于安全套接层协议SSL传输方案的实现思路
      1. 确认通信双方端点的可靠性
        • 数据库管理系统采用基于数字证书的服务器和客户端认证方式
        • 通信时均首先向对方提供己方证书,然后使用本地的CA信任列表和证书撤销列表对接收到的对方证书进行验证
      2. 协商加密算法和密钥
        • 确认双方端点的可靠性后,通信双方协商本次会话的加密算法与密钥
      3. 可信数据传输
        • 业务数据在被发送之前将被用某一组特定的密钥进行加密和消息摘要计算,以密文形式在网络上传输
        • 当业务数据被接收的时候,需用相同一组特定的密钥进行解密和摘要计算

其他安全性保护

  • 推理控制

    • 处理强制存取控制未解决的问题
    • 避免用户利用能够访问的数据推知更高密级的数据
    • 常用方法
      • 基于函数依赖的推理控制
      • 基于敏感关联的推理控制
    • 举例:假设在一个医院的数据库中,有一个病人的病历表和一个药物表。假设一个用户只有访问药物表的权限,但是如果他知道某个病人正在使用特定的药物,他可能会通过查询药物表,推断出这个病人的疾病。这就是一个通过推理获得敏感信息的例子
  • 隐蔽信道

    • 处理强制存取控制未解决的问题
    • 举例:假设在一个医院的数据库中,存储了患者的健康记录,这个数据库只有经过授权的医生才能查看。有一个恶意的员工想要将一些患者的信息传递给外部的不法分子,但他无法直接访问数据库系统,他可以通过修改某个患者的健康记录中的某些字段(比如血压)来传递二进制信息,对于外部接收者来说,他们可以监视这个字段的变化,根据特定的规则来解读信息,就像是使用二进制编码一样。如血压值升高1个单位,表示1;血压值降低1个单位,表示0
  • 数据隐私保护

    • 描述个人控制其不愿他人知道或他人不便知道的个人数据的能力
    • 数据隐私范围很广,涉及数据收集、数据存储、数据处理和数据发布等各个阶段

第五章 数据库完整性

  • 数据库的完整性

    • 数据的正确性
      • 是指数据是符合现实世界语义,反映了当前实际状况的
      • 例如学生的学号必须唯一,性别只能是男或女,本科学生年龄的取值范围为14~50的整数
    • 数据的相容性
      • 是指数据库同一对象在不同关系表中的数据是符合逻辑的
      • 例如学生所选的课程必须是学校开设的课程,学生所在的院系必须是学校已成立的院系等
  • 数据的完整性和安全性是两个不同概念

    • 数据的完整性
      • 防止数据库中存在不符合语义的数据,也就是防止数据库中存在不正确的数据
      • 防范对象:不合语义的、不正确的数据
    • 数据的安全性
      • 保护数据库 防止恶意的破坏和非法的存取
      • 防范对象:非法用户和非法操作
  • 为维护数据库的完整性,数据库管理系统需要做到以下几点:

    1. 提供完整性约束条件机制
      • 完整性约束条件也称为完整性规则,是数据库中的数据必须满足的语义约束条件
      • SQL标准定义了实体完整性、参照完整性和用户定义完整性等概念
      • 这些完整性一般由SQL的数据定义语言语句来实现并存入数据字典中
    2. 提供完整性检查方法
      • 数据库管理系统需要检查数据是否满足完整性约束条件
      • 检查通常在 INSERT、UPDATE、DELETE 语句执行后或事务提交时进行
    3. 违约处理
      • 数据库管理系统若发现用户的操作违背了完整性约束条件,就采取一定的动作:拒绝(NO ACTION)执行该操作和级连(CASCADE)执行其他操作

实体完整性

实体完整性定义

  • 关系模型的实体完整性CREATE TABLE 中用 PRIMARY KEY 定义

  • 单属性构成的码有两种说明方法

    • 定义为列级约束条件
    • 定义为表级约束条件
  • 多个属性构成的码只有一种说明方法

    • 定义为表级约束条件
  • 例1:将Student表中的Sno属性定义为码

    CREATE TABLE Student (
         Sno CHAR(9) PRIMARY KEY,	/* 在列级定义主码 */
         Sname CHAR(20) NOT NULL,
         Ssex CHAR(2),
         Sage SMALLINT,
         Sdept CHAR(20)
    );
    

    或者

    CREATE TABLE Student (
         Sno CHAR(9),
         Sname CHAR(20) NOT NULL,
         Ssex CHAR(2),
         Sage SMALLINT,
         Sdept CHAR(20),
         PRIMARY KEY (Sno)   /* 在表级定义主码 */
    );
    
  • 例2:将SC表中的Sno,Cno属性组定义为码

    CREATE TABLE SC (
         Sno CHAR(9) NOT NULL,
         Cno CHAR(4) NOT NULL,
         Grade SMALLINT,
         PRIMARY KEY (Sno, Cno)   /*只能在表级定义主码*/
    );
    

实体完整性检查和违约处理

  • 插入或更新主码列时,RDBMS会按照实体完整性规则自动进行检查。包括:

    • 检查主码值是否唯一,若不唯一则拒绝插入或修改
    • 检查主码的各个属性(多主码)是否为空,如有有空则拒绝插入或修改
  • 检查记录中主码值是否唯一的方法

    1. 全表扫描

      • 逐条比较表中记录的主码值与要插入或修改记录的主码值

        缺点:十分耗时

      • 为避免对基本表进行全表扫描,RDBMS核心一般都在主码上自动建立一个索引

    2. B+树索引

      • 例如,若新插入记录的主码值为25,通过主码索引查找,从B+树的根结点开始,经过3个结点:根结点(51)、中间结点(12 30)、叶结点(15 20 25)。由于该主码值已存在,故不能插入该记录。

参照完整性

参照完整性定义

  • CREATE TABLE 中用 FOREIGN KEY 短语定义哪些列为外码,用 REFERENCES 短语指明这些外码参照哪些表的主码

  • 例如,关系SC中(Sno,Cno)是主码。Sno,Cno分别参照Student表的主码和Course表的主码

  • 例3:定义SC中的参照完整性

    CREATE TABLE SC (
         Sno CHAR(9) NOT NULL,
         Cno CHAR(4) NOT NULL,
         Grade SMALLINT,
         PRIMARY KEY (Sno, Cno),	/*在表级定义实体完整性*/
         FOREIGN KEY (Sno) REFERENCES Student(Sno),	/*在表级定义参照完整性*/
         FOREIGN KEY (Cno) REFERENCES Course(Cno)	/*在表级定义参照完整性*/
    );
    

参照完整性检查和违约处理

  • 参照完整性用于连接两个表的相应元组,对这两表的增删改操作可能破坏参照完整性,因此需要检查

  • 例如,对表SC和Student有四种可能破坏参照完整性的情况:

    1. SC 表增加元组,Sno 属性值在 Student 表中不存在
    2. 修改 SC 表元组,Sno 属性值在 Student 表中不存在
    3. 从 Student 表删除元组,导致 SC 表元组 Sno 属性值在 Student 表中不存在
    4. 修改 Student 表元组 Sno 属性,导致 SC 表元组 Sno 属性值在 Student 表中不存在
  • 参照完整性违约处理

    1. 拒绝( NO ACTION )执行
      • 不允许违约操作,默认策略
    2. 级联( CASCADE )操作
      • 被参照表(Student)元组删除或修改导致不一致时,删除或修改参照表(SC)中造成不一致的所有元组。
    3. 设置为空值( SET-NULL
      • 被参照表元组删除或修改导致不一致时,将参照表中因此造成不一致的元组对应属性设置为空值
  • 例如,有2个关系:学生(学号,姓名,性别,专业号,年龄);专业(专业号,专业名)

    • 假设删除专业表中专业号为 12 的元组
    • 生表中专业号为 12 的元组专业号设为空
    • 对应语义:专业删除,该专业的所有学生专业未定,等待重新分配专业
  • 对于参照完整性,除了应该定义外码,还应定义外码列是否允许空值

  • 例4:显式说明参照完整性的违约处理示例

    CREATE TABLE SC (
         Sno CHAR(9) NOT NULL,
         Cno CHAR(4) NOT NULL,
         Grade SMALLINT,
         PRIMARY KEY(Sno, Cno),
         FOREIGN KEY (Sno) REFERENCES Student(Sno)
         ON DELETE CASCADE 		/*级联删除SC表中相应的元组*/
         ON UPDATE CASCADE, 	/*级联更新SC表中相应的元组*/
         FOREIGN KEY (Cno) REFERENCES Course(Cno)
         ON DELETE NO ACTION	/*当删除course 表中的元组造成了与SC表不一致时拒绝删除*/
         ON UPDATE CASCADE		/*当更新course表中的cno时,级联更新SC表中相应的元组*/
    );
    

用户定义的完整性

  • 用户定义的完整性是:针对某一具体应用的数据必须满足的语义要求
  • 关系数据库管理系统提供了定义和检验用户定义完整性的机制,不必由应用程序承担

属性上的约束条件

  1. 属性上约束条件的定义

    • CREATE TABLE时定义属性上的约束条件

      • 列值非空( NOT NULL
      • 列值唯一( UNIQUE
      • 检查列值是否满足一个条件表达式( CHECK
    • 不允许取空值

    • 例5:在定义SC表时,说明Sno、Cno、Grade属性不允许取空值

      CREATE TABLE SC (
           Sno CHAR(9) NOT NULL,
           Cno CHAR(4) NOT NULL,
           Grade SMALLINT NOT NULL,
           PRIMARY KEY (Sno, Cno),
      	 ...
           /* 如果在表级定义实体完整性,隐含了Sno,Cno不允
              许取空值,则在列级不允许取空值的定义 可以不写 */
      );
      
    • 列值唯一

    • 例6:建立部门表DEPT,要求部门名称Dname列取值唯一,部门编号Deptno列为主码

      CREATE TABLE DEPT (
           Deptno NUMERIC(2),
           /*要求Dname列值唯一, 并且不能取空值*/
           Dname CHAR(9) UNIQUE NOT NULL,
           Location CHAR(10),
           PRIMARY KEY (Deptno)
      );
      
    • 用CHECK短语指定列值应该满足的条件

    • 例7:Student表的Ssex只允许取“男”或“女”

      CREATE TABLE Student (
           Sno CHAR(9) PRIMARY KEY,
           Sname CHAR(8) NOT NULL,
           /*性别属性Ssex只允许取'男'或'女' */
           Ssex CHAR(2) CHECK (Ssex IN ('男', '女')),
           Sage SMALLINT,
           Sdept CHAR(20)
      );
      
    • 例8:SC表的Grade的值应该在0和100之间

      CREATE TABLE SC (
           Sno CHAR(9),
           Cno CHAR(4),
           Grade SMALLINT CHECK (Grade >= 0 AND Grade <= 100),
           /*Grade取值范围是0到100*/
           PRIMARY KEY (Sno, Cno),
           FOREIGN KEY (Sno) REFERENCES Student(Sno),
           FOREIGN KEY (Cno) REFERENCES Course(Cno)
      );
      
  2. 属性上的约束条件检查和违约处理

    • 插入元组或修改属性的值时,关系数据库管理系统检查属性上的约束条件是否被满足
    • 如果不满足则操作被拒绝执行

元组上的约束条件

  1. 元组上约束条件的定义

    • CREATE TABLE 时可以用 CHECK 短语定义元组上的约束条件,即元组级的限制

    • 同属性值限制相比,元组级的限制可以设置不同属性之间的取值的相互约束条件

    • 例9:当学生的性别是男时,其名字不能以Ms.打头

      CREATE TABLE Student (
           Sno CHAR(9),
           Sname CHAR(8) NOT NULL,
           Ssex CHAR(2),
           Sage SMALLINT,
           Sdept CHAR(20),
           PRIMARY KEY (Sno),
           /*定义了元组中Sname和Ssex两个属性值之间的约束条件*/
           CHECK (Ssex = '女' OR Sname NOT LIKE 'Ms.%')
      );
      
      • 性别是女性的元组都能通过该项检查,因为Ssex=‘女’成立
      • 当性别是男性时,要通过检查则名字一定不能以Ms.打头
  2. 元组上的约束条件检查和违约处理

    • 插入元组或修改属性的值时,关系数据库管理系统检查元组上的约束条件是否被满足
    • 如果不满足则操作被拒绝执行

完整性约束命名子句

  1. 完整性约束命名子句

    • <完整性约束条件>包括NOT NULL、UNIQUE、PRIMARY KEY短语、FOREIGN KEY短语、CHECK短语等

      CONSTRAINT <完整性约束条件名> <完整性约束条件>
      
    • 例10:建立学生登记表Student,要求学号在90000~99999之间,姓名不能取空值,年龄小于30,性别只能是“男”或“女”

      CREATE TABLE Student (
           Sno NUMERIC(6) CONSTRAINT C1 CHECK (Sno BETWEEN 90000 AND 99999),
           Sname CHAR(20) CONSTRAINT C2 NOT NULL,
           Sage NUMERIC(3) CONSTRAINT C3 CHECK (Sage < 30),
           Ssex CHAR(2) CONSTRAINT C4 CHECK (Ssex IN ('男', '女')),
           CONSTRAINT StudentKey PRIMARY KEY(Sno)
      );
      
      • 在Student表上建立了5个约束条件,包括主码约束(命名为StudentKey)以及C1、C2、C3、C4四个列级约束
    • 例11:建立教师表TEACHER,要求每个教师的应发工资不低于3000元,应发工资是工资列Sal与扣除项Deduct之和

      CREATE TABLE TEACHER (
           Eno NUMERIC(4) PRIMARY KEY	/*在列级定义主码*/
           Ename CHAR(10),
           Job CHAR(8),
           Sal NUMERIC(7, 2),
           Deduct NUMERIC(7, 2),
           Deptno NUMERIC(2),
           CONSTRAINT TEACHERFKey FOREIGN KEY (Deptno) REFERENCES DEPT(Deptno),
           CONSTRAINT C1 CHECK (Sal + Deduct >= 3000)
      );
      
  2. 修改表中的完整性限制

    • 使用 ALTER TABLE 语句修改表中的完整性限制

    • 例12:去掉例10 Student表中对性别的限制

      ALTER TABLE Student 
      DROP CONSTRAINT C4;
      
    • 例13:修改表Student中的约束条件,要求学号改为在900000~999999之间,年龄由小于30改为小于40

      可以先删除原来的约束条件,再增加新的约束条件

      ALTER TABLE Student DROP CONSTRAINT C1;
      ALTER TABLE Student
      ADD CONSTRAINT C1 CHECK (Sno BETWEEN 900000 AND 999999);
      ALTER TABLE Student DROP CONSTRAINT C3;
      ALTER TABLE  Student ADD CONSTRAINT C3 CHECK(Sage < 40);
      

断言

  • 在 SQL 中,使用 CREATE ASSERTION 语句可以创建声明性断言,以指定更具一般性的约束
  • 可以定义涉及多个表的或聚集操作的比较复杂的完整性约束
  • 断言创建以后,任何对断言中所涉及的关系的操作都会触发关系数据库管理系统对断言的检查,任何使断言不为真值的操作都会被拒绝执行
  1. 创建断言的语句格式

    • CREATE ASSERTION <断言名> <CHECK 子句>

    • 每个断言都被赋予一个名字,<CHECK 子句>中的约束条件与WHERE子句的条件表达式类似

    • 例18:限制数据库课程最多60名学生选修

      CREATE ASSERTION ASSE_SC_DB_NUM
      CHECK (60 >= (select count(*)  /*此断言的谓词涉及聚集操作count的SQL语句*/
                    From Course, SC
                    Where SC.Cno = Course.Cno and Course.Cname = '数据库'));
      

      CHECK子句返回值为false,则对SC表的插入操作被拒绝

    • 例20:限制每个学期每一门课程最多60名学生选修

      • 首先需要修改SC表的模式,增加一个“学期(TERM)”属性

        ALTER TABLE SC ADD TERM DATE;
        
      • 然后,定义断言

        CREATE ASSERTION ASSE_SC_CNUM2
        CHECK(60 >= ALL (SELECT count(*)
                         FROM SC
                         GROUP by cno, TERM));
        
  2. 删除断言的语句格式为

    • DROP ASSERTION <断言名>;
    • 如果断言很复杂,则系统在检测和维护断言的开销较高,这是在使用断言时应该注意的

触发器

  • 触发器(Trigger)是用户定义在关系表上的一类由事件驱动的特殊过程
    • 触发器保存在数据库服务器中
    • 任何用户对表的增、删、改操作均由服务器自动激活相应的触发器
    • 触发器可以实施更为复杂的检查和操作,具有更精细和更强大的数据控制能力

定义触发器

  • 触发器又叫做事件-条件-动作(event-condition-action)规则。

  • 当特定的系统事件发生时,对规则的条件进行检查,如果条件成立则执行规则中的动作,否则不执行该动作。规则中的动作体可以很复杂,通常是一段SQL存储过程。

  • CREATE TRIGGER 语法格式

    CREATE TRIGGER <触发器名>           /*每当触发事件发生时,该触发器被激活*/
    {BEFORE|AFTER} <触发事件> ON <表名>  /*指明触发器激活的时间是在执行触发事件前或后*/
    REFERENCING NEW|OLD ROW AS <变量>   /*REFERENCING指出引用的变量*/
    FOR EACH {ROW|STATEMENT}           /*定义触发器的类型,指明动作体执行的频率*/
    [WHEN <触发条件>] <触发动作体>        /*仅当触发条件为真时才执行触发动作体*/
    
  • 定义触发器的语法说明

    1. 表的拥有者才可以在表上创建触发器

    2. 触发器名

      • 触发器名可以包含模式名,也可以不包含模式名
      • 同一模式下,触发器名必须是唯一的
      • 触发器名和表名必须在同一模式下
    3. 表名

      • 触发器只能定义在基本表上,不能定义在视图上
      • 当基本表的数据发生变化时,将激活定义在该表上相应触发事件的触发器
    4. 触发事件

      • 触发事件可以是INSERT、DELETE或UPDATE,也可以是这几个事件的组合
      • 还可以是UPDATE OF<触发列,...>,即进一步指明修改哪些列时激活触发器
      • AFTER/BEFORE是触发的时机
        • AFTER表示在触发事件的操作执行之后激活触发器
        • BEFORE表示在触发事件的操作执行之前激活触发器
    5. 触发器类型

      • 行级触发器(FOR EACH ROW)
      • 语句级触发器(FOR EACH STATEMENT)
      • 例如,在例11的TEACHER表上创建一个AFTER UPDATE触发器,触发事件是UPDATE语句: UPDATE TEACHER SET Deptno=5;
      • 假设表TEACHER有1000行
        • 如果是语句级触发器,那么执行完该语句后,触发动作只发生一次
        • 如果是行级触发器,触发动作将执行1000次
    6. 触发条件

      • 触发器被激活时,只有当触发条件为真时触发动作体才执行;否则触发动作体不执行。
      • 如果省略WHEN触发条件,则触发动作体在触发器激活后立即执行
    7. 触发动作体

      • 触发动作体可以是一个匿名PL/SQL过程块,也可以是对已创建存储过程的调用
      • 如果是行级触发器,用户都可以在过程体中使用NEW和OLD引用事件之后的新值和事件之前的旧值
      • 如果是语句级触发器,则不能在触发动作体中使用NEW或OLD进行引用
      • 如果触发动作体执行失败,激活触发器的事件就会终止执行,触发器的目标表或触发器可能影响的其他对象不发生任何变化

      注意:不同的RDBMS产品触发器语法各部相同

  • 例21:当对表SC的Grade属性进行修改时,若分数增加了10%则将此次操作记录到下面表中:

    SC_U(Sno,Cno,Oldgrade,Newgrade)

    其中Oldgrade是修改前的分数,Newgrade是修改后的分数

    CREATE TRIGGER SC_T
    AFTER UPDATE OF Grade ON SC
    REFERENCING OLD row AS OldTuple, NEW row AS NewTuple
    FOR EACH ROW
    WHEN (NewTuple.Grade >= 1.1 * OldTuple.Grade)
    INSERT INTO SC_U(Sno, Cno, OldGrade, NewGrade)
    VALUES(OldTuple.Sno, OldTuple.Cno, OldTuple.Grade, NewTuple.Grade)
    
  • 例22:将每次对表Student的插入操作所增加的学生个数记录到表StudentInsertLog中

    CREATE TRIGGER Student_Count
    AFTER INSERT ON Student /*指明触发器激活的时间是在执行INSERT后*/
    REFERENCING NEW TABLE AS DELTA
    FOR EACH STATEMENT   /*语句级触发器, 即执行完INSERT语句后下面的触发动作体才执行一次*/
    INSERT INTO StudentInsertLog(Numbers)
    SELECT COUNT(*) FROM DELTA
    
  • 例23:定义一个BEFORE行级触发器,为教师表Teacher定义完整性规则“教授的工资不得低于4000元,如果低于4000元,自动改为4000元”

    CREATE TRIGGER Insert_Or_Update_Sal
    BEFORE INSERT OR UPDATE ON Teacher /*触发事件是插入或更新操作*/
    FOR EACH ROW /*行级触发器*/
    BEGIN /*定义触发动作体,是PL/SQL过程块*/
         IF (new.Job = '教授') AND (new.Sal < 4000)
         	THEN new.Sal := 4000;
    	 END IF;
    END;
    

激活触发器

  • 触发器的执行,是由触发事件激活的,并由数据库服务器自动执行
  • 一个数据表上可能定义了多个触发器,遵循如下的执行顺序:
    1. 执行该表上的BEFORE触发器
    2. 激活触发器的SQL语句
    3. 执行该表上的AFTER触发器。

删除触发器

  • 删除触发器的SQL语法:

    DROP TRIGGER <触发器名> ON <表名>;
    
  • 触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除

第六章 关系数据理论

问题的提出

  • 针对具体问题,如何构造一个适合于它的数据模式,于是有了数据库逻辑设计的工具--关系数据库的规范化理论

  • 复习:关系模式由五部分组成,是一个五元组:

    \[R(U, D, DOM, F) \]

    • 关系名R是符号化的元组语义

    • U为一组属性

    • D为属性组U中的属性所来自的域

    • DOM为属性到域的映射

    • F为属性组U上的一组数据依赖

    • 如果有一个学生关系,有学号、姓名、入学日期这三个属性,则

      • R = 学生

      • U =

      • D =

      • DOM =

      • F =

  • 由于D、DOM与模式设计关系不大,因此在本章中把关系模式看作一个三元组

    \[R<U, F> \]

  • 当且仅当U上的一个关系r满足F时,r称为关系模式 R<U,F> 的一个关系

  • 作为二维表,关系要符合一个最基本的条件:每个分量必须是不可分开的数据项。满足了这个条件的关系模式就属于第一范式(1NF)

  • 数据依赖(内容很抽象)

    • 数据依赖是一个关系内部属性与属性之间的一种约束关系
    • 这种约束关系通过属性间值的相等与否体现出来的数据间相互联系
    • 数据依赖是现实世界属性间相互联系的抽象,是数据内在的性质,是语义的体现
  • 数据依赖的主要类型

    • 函数依赖(Functional Dependency,简记为FD)
    • 多值依赖(Multi-Valued Dependency,简记为MVD)
  • 函数依赖普遍存在于现实生活中

    • 描述一个学生关系,可以有学号、姓名、系名等属性
      • 一个学号只对应一个学生,一个学生只在一个系中学习
      • 学号确定后,学生的姓名及所在系的值就被唯一确定
    • Sname=f(Sno),Sdept=f(Sno)
      • 即Sno函数决定Sname,Sno函数决定Sdept
      • 记作Sno->Sname,Sno->Sdept
  • 例1:建立一个描述学校教务的数据库。涉及的对象包括:学生的学号(Sno),所在系(Sdept),系主任姓名(Mname),课程号(Cno),成绩(Grade)

    • 假设学校教务的数据库模式用一个单一的关系模式Student来表示,则该关系模式的属性集合为:U =

    • 现实世界的已知事实(语义):

      • 一个系有若干学生, 但一个学生只属于一个系
      • 一个系只有一名(正职)负责人
      • 一个学生可以选修多门课程,每门课程有若干学生选修
      • 每个学生学习每一门课程有一个成绩
    • 由此可得到属性组U上的一组函数依赖F:

      F={Sno->Sdept, Sdept->Mname, (Sno, Cno)->Grade}

  • 关系模式 Student<U, F> 中存在的问题:

    1. 数据冗余:浪费大量存储空间。每个系主任的姓名重复出现,次数与该系的学生所有课程成绩的出现次数相同
    2. 更新异常(Update Anomalies):数据冗余 ,更新数据时,维护数据完整性代价大。当某系主任更换时,必须修改与该系学生相关的每个记录
    3. 插入异常(Insertion Anomalies):如果一个系刚成立,尚无学生,无法把这个系及其系主任的信息存入数据库
    4. 删除异常(Deletion Anomalies):如果某个系的学生全部毕业了,删除该系学生信息的同时也丢失了该系和系主任的信息
  • 结论

    • Student关系模式不是一个好的模式
    • 一个好的模式应当不会发生插入异常、删除异常和更新异常,数据冗余应尽可能少
  • 原因:由存在于模式中的某些数据依赖引起的

  • 解决方法:用规范化理论改造关系模式来消除其中不合适的数据依赖

  • 把这个单一的模式分成三个关系模式:

    • S(Sno,Sdept,Sno->Sdept)
    • SC(Sno,Cno,Grade,(Sno,Cno)->Grade)
    • DEPT(Sdept,Mname,Sdept->Mname)
    • 这三个模式都不会发生插入异常、删除异常的问题,数据的冗余也得到了控制

规范化

建议观看视频学习

函数依赖

函数依赖的定义

  • 定义1:设R(U)是一个属性集U上的关系模式,X和Y是U的子集。若对于R(U)的任意一个可能的关系r,r中不可能存在两个元组在X上的属性值相等, 而在Y上的属性值不等,则称X函数确定YY函数依赖于X,记作X->Y

    • 通俗的讲就是:X中的值唯一决定了Y中的值

    • 例如在下图中,两个元组Sno相等,而Sdept不等,违背了Sno->Sdept,因此下表是错误的

  • 例:Student(Sno, Sname, Ssex, Sage, Sdept),假设不允许重名,则有

    \[Sno\rightarrow Ssex, Sno\rightarrow Sage, Sno\rightarrow Sdept, Sno\harr Sname\\ Sname\rightarrow Ssex, Sname\rightarrow Sage, Sname\rightarrow Sdept \]

    但是

    \[Ssex\nrightarrow Sage, Ssex\nrightarrow Sdept \]

  • 函数依赖是语义范畴的概念,只能根据数据的语义来确定一个函数依赖

    • 例如姓名->年龄这个函数依赖只有在不允许有同名人的条件下成立

平凡函数依赖与非平凡函数依赖

  • X->Y,但Y⊈X则称X->Y是非平凡的函数依赖

  • X->Y,但Y⊆X则称X->Y是平凡的函数依赖

    对于任一关系模式,平凡函数依赖都是必然成立的,它不反映新的语义

    若不特别声明,我们总是讨论非平凡函数依赖

  • 若X->Y,则X称为这个函数依赖的决定因素(Determinant)

  • 若X->Y,Y->X,则记作X<-->Y

  • 若Y不函数依赖于X,则记作X↛Y

  • 定义2:在R(U)中,如果X->Y,并且对于X的任何一个真子集 \(X'\),都有 \(X'\nrightarrow Y\),则称Y对X完全函数依赖,记作 \(X\overset{F}{\rightarrow}Y\) ;若X->Y,但Y不完全函数依赖于X,则称Y对X部分函数依赖,记作 \(X \overset{P}{\rightarrow}Y\)

  • 例:在关系SC(Sno, Cno, Grade)中,有:

    \[Sno\nrightarrow Grade,Cno\nrightarrow Grade\\ (Sno,Cno)\overset{F}{\rightarrow}Grade,(Sno,Cno)\overset{P}{\rightarrow}Sno,(Sno,Cno)\overset{P}{\rightarrow}Cno \]

  • 定义3:在R(U)中,如果X->Y(Y⊈X),Y↛X,Y->Z,Z⊈Y,则称Z对X传递函数依赖(transitive functional dependency)。记为: \(X\overset{传递}{\rightarrow}Y\)

    注: 如果Y->X, 即X<-->Y,则Z直接依赖于X,而不是传递函数依赖。

  • 例:在关系Std(Sno, Sdept, Mname)中,有:

    \[Sno\rightarrow Sdept,Sdept\rightarrow Mname \]

  • 定义4:设K为 R<U,F> 中的属性或属性组合。\(K\overset{F}{\rightarrow}U\) ,则K称为R的一个候选码(Candidate Key)

    • 如果U部分函数依赖于K,即 \(K\overset{P}{\rightarrow}U\) ,则K称为超码(Surpkey)
    • 候选码是最小的超码,即K的任意一个真子集都不是候选码(注意候选码是最小的超码)

    候选码能推出所有的属性,候选码可能有多个

    所以在这里有一个总结,这里是在第二章中对候选码定义的部分加以修改:若关系中的某一属性组的值能唯一地标识一个元组,而其真子集不能,则称该属性组为候选码

    举例:

    若学生表(Sno,Sname,Sdept),其中Sno和Sname唯一,那么其候选码为Sno和Sname,而没有(Sno,Sname),因为其真子集还能唯一地标识一个元组

    • 若关系模式R有多个候选码,则选定其中的一个做为主码(Primary key)
  • 主属性与非主属性

    • 候选码中的任何一个属性,称为主属性(Prime attribute)

    • 不包含在任何侯选码中的属性称为非主属性(Nonprime attribute)或非码属性(Non-key attribute)

      • 例:有关系模式 R(A, B, C, D, E) 和候选码 {(A, B)};那么主属性是 A 和 B,非主属性是 C、D 和 E
    • 全码:整个属性组是(候选)码,称为全码(All-key)

      后面中主码或候选码都简称为码

  • 例2:S(Sno,Sdept,Sage),单个属性Sno是码;SC(Sno,Cno,Grade)中,(Sno,Cno)是码

  • 例3:在关系R(P,W,A)中,P是演奏者,W是作品,A是听众。假设一个演奏者可以演奏多个作品,某一作品可被多个演奏者演奏,听众可以欣赏不同演奏者的不同作品

    码为(P,W,A),即All-Key

  • 定义5:关系模式R中属性或属性组X并非R的码,但X是另一个关系模式的码,则称X是R的外部码(Foreign key)也称外码

    • SC(Sno,Cno,Grade)中,Sno不是码
    • Sno是S(Sno,Sdept,Sage)的码,则Sno是SC的外码

范式

看这篇文章

  • 范式是符合某一种级别的关系模式的集合

  • 关系数据库中的关系必须满足一定的要求。满足不同程度要求的为不同范式

  • 范式的种类:

  • 各种范式之间存在联系

    \[5NF\subset 4NF\subset BCNF\subset 3NF\subset 2NF\subset 1NF \]

    • 某一关系模式R为第n范式,可简记为 \(R\in nNF\)
  • 一个低一级范式的关系模式,通过模式分解(schema decomposition)可以转换为若干个高一级范式的关系模式的集合,这种过程就叫规范化(normalization)

  • 在前面提过:每个分量必须是不可分割的数据项,满足这个条件的关系模式属于第一范式(1NF)

2NF

  • 定义6:若关系模式 \(R\in 1NF\) ,并且每个非主属性都完全函数依赖于任何一个候选码,则 \(R\in 2NF\)

    • 消除部分依赖
  • 例4:S-L-C(Sno,Sdept,Sloc,Cno,Grade),Sloc为学生的住处,并且每个系的学生住在同一个地方。S-L-C的码为(Sno,Cno),其函数依赖有

    \[\begin{aligned} &(Sno,Cno)\overset{F}{\rightarrow}Grade\\ &Sno\rightarrow Sdept,(Sno,Cno)\overset{P}{\rightarrow}Sdept\\ &Sno\rightarrow Sloc,(Sno,Cno)\overset{P}{\rightarrow}Sloc\\ &Sdept\rightarrow Sloc\\ \end{aligned} \]

    • 虚线表示部分函数依赖
    • 可以看到非主属性Sdept、Sloc并不完全依赖于码
    • 关系模式S-L-C不属于2NF
  • 一个关系模式不属于2NF,会产生以下问题

    1. 插入异常:如果插入一个新学生,但该生未选课,即该生无Cno,由于插入元组时,必须给定码值,因此插入失败
    2. 删除异常:如果S4只选了一门课C3,现在他不再选这门课,则删除C3后,整个元组的其他信息也被删除了
    3. 修改复杂:如果一个学生选了多门课,则Sdept,Sloc被存储了多次。如果该生转系,则需要修改所有相关的Sdept和Sloc,造成修改的复杂化
    4. 数据冗余
  • 出现这种问题的原因

    • 例子中有两类非主属性:一类如Grade,它对码完全函数依赖;另一类如Sdept、Sloc,它们对码不是完全函数依赖
  • 解决方法:

    • 用投影分解把关系模式S-L-C分解成两个关系模式:

      • SC(Sno,Cno,Grade)

      • S-L(Sno,Sdept,Sloc)

    • SC的码为(Sno,Cno),SL的码为Sno,这样使得非主属性对码都是完全函数依赖了

3NF

  • 定义7:设关系模式 \(R<U,F>\in1NF\),若R中不存在这样的码X,属性组Y及非主属性 \(Z(Z \nsupseteq Y)\) , 使得X->Y,Y->Z成立,\(Y\nrightarrow X\) 不成立,则称 \(R<U,F>\in3NF\)

    • 条件 \(Z \nsupseteq Y\),是为了满足非平凡的函数依赖,确保依赖Y->Z成立,避免了数据中的部分冗余
    • \(R\in3NF\),则每一个非主属性既不传递依赖于码,也不部分依赖于码
  • 在上面的例4结果中

    • SC没有传递依赖,因此 \(SC\in3NF\)
    • S-L中 \(Sno\rightarrow Sdept(Sdept\nrightarrow Sno)\)\(Sdept\rightarrow Sloc\),可得 \(Sno\overset{传递}{\rightarrow}Sloc\) ,因此 \(S-L\notin3NF\)
    • 解决的办法是将S-L分解成
      • S-D(Sno,Sdept)∈3NF
      • D-L(Sdept,Sloc)∈3NF
  • 一个关系模式不属于3NF,S-L表会产生以下问题

    1. 插入异常:如果某个新的院校没有学生,那么系信息和地点信息无法储存到数据库
    2. 删除异常:如果删除了一个院校的所有学生,那么系信息也将被删除掉
    3. 修改复杂:如果要更改某个系的地点,那么整个表的地点信息都需要更改,造成修改的复杂化
    4. 数据冗余

BCNF

  • BCNF(Boyce Codd Normal Form)由Boyce和Codd提出,比3NF更进了一步。通常认为BCNF是修正的第三范式,有时也称为扩充的第三范式

  • 定义8:设模式 \(R<U,F>\in1NF\),若X->Y且 \(Y\nsubseteq X\) 时X必含有码,则 \(R<U,F>\in BCNF\)

  • 换言之,在关系模式R<U,F>中,如果每一个决定属性集都包含候选码即超码,则 \(R\in BCNF\)

  • BCNF的关系模式所具有的性质(不宜懂)

    • 所有非主属性都完全函数依赖于每个候选码
      • 根据定义“X->Y且 \(Y\nsubseteq X\) 时X必含有码”,设Y为非主属性,且不包含在X中;因为性质是定义的必要条件,所以根据定义“X必含有码”,那我们就指定X为任一(候选)码。根据“X->Y且 \(Y\nsubseteq X\) ”和完全函数依赖的定义,可以推出非主属性Y完全函数依赖每个候选码X
      • 这是为了消除非主属性的部分函数依赖和传递函数依赖
    • 所有主属性都完全函数依赖于每个不包含它的候选码
      • 消除主属性对候选码的部分和传递函数依赖
      • 例:看这个之前先看下面一段的内容。有关系模式 R(A, B, C, D, E) 和候选码 {(A, B),(B, C)},对于A,不包含它的候选码为(B, C),只有当BC->A时是完全函数依赖;如果有C->A,那么主属性和候选码产生了部分依赖
    • 没有任何属性完全函数依赖于非码的任何一组属性
  • 任何属性(无论是主属性还是非主属性)不能对非超码存在函数依赖

    • 相比第三范式,BC范式消除了主属性对码的部分依赖和传递依赖
  • 如果一个关系数据库中的所有关系模式都属于BCNF,那么在函数依赖范畴内,它已实现了模式的彻底分解,达到了最高的规范化程度,消除了插入异常和删除异常

  • 3NF的“不彻底”性表现在可能存在主属性对码的部分依赖和传递依赖

  • 例5:考察关系模式C(Cno,Cname,Pcno)

    • 它只有一个码Cno,没有任何属性对Cno部分依赖或传递依赖,所以 \(C\in3NF\)
    • 同时C中Cno是唯一的决定因素,所以 \(C\in BCNF\)
    • 对于关系模式SC(Sno,Cno,Grade)可作同样分析
  • 例6:关系模式S(Sno,Sname,Sdept,Sage)

    • 假定Sname也具有唯一性,那么S就有两个码,这两个码都由单个属性组成,彼此不相交
    • 其他属性不存在对码的传递依赖与部分依赖,所以 \(STJ\in 3NF\)
    • 同时S中除Sno,Sname外没有其他决定因素,所以S也属于BCNF
  • 例7:关系模式SJP(S,J,P)中,S是学生,J表示课程,P表示名次。每一个学生选修每门课程的成绩有一定的名次,每门课程中每一名次只有一个学生(即没有并列名次)

    • 由语义可得到函数依赖:(S,J)->P;(J,P)->S
    • (S,J)与(J,P)都可以作为候选码
    • 关系模式中没有属性对码传递依赖或部分依赖,所以 \(STJ\in 3NF\)
    • 除(S,J)与(J,P)以外没有其他决定因素,所以 \(STJ\in BCNF\)
  • 例8:关系模式STJ(S,T,J)中,S表示学生,T表示教师,J表示课程。每一教师只教一门课。每门课有若干教师,某一学生选定某门课,就对应一个固定的教师

    • 由语义可得到函数依赖:(S,J)->T;(S,T)->J;T->J

    • 因为没有任何非主属性对码传递依赖或部分依赖, \(STJ\in 3NF\)

    • 因为T是决定因素,而T不包含码,所以 \(STJ\notin BCNF\) 关系

  • 对于不是BCNF的关系模式,仍然存在不合适的地方,在STJ(S,T,J)中

    • 插入异常:如果某个课程没有学生,那么课程信息无法储存到数据库
    • 删除异常:如果删除了某个课程的所有学生,那么课程信息也将被删除掉
    • 修改复杂:如果要更改教师所教的课程,那么整个表的课程信息都需要更改
    • 数据冗余
  • 非BCNF的关系模式也可以通过分解成为BCNF。例如STJ可分解为ST(S,T)与TJ(T,J),它们都是BCNF

*多值依赖

  • 例9:设学校中某一门课程由多个教师讲授,他们使用相同的一套参考书。每个教员可以讲授多门课程,每种参考书可以供多门课程使用。用关系模式Teaching(C,T,B)来表示课程C、教师T和参考书B之间的关系。

    • 非规范化关系示例

    • 规范化的二维表Teaching

    • Teaching具有唯一候选码(C,T,B),即全码,所以Teaching∈BCNF

    • 存在的问题:

      1. 数据冗余度大:有多少名任课教师,参考书就要存储多少次
      2. 增加操作复杂:当某一课程增加一名任课教师时,该课程有多少本参照书,就必须插入多少个元组
      3. 删除操作复杂:某一门课要去掉一本参考书,该课程有多少名教师,就必须删除多少个元组
      4. 修改操作复杂:某一门课要修改一本参考书,该课程有多少名教师,就必须修改多少个元组
    • 产生这些的原因:存在多值依赖

  • 定义9:设R(U)是属性集U上的一个关系模式。X,Y,Z是U的子集,并且Z=U-X-Y。关系模式R(U)中多值依赖 \(X\rightarrow\rightarrow Y\) 成立,当且仅当对R(U)的任一关系r,给定的一对(x,z)值,有一组Y的值,这组值仅仅决定于x值而与z值无关

  • 例:Teaching(C, T, B)对于C的每一个值,T有一组值与之对应,而不论B取何值。因此T多值依赖于C,即C->->T

  • 平凡多值依赖和非平凡的多值依赖

    • 若X->->Y,而 \(Z=\phi\),即Z为空,则称X->->Y为平凡的多值依赖
    • 否则称X->->Y为非平凡的多值依赖
  • 例10:关系模式WSC(W,S,C)中,W表示仓库,S表示保管员,C表示商品。假设每个仓库有若干个保管员,有若干种商品。每个保管员保管所在仓库的所有商品,每种商品被所有保管员保管

    • 关系如下表所示

    • 按照语义对于W的每一个值 \(W_i\),S有一个完整的集合与之对应而不问C取何值。所以W->->S

    • 如下图所示

      • 对应W的某一个值 \(W_i\) 的全部S值记作 \(\{S\}_{W_i}\)(表示此仓库工作的全部保管员)
      • 全部C值记作 \(\{C\}_{W_i}\)(表示在此仓库中存放的所有商品)
      • 应当有 \(\{S\}_{W_i}\) 中的每一个值和 \(\{C\}_{W_i}\) 中的每一个C值对应
      • 于是 \(\{S\}_{W_i}\)\(\{C\}_{W_i}\) 之间正好形成一个完全二分图,因而W->->S
    • 由于C与S的完全对称性,必然有W->->C成立

  • 多值依赖的性质(看不懂)

    1. 多值依赖具有对称性
      • 即若X->->Y,则X->->Z,其中Z=U-X-Y
      • 多值依赖的对称性可以用完全二分图直观地表示出来。完全二分图解释:节点可以被分为两组,每个节点都与另一组中的所有节点连接。
      • 从例10容易看出,因为每个保管员保管所有商品,同时每种商品被所有保管员保管,显然若W->->S,必然有W->->C
    2. 多值依赖具有传递性。即若X->->Y,Y->->Z,则X->->Z-Y
    3. 函数依赖是多值依赖的特殊情况。即若X->Y,则X->->Y
      • 对于X->Y,意味着在X属性组中一个确定的值对应着Y属性组中唯一的值
      • 而X->->Y意味着在X属性组中一个确定的值对应着Y属性组中一组值。当然,在集合的角度上,唯一的值也可构成单元素集合
    4. 若X->->Y,X->->Z,则X->->YZ
    5. 若X->->Y,X->->Z,则X->->Y∩Z
    6. 若X->->Y,X->->Z,则X->->Y-Z,X->->Z-Y

*4NF

  • 定义10:关系模式 \(R<U,F>\in 1NF\),如果对于R的每个非平凡多值依赖 \(X\rightarrow \rightarrow Y(Y\nsubseteq X)\),X都含有码,则 \(R<U,F>\in 4NF\)
  • 4NF就是限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖。4NF所允许的非平凡多值依赖实际上是函数依赖。

小结

数据依赖的公理系统

定义较多需要记忆

  • 定义6.11:对于满足一组函数依赖F的关系模式,R<U,F>其任何一个关系r,若函数依赖X->Y都成立(即r中任意两元组t、s,若t[X]=s[X],则t[Y]=s[Y]),则称F逻辑蕴涵X->Y

  • Armstrong公理系统

    • 一套推理规则,是模式分解算法的理论基础

    • 是用来求给定关系模式的码,从一组函数依赖求得蕴涵的函数依赖

    • 设U为属性集总体,F是U上的一组函数依赖,于是有关系模式R<U,F>。对R<U,F>来说有以下的推理规则:

      • A1 自反律(reflexivity rule):若 \(Y\subseteq X\subseteq U\),则X->Y为F所蕴涵
      • A2 增广律(augmentation rule):若X->Y为F所蕴涵,且 \(Z\subseteq U\),则XZ->YZ为F所蕴涵
      • A3 传递律(transitivity rule):若X->Y及Y->Z为F所蕴涵,则X->Z为F所蕴涵

      注意:由自反律所得到的函数依赖均是平凡的函数依赖,自反律的使用并不依赖于F

  • 定理6.1Armstrong推理规则正确性证明

    • A1 自反律:

      \(Y\subseteq X\subseteq U\) ,对R<U,F>的任一关系r中的任意两个元组t、s:若其在属性组X上取值相同,即t[X]=s[X],由于 \(Y\subseteq X\),所以它们在属性组Y上的取值也相等,即t[Y]=s[Y],所以X->Y成立,自反律得证

    • A2 增广律:

      设X->Y为F所蕴涵,且 \(Z\subseteq U\) ,对R<U,F>的任一关系r中任意的两个元组t、s:若t[XZ]=s[XZ],则有t[X]=s[X]和t[Z]=s[Z];由X->Y,于是有t[Y]=s[Y],所以t[YZ]=s[YZ],XZ->YZ为F所蕴涵,增广律得证

    • A3 传递律

      设X->Y及Y->Z为F所蕴涵。对R<U,F>的任一关系r中的任意两个元组t、s:若t[X]=s[X],由于X->Y,有t[Y]=s[Y];再由Y->Z,有t[Z]=s[Z],所以X->Z为F所蕴涵,传递律得证

  • 根据A1,A2,A3这三条推理规则可以得到下面三条推理规则:

    • 合并规则(union rule):由X->Y,X->Z,有X->YZ
      • 根据X->Y,X->Z有:t[X]=s[X]则t[Y]=s[Y]、t[Z]=s[Z],根据t[Y]=s[Y]、t[Z]=s[Z]有t[YZ]=s[YZ],于是X->YZ
    • 伪传递规则(pseudo transitivity rule):由X->Y,YW->Z,有XW->Z
      • 增广律有XW->YW,YW->Z传递律有XW->Z
    • 分解规则(decomposition rule):由X->Y及 \(Z\subseteq Y\),有X->Z
      • 自反律 \(Z\subseteq Y\) 有Y->Z,传递律X->Z
  • 引理6.1\(X\rightarrow A_1A_2\cdots A_k\) 成立的充分必要条件\(X\rightarrow A_i\) 成立(i=1,2,...,k)

  • 定义6.12:在关系模式R<U,F>中为F所逻辑蕴涵的函数依赖的全体叫作F的闭包,记为 \(F^+\)

    • 举例:有一个关系模式R(A,B,C)和一个函数依赖集F:{A->B,B->C}。根据定义,F的闭包 \(F^+\) 是{A->B,B->C,A->C},因为我们可以通过推导得出A->C
  • 定义6.13:设F为属性集U上的一组函数依赖,\(X\subseteq U\)\(X_F^+=\{A|X\rightarrow A 能由F根据Armstrong公理导出\}\)\(X_F^+\) 称为属性集X关于函数依赖集F的闭包

    • 举例:有属性集X={A,B},函数依赖集F包含了{A->C,B->D,C->E},那么 \(X_F^+\) 包含{A,B,C,D,E},因为根据F中的函数依赖,我们可以推导出X中的所有属性。
  • 引理6.2:设F为属性集U上的一组函数依赖,\(X、Y\subseteq U\),X->Y能由F根据Armstrong公理导出的充分必要条件\(Y\subseteq X_F^+\)

    • 作用是判定X->Y是否能由F根据Armstrong公理导出的问题,就转化为求出 \(X_F^+\),判定Y是否为 \(X_F^+\) 的子集的问题
    • 例:有函数依赖集F={A->B,B->C,C->D},属性集X={A,B}和属性集Y={C,D},是否可以根据函数依赖集F导出函数依赖X->Y?求得 \(X_F^+\) 包含{A,B,C,D},且Y是 \(X_F^+\) 的子集,得证
  • 算法6.1:求属性集 \(X(X\subseteq U)\) 关于U上的函数依赖集F的闭包 \(X_F^+\)

    • 步骤
      1. \(X^{(i)}=X,i=0\)
      2. 求B,这里 \(B=\{A|(\exists V)(\exists W)(V\rightarrow W\in F\wedge V\subseteq X^{(i)}\wedge A\in W)\}\) ,其作用是对 \(X^{(i)}\) 中的每个元素,依次检查相应的函数依赖,将依赖它的属性加入B
      3. \(X^{(i+1)}=B\cup X^{(i)}\)
      4. 判断 \(X^{(i+1)}==X^{(i)}\) ,若相等或 \(X^{(i)}=U\)\(X^{(i)}\) 就是 \(X_F^+\) ,算法终止
      5. 若否,则i=i+1,返回第2步
  • 例6.11:已知关系模式R<U,F>,其中

    \[U=\{A,B,C,D,E\},F=\{AB\rightarrow C,B\rightarrow D,C\rightarrow E,EC\rightarrow B,AC\rightarrow B\} \]

    \((AB)_F^+\)

    解:由算法6.1,设 \(X^{(0)}=AB\)

    计算 \(X^{(1)}\):扫描F集合中各个函数依赖,找左部为A、B或AB的函数依赖。得到:B->C,B->D。于是 \(X^{(1)}=AB\cup CD=ABCD\)

    因为 \(X^{(0)}\neq X^{(1)}\),所以再找出左部为ABCD子集的那些函数依赖,又得到C->E,AC->B,于是 \(X^{(2)}=X^{(1)}\cup BE=ABCDE\)

    因为 \(X^{(2)}\) 已等于全部属性集合,所以 \((AB)_F^+=ABCDE\)

  • 定理6.2:Armstrong公理系统是有效的、完备的

    • 有效性:由F出发根据Armstrong公理推导出来的每一个函数依赖一定在 \(F^+\)

    • 完备性:\(F^+\) 中的每一个函数依赖,必定可以由F出发根据Armstrong公理推导出来

    • Armstrong公理系统的有效性可由定理6.1得证(证明可以不看)

    • 完备性证明:证明逆否命题,若函数依赖X->Y不能由F从Armstrong公理导出,那么它必然不为F所蕴涵

      1. 若V->W成立,且 \(V\subseteq X_F^+\) ,则 \(W\subseteq X_F^+\)

        • 证:因为 \(V\subseteq X_F^+\) ,所以有X->V成立;因为 X->V,V->W,于是X->W 成立;所以 \(W\subseteq X_F^+\)
      2. 构造一张二维表r,它由下列两个元组构成,可以证明r必是R<U,F>的一个关系,即F中的全部函数依赖在r上成立

        若r不是R<U,F>的关系,则必由于F中有某一个函数依赖V->W在r上不成立所致。由r的构成可知,V必定是 \(X_F^+\) 的子集,而W不是 \(X_F^+\) 的子集,可是由第一步,\(W\subseteq X_F^+\),矛盾。所以r必是R<U,F>的一个关系

      3. 若X->Y不能由F从Armstrong公理导出,则Y不是 \(X_F^+\) 的子集(引理6.2),因此必有Y的子集Y'满足 \(Y'\subseteq U-X_F^+\) ,则X->Y在r中不成立,即X->Y必不为R<U,F>蕴涵

  • Armstrong公理的完备性及有效性说明“导出”与“蕴涵”是两个完全等价的概念

    • \(F^+\):可以说成由F出发借助Armstrong公理导出的函数依赖的集合
  • 定义6.14:如果 \(G^+=F^+\),就说函数依赖集F覆盖G(F是G的覆盖,或G是F的覆盖),或F与G等价

    两个函数依赖集等价是指它们的闭包等价

  • 引理6.3\(G^+=F^+\)充分必要条件\(F\subseteq G^+和G\subseteq F^+\)

    • 给出了判断两个函数依赖集等价的可行算法
  • 引理6.3的证明:必要性显然,只证充分性

    1. \(F\subseteq G^+\),则 \(X_F^+\subseteq X_{G^+}^+\)
    2. 任取 \(X\rightarrow Y\in F^+\) 则有 \(Y\subseteq X_F^+\subseteq X_{G^+}^+\),所以 \(X\rightarrow Y\in G^+\)。即 \(F^+\subseteq G^+\)
    3. 同理可证 \(G^+\subseteq F^+\) ,所以 \(G^+=F^+\)
  • 定义6.15:如果函数依赖集F满足下列条件,则称F为一个极小函数依赖集,亦称为或最小覆盖

    1. F中任一函数依赖的右部仅含有一个属性
      • 避免冗余,如果有A->BC,可能同时存在A->B和A->C,这里A->BC就显得多余
    2. F中不存在这样的函数依赖X->A,使得F与F-{X->A}等价
      • 即F中的函数依赖均不能由F中其他函数依赖导出
    3. F中不存在这样的函数依赖X->A,X有真子集Z使得F-{X->A}∪{Z->A}与F等价
      • F中各函数依赖左部均为最小属性集(不存在冗余属性)
  • 例6.12:有关系模式S<U,F>,其中:

    \[\begin{aligned} &&U=&\{Sno, Sdept, Mname, Cno, Grade\}\\ &&F=&\{Sno\rightarrow Sdept, Sdept\rightarrow Mname, (Sno,Cno)\rightarrow Grade\}\\ &&F'=& \begin{aligned} \{&Sno→Sdept, Sno→Mname, Sdept→Mname,\\&(Sno,Cno)→Grade, (Sno,Sdept)→Sdept\} \end{aligned} \end{aligned} \]

    F是最小覆盖,F'不是最小覆盖

  • 定理6.3:每一个函数依赖集F均等价于一个极小函数依赖集 \(F_m\) 。此 \(F_m\) 称为F的最小依赖集

    • 证明:构造性证明,分三步对F进行“极小化处理”,找出F的一个最小依赖集

      1. 逐一检查F中各函数依赖 \(FD_i\)\(X\rightarrow Y\),若 \(Y=A_1A_2\cdots A_k,k\ge 2\),则用 \(\{X\rightarrow A_j|j=1,2,…,k\}\) 来取代 \(X\rightarrow Y\)。引理6.1保证了F变换前后的等价性

      2. 逐一检查F中各函数依赖 \(FD_i\)\(X\rightarrow A\),令 \(G=F-\{X\rightarrow A\}\),若 \(A\in X_G^+\),则从F中去掉此函数依赖。由于F与G等价的充要条件是 \(A\in X_G^+\),因此F变换前后是等价的

      3. 逐一取出F中各函数依赖 \(FD_i\)\(X\rightarrow A\),设 \(X=B_1B_2\cdots B_m,m\ge2\),逐一考查 \(B_i(i=1,2,\cdots,m)\),若 \(A\in(X-B_i)_F^+\),则以 \(X-B_i\) 取代X

        由于F与 \(F-\{X\rightarrow A\}\cup\{Z\rightarrow A\}\) 等价的充要条件是 \(A\in Z_F^+\),其中 \(Z=X-B_i\),因此F变换前后是等价的。最后剩下的F就一定是极小依赖集。因为对F的每一次“改造”都保证了改造前后的两个函数依赖集等价,因此剩下的F与原来的F等价

  • 例6.13:F={A->B,B->A,B->C,A->C,C->A},求F的最小依赖集

    \[\begin{aligned} &F_{m1}=\{A\rightarrow B, B\rightarrow C, C\rightarrow A\}\\ &F_{m2}=\{A\rightarrow B, B\rightarrow A, A\rightarrow C, C\rightarrow A\} \end{aligned} \]

第七章 数据库设计

数据库设计概述

  • 数据库设计是指对于一个给定的应用环境,构造(设计)优化的数据库逻辑模式和物理结构并据此建立数据库及其应用系统,使之能够有效地存储和管理数据,满足各种用户的应用需求,包括信息管理要求和数据操作要求。
  • 信息管理要求:在数据库中应该存储和管理哪些数据对象
  • 数据操作要求:对数据对象需要进行哪些操作,如查询、增、删、改、统计等操作
  • 数据库设计的目标是为用户和各种应用系统提供一个信息基础设施和高效率的运行环境
    • 数据库数据的存取效率高
    • 数据库存储空间的利用率高
    • 数据库系统运行管理的效率高

数据库设计的特点

数据库建设的基本规律

  • 三分技术,七分管理,十二分基础数据
  • 管理
    • 数据库建设项目管理
    • 企业(即应用部门)的业务管理
  • 基础数据
    • 数据的收集、整理、组织和不断更新

结构(数据)设计和行为(处理)设计相结合

  • 将数据库结构设计和数据处理设计密切结合

  • 在早期的数据库应用系统开发过程中,常常使用结构和行为分离的设计

  • 传统的软件工程:重行为设计

    • 忽视对应用中数据语义的分析和抽象,只要有可能就尽量推迟数据结构设计的决策
  • 早期的数据库设计:重结构设计

    • 致力于数据模型和数据库建模方法研究,忽视了行为设计对结构设计的影响

数据库设计方法

  • 大型数据库设计是涉及多学科的综合性技术,又是一项庞大的工程项目
  • 它要求多方面的知识和技术。主要包括:计算机的基础知识,软件工程的原理和方法,程序设计的方法和技巧,数据库的基本知识,数据库设计技术,应用领域的知识
  • 早期采用手工试凑法
    • 设计质量与设计人员的经验和水平有直接关系
    • 缺乏科学理论和工程方法的支持,工程的质量难以保证
    • 数据库运行一段时间后常常又不同程度地发现各种问题,增加了维护代价
  • 经过努力探索,提出了各种数据库设计方法
    • 新奥尔良(New Orleans)方法
    • 基于E-R模型的数据库设计方法
    • 3NF(第三范式)的设计方法
    • 面向对象的数据库设计方法
    • 统一建模语言(UML)方法

数据库设计的基本步骤

  • 数据库设计分6个阶段:需求分析、概念结构设计、逻辑结构设计、物理结构设计、数据库实施、数据库运行和维护

  • 需求分析和概念设计独立于任何数据库管理系统

  • 逻辑设计和物理设计与选用的数据库管理系统密切相关

  • 参加数据库设计的人员

    • 系统分析人员和数据库设计人员
      • 自始至终参与数据库设计,其水平决定了数据库系统的质量
    • 数据库管理员和用户代表
      • 主要参加需求分析与数据库的运行和维护
    • 应用开发人员
      • 包括程序员和操作员
      • 在实施阶段参与进来,分别负责编制程序和准备软硬件环境
  • 6个阶段过程

    1. 需求分析阶段
      • 是否做得充分与准确,决定了构建数据库的速度和质量
    2. 概念结构设计阶段
      • 通过对用户需求进行综合、归纳与抽象,形成一个独立于具体数据库管理系统的概念模型
    3. 逻辑结构设计阶段
      • 将概念结构转换为某个数据库管理系统所支持的数据模型,并对其进行优化
    4. 物理结构设计阶段
      • 为逻辑数据结构选取一个最适合应用环境的物理结构
      • 包括存储结构和存取方法
    5. 数据库实施阶段
      • 根据逻辑设计和物理设计的结果构建数据库
      • 编写与调试应用程序
      • 组织数据入库并进行试运行
    6. 数据库运行和维护阶段
      • 经过试运行后即可投入正式运行
      • 在运行过程中必须不断对其进行评估、调整与修改
  • 设计一个完善的数据库应用系统 往往是上述6个阶段的不断反复

  • 这个设计步骤既是数据库设计的过程,也包括了数据库应用系统的设计过程

  • 把数据库的设计和对数据库中数据处理的设计紧密结合起来,将这两个方面的需求分析、抽象、设计、实现在各个阶段同时进行,相互参照,相互补充,以完善两方面的设计

数据库设计过程中的各级模式

  • 数据库设计不同阶段形成的数据库各级模式

  • 需求分析阶段:综合各个用户的应用需求

  • 概念设计阶段:形成独立于机器特点,独立于各个数据库管理系统产品的概念模式(E-R图)

  • 逻辑设计阶段:

    1. 首先将E-R图转换成具体的数据库产品支持的数据模型,如关系模型,形成数据库逻辑模式
    2. 然后根据用户处理的要求、安全性的考虑,在基本表的基础上再建立必要的视图(View),形成数据的外模式
  • 物理设计阶段:根据数据库管理系统特点和处理的需要,进行物理存储安排,建立索引,形成数据库内模式

需求分析

  • 需求分析就是分析用户的要求,是设计数据库的起点,其结果是否准确地反映了用户的实际要求,将直接影响到后面各个阶段的设计,并影响到设计结果是否合理和实用

需求分析的任务

  • 详细调查现实世界要处理的对象(组织、部门、企业等),充分了解原系统(手工系统或计算机系统)工作概况,明确用户的各种需求,在此基础上确定新系统的功能,新系统必须充分考虑今后可能的扩充和改变
  • 调查的重点是“数据”和“处理”,获得用户对数据库的要求
    1. 信息要求
      • 用户需要从数据库中获得信息的内容与性质
      • 由信息要求可以导出数据要求,即在数据库中需要存储哪些数据
    2. 处理要求
      • 用户要完成的处理功能
      • 对处理性能的要求
    3. 安全性与完整性要求
  • 确定用户最终需求的难点
    • 用户缺少计算机知识,不能准确地表达自己的需求,他们所提出的需求往往不断地变化
    • 设计人员缺少用户的专业知识,不易理解用户的真正需求,甚至误解用户的需求
    • 所以设计人员必须不断深入地与用户进行交流,才能逐步确定用户的实际需求

需求分析的方法

  • 需求分析首先是调查清楚用户的实际需求,与用户达成共识并分析与表达这些需求

  • 调查用户需求的步骤

    1. 调查组织机构情况。了解该组织的部门组成情况、各部门的职责等
    2. 调查各部门的业务活动情况。了解各部门输入和使用什么数据等
    3. 协助用户明确对新系统的各种要求,包括信息要求、处理要求、完全性与完整性要求
    4. 确定新系统的边界。确定哪些功能由计算机完成,哪些活动由人工完成
  • 常用调查方法

    1. 跟班作业。通过亲身参加业务工作了解业务活动的情况
    2. 开调查会。通过与用户座谈来了解业务活动情况及用户需求
    3. 请专人介绍
    4. 询问。对某些调查中的问题,可以找专人询问
    5. 设计调查表请用户填写。调查表设计合理,则很有效
    6. 查阅记录。查阅与原系统有关的数据记录
  • 调查以后还需要进一步分析和表达用户的需求

    • 结构化分析方法(Structured Analysis,简称SA方法):SA方法从最上层的系统组织机构入手,采用自顶向下、逐层分解的方式分析系统
    • 对用户需求进行分析与表达后,需求分析报告必须提交给用户,征得用户的认可

数据字典

  • 数据字典是进行详细的数据收集和数据分析所获得的主要结果

  • 数据字典是关于数据库中数据的描述,即元数据,不是数据本身

  • 数据字典在需求分析阶段建立,在数据库设计过程中不断修改、充实、完善

    注意:和关系数据库管理系统中数据字典的区别和联系

    这里的数据字典更偏向于文档或外部文件,主要用于描述和理解数据;而RDBMS中的数据字典是数据库的一个实际组成部分,存储关于数据库结构的元数据(如表、视图、索引、约束等)

  • 数据字典的内容:数据项、数据结构、数据流、数据存储、处理过程

  • 数据项是数据的最小组成单位,若干个数据项可以组成一个数据结构,数据字典通过对数据项和数据结构的定义来描述数据流、数据存储的逻辑内容

数据项

  • 数据项是不可再分的数据单位

  • 数据项描述={数据项名,数据项含义说明,别名,数据类型,长度,取值范围,

    ​ 取值含义,与其他数据项的逻辑关系,数据项之间的联系}

  • 取值范围 和 与其他数据项的逻辑关系 定义了数据的完整性约束条件,是设计数据检验功能的依据

  • 可以用关系规范化理论为指导,用数据依赖的概念分析和表示数据项之间的联系

数据结构

  • 数据结构反映了数据之间的组合关系
  • 一个数据结构可以由若干个数据项组成,也可以由若干个数据结构组成,或由若干个数据项和数据结构混合组成
  • 例如客户信息可以是一个数据结构,包括客户姓名、联系信息、交易历史等数据项
  • 数据结构描述={数据结构名,含义说明,组成:{数据项或数据结构}}

数据流

  • 数据流是数据结构在系统内传输的路径
  • 数据流描述={数据流名,说明,数据流来源,数据流去向,组成:{数据结构},平均流量,高峰期流量}
    • 数据流来源:说明该数据流来自哪个过程
    • 数据流去向:说明该数据流将到哪个过程去
    • 平均流量:在单位时间(每天、每周、每月等)里的传输次数
    • 高峰期流量:在高峰时期的数据流量

数据存储

  • 数据存储是数据结构停留或保存的地方,也是数据流的来源和去向之一

  • 数据存储描述={数据存储名,说明,编号,输入的数据流,输出的数据流,

    ​ 组成:{数据结构},数据量,存取频度,存取方式}

    • 存取频度:每小时、每天或每周存取次数,每次存取的数据量等信息
    • 存取方法:批处理/联机处理;检索/更新;顺序检索/随机检索
    • 输入的数据流:数据来源
    • 输出的数据流:数据去向

处理过程

  • 处理过程的具体处理逻辑一般用判定表或判定树来描述。数据字典中只需要描述处理过程的说明性信息
  • 处理过程描述={处理过程名,说明,输入:{数据流},输出:{数据流},处理:{简要说明}}
  • 简要说明:说明该处理过程的功能及处理要求
    • 功能:该处理过程用来做什么
    • 处理要求:处理频度要求,如单位时间里处理多少事务,多少数据量、响应时间要求等
    • 处理要求是后面物理设计的输入及性能评价的标准

需求分析小结

  • 把需求收集和分析作为数据库设计的第一阶段是十分重要的
  • 第一阶段收集的基础数据(用数据字典来表达)是下一步进行概念设计的基础
  • 强调两点
    1. 设计人员应充分考虑到可能的扩充和改变,使设计易于更改,系统易于扩充
    2. 必须强调用户的参与

概念结构设计

概念模型

  • 将需求分析得到的用户需求抽象为信息结构(即概念模型)的过程就是概念结构设计
  • 概念模型的特点
    1. 能真实、充分地反映现实世界,是现实世界的一个真实模型
    2. 易于理解,从而可以用它和不熟悉计算机的用户交换意见
    3. 易于更改,当应用环境和应用要求改变时,容易对概念模型修改和扩充
    4. 易于向关系、网状、层次等各种数据模型转换

E-R模型

实体之间的联系

  1. 两个实体型之间的联系

    • 一对一联系(1∶1)
      • 如果对于实体集A中的每一个实体,实体集B中至多有一个(也可以没有)实体与之联系,反之亦然,则称实体集A与实体集B具有一对一联系,记为1∶1
      • 例如,学校里一个班级只有一个正班长,而一个班长只在一个班中任职,则班级与班长之间具有一对一联系。
    • 一对多联系(1∶n)
      • 如果对于实体集A中的每一个实体,实体集B中有n个实体(n≥0)与之联系,反之,对于实体集B中的每一个实体,实体集A中至多只有一个实体与之联系,则称实体集A与实体集B有一对多联系,记为1∶n
      • 例如,一个班级中有若干名学生,而每个学生只在一个班级中学习,则班级与学生之间具有一对多联系
    • 多对多联系(m∶n)
      • 如果对于实体集A中的每一个实体,实体集B中有n个实体(n≥0)与之联系,反之,对于实体集B中的每一个实体,实体集A中也有m个实体(m≥0)与之联系,则称实体集A与实体集B具有多对多联系,记为m∶n
      • 例如,一门课程同时有若干个学生选修,而一个学生可以同时选修多门课程,则课程与学生之间具有多对多联系
  2. 两个以上的实体型之间的联系

    • 一般地,两个以上的实体型之间也存在着一对一、一对多、多对多联系

    • 对于课程、教师与参考书3个实体型,如果一门课程可以有若干个教师讲授,使用若干本参考书,而每一个教师只讲授一门课程,每一本参考书只供一门课程使用,则课程与教师、参考书之间的联系是一对多的

  3. 单个实体型内的联系

    • 同一个实体集内的各实体之间也可以存在一对一、一对多、多对多的联系

    • 例如,职工实体型内部具有领导与被领导的联系,即某一职工(干部)“领导”若干名职工,而一个职工仅被另外一个职工直接领导,因此这是一对多的联系

    • 联系的度:参与联系的实体型的数目

      • 2个实体型之间的联系度为2,也称为二元联系
      • 3个实体型之间的联系度为3,称为三元联系
      • N个实体型之间的联系度为N,也称为N元联系

E-R图

  • E-R图提供了表示实体型、属性和联系的方法:

    • 实体型:用矩形表示,矩形框内写明实体名

    • 属性:用椭圆形表示,并用无向边将其与相应的实体型连接起来

    • 例如,学生实体具有学号、姓名、性别、出生年份、系、入学时间等属性,用E-R图表示如下

    • 联系:用菱形表示,菱形框内写明联系名,并用无向边分别与有关实体型连接起来,同时在无向边旁标上联系的类型(1∶1,1∶n或m∶n)

    • 联系可以具有属性,如下所示,关系供应也有对应的属性供应量

一个实例

  • 某个工厂物资管理的概念模型。物资管理涉及的实体有:

    • 仓库:属性有仓库号、面积、电话号码
    • 零件:属性有零件号、名称、规格、单价、描述
    • 供应商:属性有供应商号、姓名、地址、电话号码、账号
    • 项目:属性有项目号、预算、开工日期
    • 职工:属性有职工号、姓名、年龄、职称
  • 这些实体之间的联系如下:

    1. 一个仓库可以存放多种零件,一种零件可以存放在多个仓库中,因此仓库和零件具有多对多的联系。用库存量来表示某种零件在某个仓库中的数量
    2. 一个仓库有多个职工当仓库保管员,一个职工只能在一个仓库工作,因此仓库和职工之间是一对多的联系
    3. 职工之间具有领导与被领导关系。即仓库主任领导若干保管员,因此职工实体型中具有一对多的联系
    4. 供应商、项目和零件三者之间具有多对多的联系。即一 个供应商可以供给若干项目多种零件,每个项目可以使用不同供应商供应的零件,每种零件可由不同供应商供给
  • 此工厂的物资管理E-R图如下:

概念结构设计

实体与属性的划分原则

  • 为了简化E-R图的处置,现实世界的事物能作为属性对待的,尽量作为属性对待

  • 能作为属性对待两条准则:

    1. 作为属性,不能再具有需要描述的性质。属性必须是不可分的数据项,不能包含其他属性
    2. 属性不能与其他实体具有联系,即E-R图中所表示的联系是实体之间的联系
  • 例:职工是一个实体,职工号、姓名、年龄是职工的属性,职称如果没有与工资、福利挂钩,根据准则1可以作为职工实体的属性,如果不同的职称有不同的工资、住房标准和不同的附加福利,则职称作为一个实体更恰当

  • 例:在医院中,一个病人只能住在一个病房,病房号可以作为病人实体的一个属性;如果病房还要与医生实体发生联系,即一个医生负责几个病房的病人的医疗工作,则根据准则2病房应作为一个实体

  • 例:如果一种货物只存放在一个仓库,那么就可以把存放货物的仓库的仓库号作为描述货物存放地点的属性。如果一种货物可以存放在多个仓库中,或者仓库本身又用面积作为属性,或者仓库与职工发生管理上的联系,那么就应把仓库作为一个实体

  • 例1:销售管理子系统E-R图的设计

    • 该子系统的主要功能是:

      • 处理顾客和销售员送来的订单
      • 工厂是根据订货安排生产的
      • 交出货物同时开出发票
      • 收到顾客付款后,根据发票存根和信贷情况进行应收款处理
    • 参照需求分析和数据字典中的详尽描述,遵循前面给出的两个准则,进行了如下调整

      1. 每张订单由订单号、若干头信息和订单细节组成。订单细节又有订货的零件号、数量等来描述。按照准则1,订单细节就不能作订单的属性处理而应该上升为实体。一张订单可以订若干产品,所以订单与订单细节两个实体之间是1∶n的联系

      2. 原订单和产品的联系实际上是订单细节和产品的联系。每条订货细节对应一个产品描述,订单处理时从中获得当前单价、产品重量等信息

      3. 工厂对大宗订货给予优惠。每种产品都规定了不同订货数量的折扣,应增加一个“折扣规则”实体存放这些信息,而不应把它们放在产品实体中

    • 最后得到销售管理子系统E-R图

    • 对每个实体定义的属性如下:

      • 顾客:
      • 订单:
      • 订单细则:
      • 应收账款:
      • 产品:
      • 折扣规则:

E-R图的集成

  • E-R图的集成一般需要分两步

    • 合并。解决各分E-R图之间的冲突,将分E-R图合并起来生成初步E-R图
    • 修改和重构。消除不必要的冗余,生成基本E-R图
    1. 合并E-R图,生成初步E-R图

      • 各个局部应用所面向的问题不同,各个子系统的E-R图之间必定会存在许多不一致的地方,称之为冲突

      • 子系统E-R图之间的冲突主要有三类:

        1. 属性冲突
          • 属性域冲突,即属性值的类型、取值范围或取值集合不同
            • 例如零件号,有的部门把它定义为整数,有的部门把它定义为字符型
            • 例如年龄,某些部门以出生日期形式表示职工的年龄,而另一些部门用整数表示职工的年龄
          • 属性取值单位冲突。例如,零件的重量有的以公斤为单位,有的以斤为单位,有的以克为单位。
        2. 命名冲突
          • 同名异义,即不同意义的对象在不同的局部应用中具有相同的名字
          • 异名同义(一义多名),即同一意义的对象在不同的局部应用中具有不同的名字
            • 如对科研项目,财务科称为项目,科研处称为课题,生产管理处称为工程
          • 命名冲突
            • 可能发生在实体、联系一级上
            • 也可能发生在属性一级上
            • 通过讨论、协商等行政手段加以解决
        3. 结构冲突
          • 同一对象在不同应用中具有不同的抽象
            • 例如,职工在某一局部应用中被当作实体,而在另一局部应用中则被当作属性
            • 解决方法:把属性变换为实体或把实体变换为属性,使同一对象具有相同的抽象
          • 同一实体在不同子系统的E-R图中所包含的属性个数和属性排列次序不完全相同
            • 解决方法:使该实体的属性取各子系统的E-R图中属性的并集,再适当调整属性的次序
          • 实体间的联系在不同的E-R图中为不同的类型
            • 实体E1与E2在E-R图中是多对多联系,在另一个E-R图中是一对多联系
            • 解决方法是根据应用的语义对实体联系的类型进行综合或调整。
      • 例如零件与产品之间存在多对多的联系“构成”

      • 产品、零件与供应商三者之间还存在多对多的联系“供应”

      • 合并两个E-R图

    2. 消除不必要的冗余,设计基本E-R图

      • 所谓冗余的数据是指可由基本数据导出的数据,冗余的联系是指可由其他联系导出的联系

      • 消除冗余主要采用分析方法,即以数据字典和数据流图为依据,根据数据字典中关于数据项之间逻辑关系的说明来消除冗余

      • 如下图,\(Q_3=Q1\times Q_2,Q4=\sum Q_5\),所以Q3和Q4是冗余数据,可以消去;并且由于Q3消去,产品与材料间m:n的冗余联系也应消去。

        并不是所有的冗余数据与冗余联系都必须加以消除,有时为了提高效率,不得不以冗余信息作为代价。

      • 用规范化理论来消除冗余

        1. 确定分E-R图实体之间的数据依赖

          • 实体之间一对一、一对多、多对多的联系可以用实体码之间的函数依赖来表示。于是有函数依赖集 \(F_L\)

          • 如下图中:部门和职工之间一对多的联系可表示为职工号->部门号,职工和产品之间多对多的联系可表示为(职工号,产品号)->工作天数等

        2. \(F_L\) 的最小覆盖 \(G_L\) ,差集为 \(D=F_L-G_L\)

          • 逐一考察D中的函数依赖,确定是否是冗余的联系,若是,就把它去掉
          • 由于规范化理论受到泛关系假设的限制,应注意下面两个问题:
            • 冗余的联系一定在D中,而D中的联系不一定是冗余的
            • 当实体之间存在多种联系时,要将实体之间的联系在形式上加以区分
      • 例2:某工厂管理信息系统的视图集成

        工厂物资管理E-R图

        销售管理子系统的E-R图

        劳动人事管理的分E-R图

        某工厂管理信息系统的基本E-R图

      • 集成过程中,解决了以下问题:

        • 异名同义,项目和产品含义相同。某个项目实质上是指某个产品的生产。统一用产品作实体名
        • 库存管理中职工与仓库的工作关系已包含在劳动人事管理的部门与职工之间的联系之中,所以可以取消。职工之间领导与被领导关系可由部门与职工(经理)之间的领导关系、部门与职工之间的从属关系两者导出,所以也可以取消

逻辑结构设计

  • 逻辑结构设计的任务:把概念结构设计阶段设计好的基本E-R图转换为与选用数据库管理系统产品所支持的数据模型相符合的逻辑结构

E-R图向关系模型的转换

  • E-R图由实体型、实体的属性和实体型之间的联系三个要素组成

  • 关系模型的逻辑结构是一组关系模式的集合

  • 将E-R图转换为关系模型:将实体型、实体的属性和实体型之间的联系转化为关系模式

  • 一个实体型转换为一个关系模式

    • 关系的属性:实体的属性

    • 关系的码:实体的码

  • 实体型间的联系有以下不同情况

    1. 一个1:1联系可以转换为一个独立的关系模式,也可以与任意一端对应的关系模式合并

      1. 转换为一个独立的关系模式
        • 关系的属性:与该联系相连的各实体的码以及联系本身的属性
        • 关系的候选码:每个实体的码均是该关系的候选码
        • 例如有两个实体:PersonPassport,一个人只有一个护照,而一个护照只对应一个人
          • Person 有属性:PersonID (码), Name
          • Passport 有属性:PassportNumber (码), IssuedDate
          • 可以创建一个独立的关系模式 Person_Passport 来表示这个1:1关系
            • 属性:PersonID, PassportNumber, IssueLocation (假设IssueLocation是联系的一个属性)
            • 候选码:PersonID 或 PassportNumber
      2. 与某一端实体对应的关系模式合并
        • 合并后关系的属性:加入对应关系的码和联系本身的属性
        • 合并后关系的码:不变
        • 例如选择其中一个实体 Person,并与关系模式合并,合并后的 Person
          • 属性:PersonID, Name, PassportNumber, IssuedDate, IssueLocation
          • 码:PersonID
          • 注意:在这种情况下,我们把Passport的属性加入到Person中,但不再需要一个独立的Passport关系模式。
    2. 一个1:n联系可以转换为一个独立的关系模式,也可以与n端对应的关系模式合并

      1. 转换为一个独立的关系模式
        • 关系的属性:与该联系相连的各实体的码以及联系本身的属性
        • 关系的码:n端实体的码
        • 假设有两个实体:教师课程,其中一个教师可以教多个课程,但每个课程只由一个教师教授
          • 创建一个新的关系叫教授
          • 属性为:教师ID:教师实体的码、课程ID:课程实体的码、开始日期:表示教师开始教授该课程的日期,这是联系本身的一个属性
          • 这个新的关系模式的码是课程ID,因为对于每个课程,都有一个与之对应的教师
      2. 与n端对应的关系模式合并
        • 合并后关系的属性:在n端关系中加入1端关系的码和联系本身的属性
        • 合并后关系的码:不变
        • 可以减少系统中的关系个数,一般情况下更倾向于采用这种方法
        • 将联系合并到课程关系模式中
          • 属性:课程ID:课程实体的码、课程名称:课程的名称、教师ID:教师实体的码,将其加入课程关系中、开始日期:表示教师开始教授该课程的日期,这是联系本身的一个属性
          • 合并后关系的码仍然是课程ID
    3. 一个m:n联系转换为一个关系模式

      • 关系的属性:与该联系相连的各实体的码以及联系本身的属性
      • 关系的码:各实体码的组合
      • 例如“选修”联系是一个m:n联系,可以将它转换为如下关系模式,其中学号与课程号为关系的组合码:选修(学号,课程号,成绩)
    4. 三个或三个以上实体间的一个多元联系转换为一个关系模式

      • 关系的属性:与该多元联系相连的各实体的码以及联系本身的属性

      • 关系的码:各实体码的组合

      • 假设在一个项目管理系统中,有以下三个实体:

        • Employee有属性:EmployeeID (码), EmployeeName

        • Project有属性:ProjectID (码), ProjectName

        • Role有属性:RoleID (码), RoleDescription

        • 我们想表示的多元联系是“参与”,表示某个员工在某个项目中担任了某个角色,且这个联系有一个额外的属性:Duration(持续时间)

        • 这个多元联系“参与”转换为关系模式后如下:

        • Participation

          • 属性:EmployeeID, ProjectID, RoleID, Duration
          • 关系的码:EmployeeID, ProjectID, RoleID
    5. 具有相同码的关系模式可合并

      • 目的:减少系统中的关系个数
      • 合并方法:
        • 将其中一个关系模式的全部属性加入到另一个关系模式中
        • 然后去掉其中的同义属性(可能同名也可能不同名)
        • 适当调整属性的次序

数据模型的优化

  • 数据库逻辑设计的结果不是唯一的
  • 得到初步数据模型后,还应该适当地修改、调整数据模型的结构,以进一步提高数据库应用系统的性能,这就是数据模型的优化
  • 关系数据模型的优化通常以规范化理论为指导
  • 优化数据模型的方法
    1. 确定数据依赖:按需求分析阶段所得到的语义,分别写出每个关系模式内部各属性之间的数据依赖以及不同关系模式属性之间数据依赖
    2. 对于各个关系模式之间的数据依赖进行极小化处理,消除冗余的联系
    3. 按照数据依赖的理论对关系模式进行分析,考察是否存在部分函数依赖、传递函数依赖、多值依赖等,确定各关系模式分别属于第几范式
    4. 按照需求分析阶段得到的各种应用对数据处理的要求,分析对于这样的应用环境这些模式是否合适,确定是否要对它们进行合并或分解
      • 并不是规范化程度越高的关系就越优
        • 当查询经常涉及两个或多个关系模式的属性时,系统必须经常地进行连接运算
        • 连接运算的代价是相当高,因此在这种情况下,第二范式甚至第一范式也许是适合的
        • 非BCNF的关系模式虽然会存在不同程度的更新异常,但如果在实际应用中对此关系模式只是查询,并不执行更新操作,就不会产生实际影响
        • 对于一个具体应用来说,到底规范化进行到什么程度,需要权衡响应时间和潜在问题两者的利弊才能决定
    5. 对关系模式进行必要分解,提高数据操作效率和存储空间的利用率
      • 常用分解方法:水平分解、垂直分解
      • 水平分解是把(基本)关系的元组分为若干子集合,定义每个子集合为一个子关系,以提高系统的效率
        • 根据80/20原则,一个大关系中,经常使用的数据只占关系的约20%。把经常被使用的数据(约20%)水平分解出来,形成一个子关系
        • 水平分解为若干子关系,使每个事务存取的数据对应一个子关系
      • 垂直分解是把关系模式R的属性分解为若干子集合,形成若干子关系模式
        • 垂直分解的原则是把经常在一起使用的属性从R中分解出来形成一个子关系模式
        • 垂直分解可以提高某些事务的效率
        • 垂直分解可能使另一些事务不得不执行连接操作,降低了效率
        • 因此是否垂直分解取决于分解后R上的所有事务的总效率是否得到了提高
        • 进行垂直分解的方法
          • 简单情况:直观分解
          • 复杂情况:用第6章中的模式分解算法
          • 垂直分解必须不损失关系模式的语义(保持无损连接性和保持函数依赖)

设计用户子模式

  • 定义数据库模式主要是从系统的时间效率、空间效率、易维护等角度出发
  • 定义用户外模式时应该更注重考虑用户的习惯与方便。包括三个方面:
    1. 使用更符合用户习惯的别名
      • 合并各分E-R图曾做了消除命名冲突的工作,以使数据库系统中同一关系和属性具有唯一的名字,这在设计数据库整体结构时是非常必要的
      • 用视图机制可以在设计用户视图时可以重新定义某些属性名,使其与用户习惯一致,以方便使用
    2. 针对不同级别的用户定义不同的视图,以保证系统的安全性
      • 假设有关系模式产品(产品号,产品名,规格,单价,生产车间,生产负责人,产品成本,产品合格率,质量等级),可以在产品关系上建立两个视图
      • 为一般顾客建立视图:产品1(产品号,产品名,规格,单价)
      • 为产品销售部门建立视图:产品2(产品号,产品名,规格,单价,车间,生产负责人)
    3. 简化用户对系统的使用
      • 如果某些局部应用中经常要使用某些很复杂的查询,为了方便用户,可以将这些复杂查询定义为视图

物理结构设计

  • 数据库在物理设备上的存储结构与存取方法称为数据库的物理结构,它依赖于选定的数据库管理系统
  • 为一个给定的逻辑数据模型选取一个最适合应用要求的物理结构的过程,就是数据库的物理设计
  • 数据库物理设计的步骤
    • 确定数据库的物理结构:在关系数据库中主要指存取方法和存储结构
    • 对物理结构进行评价:评价的重点是时间和空间效率
    • 若评价结果满足原设计要求,则可进入到物理实施阶段。否则,就需要重新设计或修改物理结构,有时甚至要返回逻辑设计阶段修改数据模型

数据库物理设计的内容和方法

  • 设计物理数据库结构的准备工作
    • 充分了解应用环境,详细分析要运行的事务,以获得选择物理数据库设计所需参数
    • 充分了解所用关系型数据库管理系统的内部特征,特别是系统提供的存取方法和存储结构
  • 选择物理数据库设计所需参数
    • 数据库查询事务
      • 查询的关系
      • 查询条件所涉及的属性
      • 连接条件所涉及的属性
      • 查询的投影属性
    • 数据更新事务
      • 被更新的关系
      • 每个关系上的更新操作条件所涉及的属性
      • 修改操作要改变的属性值
      • 每个事务在各关系上运行的频率和性能要求
  • 关系数据库物理设计的内容
    • 为关系模式选择存取方法(建立存取路径)
    • 设计关系、索引等数据库文件的物理存储结构

关系模式存取方法选择

  • 数据库系统是多用户共享的系统,对同一个关系要建立多条存取路径才能满足多用户的多种应用要求
  • 物理结构设计的任务之一是根据关系数据库管理系统支持的存取方法确定选择哪些存取方法
  • 数据库管理系统常用存取方法
    1. B+树索引存取方法
    2. Hash索引存取方法
    3. 聚簇存取方法

B+树索引存取方法的选择

  • 选择索引存取方法:根据应用要求确定对哪些属性列建立索引、对哪些属性列建立组合索引、对哪些索引要设计为唯一索引
  • 选择索引存取方法的一般规则
    1. 如果一个(或一组)属性经常在查询条件中出现,则考虑在这个(或这组)属性上建立索引(或组合索引)
    2. 如果一个属性经常作为最大值和最小值等聚集函数的参数,则考虑在这个属性上建立索引
    3. 如果一个(或一组)属性经常在连接操作的连接条件中 出现,则考虑在这个(或这组)属性上建立索引
  • 关系上定义的索引数过多会带来较多的额外开销:维护索引的开销、查找索引的开销

HASH存取方法的选择

  • 选择Hash存取方法的规则:
    • 如果一个关系的属性主要出现在等值连接条件中或主要出现在等值比较选择条件中,而且满足下列两个条件之一
      • 该关系的大小可预知,而且不变
      • 该关系的大小动态改变,但所选用的数据库管理系统提供了动态Hash存取方法

聚簇存取方法的选择

  • 为了提高某个属性(或属性组)的查询速度,把这个或这些属性(称为聚簇码)上具有相同值的元组集中存放在连续的物理块中称为聚簇,该属性(或属性组)称为聚簇码(cluster key)
聚簇索引
  • 建立聚簇索引后,基表中数据也需要按指定的聚簇属性值的升序或降序存放。也即聚簇索引的索引项顺序与表中元组的物理顺序一致

  • 在一个基本表上最多只能建立一个聚簇索引

  • 聚簇索引的适用条件:很少对基表进行增删操作、很少对其中的变长列进行修改操作

  • 聚簇索引大大提高按聚簇属性进行查询的效率

    • 例:假设学生关系按所在系建有索引,现在要查询信息系的所有学生名单
    • 计算机系的500名学生分布在500个不同的物理块上时,至少要执行500次I/O操作
    • 如果将同一系的学生元组集中存放,则每读一个物理块可得到多个满足查询条件的元组,从而显著地减少了访问磁盘的次数
  • 聚簇索引节省存储空间

    • 聚簇以后,聚簇码相同的元组集中在一起了,因而聚簇码值不必在每个元组中重复存储,只要在一组中存一次就行了
  • 聚簇的局限性

    • 聚簇只能提高某些特定应用的性能
    • 建立与维护聚簇的开销相当大
      • 对已有关系建立聚簇,将导致关系中元组的物理存储位置移动,并使此关系上原有的索引无效,必须重建
      • 当一个元组的聚簇码改变时,该元组的存储位置也要做相应改变
  • 聚簇的适用范围

    • 既适用于单个关系独立聚簇,也适用于多个关系组合聚簇
    • 当通过聚簇码进行访问或连接是该关系的主要应用,与聚簇码无关的其他访问很少或者是次要的时,可以使用聚簇
    • 尤其当SQL语句中包含有与聚簇码有关的ORDER BY, GROUP BY, UNION, DISTINCT等子句或短语时,使用聚簇特别有利,可以省去或减化对结果集的排序操作
选择聚簇存取方法
  • 设计候选聚簇
    1. 常在一起进行连接操作的关系可以建立组合聚簇
    2. 如果一个关系的一组属性经常出现在相等比较条件中,则该单个关系可建立聚簇
    3. 如果一个关系的一个(或一组)属性上的值重复率很高,则此单个关系可建立聚簇
  • 然后检查候选聚簇中的关系,取消其中不必要的关系
    1. 从聚簇中删除经常进行全表扫描的关系
    2. 从聚簇中删除更新操作远多于连接操作的关系,
    3. 当一个关系有多个聚簇方案时,必须从这多个聚簇方案(包括不建立聚簇)中选择一个较优的,即在这个聚簇上运行各种事务的总代价最小

确定数据库的存储结构

  • 确定数据库物理结构主要指确定数据的存放位置和存储结构,包括:确定关系、索引、聚簇、日志、备份等的存储安排和存储结构,确定系统配置等
  • 确定数据的存放位置和存储结构要综合考虑存取时间、存储空间利用率和维护代价3个方面的因素,这三个方面常常是相互矛盾的

确定数据的存放位置

  • 为了提高系统性能,应该根据应用情况将数据的易变部分与稳定部分、经常存取部和存取频率较低部分分开存放
  • 例如目前很多计算机有多个磁盘或磁盘阵列,因此可以将表和索引放在不同的磁盘上,在查询时,由于磁盘驱动器并行工作,可以提高物理IO读写的效率
    • 可以将比较大的表分别放在两个磁盘上,以加快存取速度,这在多用户环境下特别有效
    • 可以将日志文件与数据库对象(表、索引等)放在不同的磁盘以改进系统的性能

确定系统配置

  • 数据库管理系统一般都提供了一些存储分配参数
    • 同时使用数据库的用户数
    • 同时打开的数据库对象数
    • 内存分配参数
    • 缓冲区分配参数(使用的缓冲区长度、个数)
    • 存储分配参数
    • 物理块的大小
    • 物理块装填因子
    • 时间片大小
    • 数据库的大小
    • 锁的数目等
  • 系统都为这些变量赋予了合理的缺省值。在进行物理设计时需要根据应用环境确定这些参数值,以使系统性能最优
  • 在物理设计时对系统配置变量的调整只是初步的,要根据系统实际运行情况做进一步的调整,以切实改进系统性能

评价物理结构

  • 对数据库物理设计过程中产生的多种方案进行评价,从中选择一个较优的方案作为数据库的物理结构
  • 主要是从定量估算各种方案的存储空间、存取时间、维护代价入手,对估算结果进行权衡、比较,选择出一个较优的合理的物理结构

数据库的实施和维护

数据的载入和应用程序的调试

  • 数据库结构建立好后,就可以向数据库中装载数据了。组织数据入库是数据库实施阶段最主要的工作
  • 数据装载方法:人工方法、计算机辅助数据入库
  • 数据库应用程序的设计应该与数据设计并行进行
  • 在组织数据入库的同时还要调试应用程序
  • 应用程序的设计、编码和调试的方法、步骤在软件工程等课程中有详细讲解,这里就不赘述了

数据库的试运行

  • 应用程序调试完成,并且已有一小部分数据入库后,就可以开始对数据库系统进行联合调试,也称数据库的试运行
  • 主要工作包括:
    • 功能测试:实际运行应用程序,执行对数据库的各种操作,测试应用程序的各种功能
    • 性能测试:测量系统的性能指标,分析是否符合设计目标
  • 数据库性能指标的测量
    • 数据库物理设计阶段在评价数据库结构估算时间、空间指标时,作了许多简化和假设,忽略了许多次要因素,因此结果必然很粗糙
    • 数据库试运行则是要实际测量系统的各种性能指标(不仅是时间、空间指标),如果结果不符合设计目标,则需要返回物理设计阶段,调整物理结构,修改参数;有时甚至需要返回逻辑设计阶段,调整逻辑结构
  • 数据的分期入库
    • 重新设计物理结构甚至逻辑结构,会导致数据重新入库
    • 由于数据入库工作量实在太大,所以可以采用分期输入数据的方法
      • 先输入小批量数据供先期联合调试使用
      • 待试运行基本合格后再输入大批量数据
      • 逐步增加数据量,逐步完成运行评价
  • 数据库的转储和恢复
    • 在数据库试运行阶段,系统还不稳定,硬、软件故障随时都可能发生
    • 系统的操作人员对新系统还不熟悉,误操作也不可避免
    • 因此必须做好数据库的转储和恢复工作,尽量减少对数据库的破坏

数据库的运行和维护

  • 在数据库运行阶段,对数据库经常性的维护工作主要是由数据库管理员完成的,包括:
    1. 数据库的转储和恢复
      • 数据库管理员要针对不同的应用要求制定不同的转储计划,定期对数据库和日志文件进行备份
      • 一旦发生介质故障,即利用数据库备份及日志文件备份,尽快将数据库恢复到某种一致性状态
    2. 数据库的安全性、完整性控制
      • 初始定义
        • 数据库管理员根据用户的实际需要授予不同的操作权限
        • 根据应用环境定义不同的完整性约束条件
      • 修改定义
        • 当应用环境发生变化,对安全性的要求也会发生变化,数据库管理员需要根据实际情况修改原有的安全性控制
        • 由于应用环境发生变化,数据库的完整性约束条件也会变化,也需要数据库管理员不断修正,以满足用户要求
    3. 数据库性能的监督、分析和改进
      • 在数据库运行过程中,数据库管理员必须监督系统运行,对监测数据进行分析,找出改进系统性能的方法
        • 利用监测工具获取系统运行过程中一系列性能参数的值
        • 通过仔细分析这些数据,判断当前系统是否处于最佳运行状态
        • 如果不是,则需要通过调整某些参数来进一步改进数据库性能
    4. 数据库的重组织与重构造
      1. 数据库的重组织
        • 数据库运行一段时间后,由于记录的不断增、删、改,会使数据库的物理存储变坏,从而导致存储空间的利用率和数据的存取效率,因此需要重组织数据库。
        • 重组织的形式
          • 全部重组织
          • 部分重组织:只对频繁增、删的表进行重组织
        • 重组织的目标:提高系统性能
        • 重组织的工作:按原设计要求重新安排存储位置、回收垃圾、减少指针链
        • 数据库的重组织不会改变原设计的数据逻辑结构和物理结构
        • 数据库管理系统一般都提供了供重组织数据库使用的实用程序,帮助数据库管理员重新组织数据库
      2. 数据库的重构造
        • 数据库应用环境发生变化,会导致实体及实体间的联系也发生相应的变化,使原有的数据库设计不能很好地满足新的需求
        • 数据库重构造的主要工作:根据新环境调整数据库的模式和内模式
          • 如增加或删除某些数据项、改变数据项的类型、增加或删除某个表、改变数据库的容量、增加或删除某些索引
        • 重构造数据库的程度是有限的
          • 若应用变化太大,已无法通过重构数据库来满足新的需求,或重构数据库的代价太大,则表明现有数据库应用系统的生命周期已经结束,应该重新设计新的数据库应用系统了

小结

  • 数据库各级模式的形成
    • 需求分析阶段:综合各个用户的应用需求(现实世界的需求)
    • 概念设计阶段:概念模式(信息世界模型),用E-R图来描述
    • 逻辑设计阶段:逻辑模式、外模式
    • 物理设计阶段:内模式
  • 在逻辑设计阶段将E-R图转换成具体的数据库产品支持的数据模型如关系模型,形成数据库逻辑模式
  • 然后根据用户处理的要求,安全性的考虑,在基本表的基础上再建立必要的视图,形成数据的外模式
  • 在物理设计阶段根据DBMS特点和处理的需要,进行物理存储安排,设计索引,形成数据库内模式

第八章 数据库编程

嵌入式SQL

  • SQL语言提供了两种不同的使用方式:
    • 交互式:在交互式环境中,用户可以直接与数据库进行交互,并实时执行SQL语句
    • 嵌入式:在嵌入式环境中,SQL语句被嵌入到编程语言中,与编程语言的代码混合在一起
  • 这两种方式细节上有差别,在程序设计的环境下,SQL语句要做某些必要的扩充,如防止SQL注入

嵌入式SQL的处理过程

  • 嵌入式SQL是将SQL语句嵌入程序设计语言中,如C、C++、Java,称为宿主语言,简称主语言

  • 对于嵌入式SQL,DBMS一般采用预编译方法,过程如下图

  • 为了区分SQL语句与主语言语句,所有SQL语句必须加前缀EXEC SQL,主语言为C语言时,语句格式:EXEC SQL <SQL语句>

嵌入式SQL语句与主语言之间的通信

  • 将SQL嵌入到高级语言中混合编程,程序中会含有两种不同计算模型的语句
    • SQL语句是描述性的面向集合的语句,负责操纵数据库
    • 高级语言语句是过程性的面向记录的语句,负责控制逻辑流程
  • 数据库工作单元与源程序工作单元之间的通信
    1. 向主语言传递SQL的执行状态信息,使主语言能够据此控制程序流程,主要用SQL通信区实现
    2. 主语言向SQL语句提供参数,主要用主变量实现
    3. 将SQL语句查询数据库的结果交主语言处理,主要用主变量和游标实现

SQL通信区

  • SQL语句执行后,系统反馈给应用程序信息,主要包括描述系统当前工作状态和描述运行环境
  • 这些信息将送到SQL通信区中,应用程序从SQL通信区中取出这些状态信息,据此决定接下来执行的语句
  • SQLCA(SQL通信区):SQL Communication Area,SQLCA是一个数据结构
  • SQL通信区在应用程序中用 EXEC SQL INCLUDE SQLCA 加以定义
  • SQLCA中有一个存放每次执行SQL语句后返回代码的变量SQLCODE,应用程序每执行完一条SQL语句之后都应该测试一下SQLCODE的值,以了解该SQL语句执行情况并做相应处理
  • 如果SQLCODE等于预定义的常量SUCCESS,则表示SQL语句成功,否则表示出错

主变量

  • 嵌入式SQL语句中可以使用主语言的程序变量来输入或输出数据

  • 在SQL语句中使用的主语言程序变量简称为主变量(Host Variable)

  • 主变量的类型

    • 输入主变量:由应用程序对其赋值,SQL语句引用
    • 输出主变量:由SQL语句对其赋值或设置状态信息,返回给应用程序
  • 指示变量

    • 指示变量是一个整型变量,用来“指示”所指主变量的值或条件
    • 一个主变量可以附带一个指示变量(Indicator Variable)
    • 指示变量的用途
      • 指示输入主变量是否为空值
      • 检测输出变量是否为空值,值是否被截断
  • 在SQL语句中主变量和指示变量格式如下,位于BEGIN DECLARE SECTION与END DECLARE SECTION之间

    BEGIN DECLARE SECTION
    	...  		(说明主变量和指示变量)
    END DECLARE SECTION
    
  • 说明之后的主变量可以在SQL语句中任何一个能够使用表达式的地方出现

  • 为了与数据库对象名(表名、视图名、列名等)区别,SQL语句中的主变量名前要加冒号(:)作为标志

  • 指示变量前也必须加冒号标志,并且必须紧跟在所指主变量之后

  • 在SQL语句之外(主语言语句中)使用主变量和指示变量可以直接引用,不必加冒号

游标

  • SQL语言与主语言具有不同数据处理方式:SQL语言是面向集合的,一条SQL语句原则上可以产生或处理多条记录;主语言是面向记录的,一组主变量一次只能存放一条记录
  • 仅使用主变量并不能完全满足SQL语句向应用程序输出数据的要求,所以嵌入式SQL引入了游标的概念,用来协调这两种不同的处理方式
  • 游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字
  • 用户可以用SQL语句逐一从游标中获取记录,并赋给主变量,交由主语言进一步处理

建立和关闭数据库连接

  1. 建立数据库连接

    • 建立连接的嵌入式SQL语句是

      EXEC SQL CONNECT TO target[AS connection-name][USER user-name];
      
    • target是要连接的数据库服务器,它可以是常见的服务器标识串,如 <dbname>@<hostname>:<port>,也可以是包含服务器标识的SQL串常量DEFAULT

    • connect-name是可选的连接名,连接名必须是一个有效的标识符,主要用来识别一个程序内同时建立的多个连接,如果在整个程序内只有一个连接,也可以不指定连接名

    • 程序运行过程中可以修改当前连接

      EXEC SQL SET CONNECTION connection-name|DEFAULT;
      
  2. 关闭数据库连接

    EXEC SQL DISCONNECT [connection];
    

    connection是EXEC SQL CONNECT所建立的数据库连接

程序实例

  • 例1:依次检查某个系的学生记录,交互式更新某些学生年龄

    EXEC SQL BEGIN DECLARE SECTION; /*主变量说明开始*/
        char Deptname[20];
        char Hsno[9];
        char Hsname[20];
        char Hssex[2];
        int HSage;
        int NEWAGE;
    EXEC SQL END DECLARE SECTION; /*主变量说明结束*/
    long SQLCODE;
    EXEC SQL INCLUDE SQLCA; /*定义SQL通信区*/
    int main(void)          /*C语言主程序开始*/
    {
        int count = 0;
        char yn; /*变量yn代表yes或no*/
        printf("Please choose the department name(CS/MA/IS):");
        scanf("%s", deptname); /*为主变量deptname赋值*/
        /*连接数据库TEST*/
        EXEC SQL CONNECT TO TEST@localhost:54321 USER "SYSTEM"/"MANAGER";
        EXEC SQL DECLARE SX CURSOR FOR /*定义游标SX*/
            SELECT Sno, Sname, Ssex, Sage /*SX对应的语句*/
            FROM Student
            WHERE SDept = :deptname;
        EXEC SQL OPEN SX; /*打开游标SX,指向查询结果的第一行*/
        for (;;)          /*用循环结构逐条处理结果集中的记录*/
        {
            /*推进游标,将当前数据放入主变量*/
            EXEC SQL FETCH SX INTO :HSno, :Hsname, :HSsex, :HSage;
            if (SQLCA.SQLCODE != 0) /*SQLCODE != 0,表示操作不成功*/
                break;              /*利用SQLCA中的状态信息决定何时退出循环*/
            if (count++ == 0)       /*如果是第一行的话,先打出行头*/
                printf("\n%-10s %-20s %-10s %-10s\n", "Sno", " Sname", "Ssex", "Sage");
            /*打印查询结果*/
            printf("%-10s %-20s %-10s %-10d\n", HSno, Hsname, Hssex, HSage);
            printf("UPDATE AGE(y/n)?"); /*询问用户是否要更新该学生的年龄*/
            do
            {
                scanf("%c", &yn);
            } while (yn != 'N' && yn != 'n' && yn != 'Y' && yn != 'y');
            if (yn == 'y' || yn == 'Y') /*如果选择更新操作*/
            {
                printf("INPUT NEW AGE:");
                scanf("%d", &NEWAGE);   /*用户输入新年龄到主变量中*/
                EXEC SQL UPDATE Student /*嵌入式SQL更新语句*/
                         SET Sage = :NEWAGE
                         WHERE CURRENT OF SX;
            } /*对当前游标指向的学生年龄进行更新*/
        }
        EXEC SQL CLOSE SX;        /*关闭游标SX,不再和查询结果对应*/
        EXEC SQL COMMIT WORK;     /*提交更新*/
        EXEC SQL DISCONNECT TEST; /*断开数据库连接*/
    }
    

不用游标的SQL语句

  • 不用游标的SQL语句的种类:说明性语句,数据定义语句,数据控制语句,查询结果为单记录的SELECT语句,非CURRENT形式的增删改语句

查询结果为单记录的SELECT语句

  • 这类语句不需要使用游标,只需用INTO子句指定存放查询结果的主变量

  • 例2:根据学生号码查询学生信息

    /*把要查询的学生的学号赋给为了主变量givensno*/
    EXEC SQL SELECT Sno, Sname, Ssex, Sage, Sdept
             INTO :Hsno, :Hname, :Hsex, :Hage, :Hdept
             FROM Student WHERE Sno = :givensno;
    
  • INTO子句、WHERE子句和HAVING短语的条件表达式中均可以使用主变量

  • 查询返回的记录中,可能某些列为空值NULL。为了表示空值,在INTO子句的主变量后面跟有指示变量,当查询得出的某个数据项为空值时,系统会自动将相应主变量后面的指示变量置为负值,而不再向该主变量赋值。

  • 如果查询结果实际上并不是单条记录,而是多条记录,则程序出错,关系数据库管理系统会在SQLCA中返回错误信息

  • 例3:查询某个学生选修某门课程的成绩。假设已经把将要查询的学生的学号赋给了主变givensno,将课程号赋给了主变量givencno

    EXEC SQL SELECT Sno,Cno,Grade
         	 INTO :Hsno, :Hcno, :Hgrade :Gradeid  /*指示变量Gradeid*/
    	     FROM  SC
         	 WHERE Sno=:givensno AND Cno=:givencno;
    
  • 如果Gradeid<0,不论Hgrade为何值,均认为该学生成绩为空值

非CURRENT形式的增删改语句

  • 在UPDATE的SET子句和WHERE子句中可以使用主变量,SET子句还可以使用指示变量

  • 例4:修改某个学生选修1号课程的成绩

    EXEC SQL UPDATE SC
             SET Grade=:newgrade    /*修改的成绩已赋给主变量:newgrade*/
             WHERE Sno=:givensno;   /*学号赋给主变量:givensno*/
    
  • 例5:某个学生新选修了某门课程,将有关记录插入SC表中。假设插入的学号已赋给主变量stdno,课程号已赋给主变量couno

    gradeid=-1;                                  /*gradeid为指示变量,赋为负值*/
    EXEC SQL INSERT INTO SC(Sno,Cno,Grade)
             VALUES(:stdno, :couno, :gr :gradeid);  /*:stdno,:couno,:gr为主变量*/
    

    由于该学生刚选修课程,成绩应为空,所以要把指示变量赋为负值,插入后该学生成绩为NULL

使用游标的SQL语句

  • 必须使用游标的SQL语句:查询结果为多条记录的SELECT语句、CURRENT形式的UPDATE语句、CURRENT形式的DELETE语句

查询结果为多条记录的SELECT语句

  • 使用游标的步骤:说明游标、打开游标、推进游标指针并取当前记录 、关闭游标

    1. 说明游标

      使用DECLARE语句,语句格式为

      EXEC SQL DECLARE <游标名> CURSOR FOR <SELECT语句>;
      

      游标是一条说明性语句,这时关系数据库管理系统并不执行SELECT语句

    2. 打开游标

      使用OPEN语句,语句格式为

      EXEC SQL OPEN <游标名>;
      

      打开游标实际上是执行相应的SELECT语句,把查询结果取到缓冲区中。这时游标处于活动状态,指针指向查询结果集中的第一条记录

    3. 推进游标指针并取当前记录

      使用FETCH语句,语句格式为

      EXEC SQL FETCH <游标名> 
               INTO <主变量>[<指示变量>][,<主变量>[<指示变量>]]...;
      

      其中主变量必须与SELECT语句中的目标列表达式具有一一对应关系,其作用是按指定方向推动游标指针,同时将缓冲区中的当前记录取出,送至主变量供主语言进一步处理

    4. 关闭游标

      使用CLOSE语句,语句格式为

      EXEC SQL CLOSE <游标名>;
      

      关闭游标,释放结果集占用的缓冲区及其他资源。游标被关闭后,就不再和原来的查询结果集相联系;被关闭的游标可以再次被打开,与新的查询结果相联系

CURRENT形式的UPDATE语句和DELETE语句

  • CURRENT形式的UPDATE语句和DELETE语句的用途

    • 非CURRENT形式的UPDATE语句和DELETE语句是面向集合的操作,一次修改或删除所有满足条件的记录

    • 如果只想修改或删除其中某个记录,就需要用带游标的SELECT语句查出所有满足条件的记录,从中进一步找出要修改或删除的记录,然后用CURRENT形式的UPDATE语句和DELETE语句修改或删除之

    • UPDATE语句和DELETE语句中要用子句

      WHERE CURRENT OF <游标名> 
      

      表示修改或删除的是最近一次取出的记录,即游标指针指向的记录

  • 不能使用CURRENT形式的UPDATE语句和DELETE语句

    • 当游标定义中的SELECT语句带有UNION或ORDER BY子句,或者该SELECT语句相当于定义了一个不可更新的视图

动态SQL

  • 静态嵌入式SQL:静态嵌入式SQL语句能够满足一般要求,无法满足要到执行时才能够确定要提交的SQL语句、查询的条件
  • 动态嵌入式SQL:允许在程序运行过程中临时“组装”SQL语句,支持动态组装SQL语句和动态参数两种形式

使用SQL语句主变量

  • 程序主变量包含的内容是SQL语句的内容,而不是原来保存数据的输入或输出变量

  • SQL语句主变量在程序执行期间可以设定不同的SQL语句,然后立即执行

  • 例6:创建基本表TEST

    EXEC SQL BEGIN DECLARE SECTION;
    	/*SQL语句主变量,内容是创建表的SQL语句*/
    	const char *stmt="CREATE TABLE test(a int);";
    EXEC SQL END DECLARE SECTION;
        	... 
    EXEC SQL EXECUTE IMMEDIATE :stmt;  /*执行动态SQL语句*/
    

动态参数

  • 动态参数是SQL语句中的可变元素,使用参数符号(?)表示该位置的数据在运行时设定

  • 和主变量的区别:动态参数的输入不是编译时完成绑定,而是通过 PREPARE语句 准备主变量和 执行语句EXECUTE 绑定数据或主变量来完成

  • 使用动态参数的步骤

    1. 声明SQL语句主变量:声明一个SQL语句主变量,在变量中可以使用?来表示动态参数的位置

    2. 准备SQL语句(PREPARE)

      EXEC SQL PREPARE <语句名> FROM <SQL语句主变量>;
      
  • 例如,先声明了一个SQL语句主变量,该语句用于从名为"table"的表中选择所有满足条件的行,其中条件是指定的列等于动态参数

    char *sqlStatement = "SELECT * FROM table WHERE column = ?";
    

    现在我们已经准备好了SQL语句,可以根据需要执行它

    EXEC SQL PREPARE stmt FROM :sqlStatement;
    

    stmt是给这个准备好的语句起的名字,:sqlStatement是SQL语句主变量

    在执行时,我们需要将动态参数的实际值绑定到预先准备的语句中,请继续阅读下面的内容

执行准备好的语句(EXECUTE)

  • EXECUTE将SQL语句中分析出的动态参数和主变量或数据常量绑定,作为语句的输入或输出变量

    EXEC SQL EXECUTE <语句名> [INTO <主变量表>] [USING <主变量或常量>];
    
  • 例7:向TEST中插入元组

    EXEC SQL BEGIN DECLARE SECTION;
    	/* 声明SQL主变量内容是INSERT语句 */
        const char *stmt = "INSERT INTO test VALUES(?);";
    EXEC SQL END DECLARE SECTION;
    ... 
    EXEC SQL PREPARE mystmt FROM :stmt; /* 准备语句 */
    ... 
    EXEC SQL EXECUTE mystmt USING 100; /* 执行语句,设定INSERT语句插入值100 */
    EXEC SQL EXECUTE mystmt USING 200; /* 执行语句,设定INSERT语句插入值200 */
    

过程化SQL

过程化SQL的块结构

  • 过程化SQL是对SQL的扩展,增加了过程化语句功能

  • 过程化SQL基本结构是块,块之间可以互相嵌套,每个块完成一个逻辑操作

  • 过程化SQL块的基本结构

变量和常量的定义

  1. 变量定义

    变量名 数据类型 [[NOT NULL]:=初值表达式]或

    变量名 数据类型 [[NOT NULL] 初值表达式]

  2. 常量定义

    常量名 数据类型 CONSTANT:=常量表达式

    常量必须要给一个值,并且该值在存在期间或常量的作用域内不能改变。如果试图修改它,过程化SQL将返回一个异常

  3. 赋值语句

    变量名称:=表达式

流程控制

  1. 条件控制语句

    • 一般有三种形式的F语句:IF-THEN,IF-THEN-ELSE和嵌套的IF语句

      1. IF-THEN

        IF condition THEN
            Sequence_of_statements; /*条件为真时语句序列才被执行*/
        END IF;                     /*条件为假或NULL时什么也不做,控制转移至下一个语句*/
        
      2. IF-THEN-ELSE

        IF condition THEN
            Sequence_of_statements1;  /*条件为真时执行语句序列1*/
        ELSE
            Sequence_of_statements2;  /*条件为假或NULL 时执行语句序列2*/
        END IF;
        
      3. 嵌套的IF语句

        在THEN和ELSE子句中还可以再包含IF语句,即IF语句可以嵌套

  2. 循环控制语句

    • 过程化SQL有三种循环结构:LOOP,WHILE-LOOP和FOR-LOOP
    1. 简单的循环语句LOOP

      LOOP
         Sequence_of_statements;  /*循环体,一组过程化SQL语句*/
      END LOOP; 
      

      多数数据库服务器的过程化SQL都提供EXIT、BREAK或LEAVE等循环结束语句,保证LOOP语句块能够结束

    2. WHILE-LOOP

      WHILE condition LOOP
          Sequence_of_statements;
      END LOOP;
      

      每次执行循环体语句之前,首先对条件进行求值,如果条件为真,则执行循环体内的语句序列,如果条件为假,则跳过循环并把控制传递给下一个语句

    3. FOR-LOOP

      FOR count IN [REVERSE] bound1...bound2 LOOP
          Sequence_of_statements;
      END LOOP;
      
      1. 设置计数器:循环开始时,将count设置为起始点bound1
      2. 检查边界条件:循环会检查计数器count是否小于(或大于,如果使用REVERSE关键字)结束点bound2。如果不满足此条件,循环会跳出,执行FOR循环之后的代码
      3. 执行循环体:如果计数器count满足边界条件,将执行放在Sequence_of_statements中指定的一组语句。这些语句可以是任何需要在每次循环迭代时执行的操作
      4. 更新计数器:在执行完循环体中的语句之后,计数器count会根据步长进行更新。如果没有指定REVERSE关键字,计数器将递增(加1)。如果指定了REVERSE关键字,计数器将递减(减1)
  3. 错误处理

    • 如果过程化SQL在执行时出现异常,则应该让程序在产生异常的语句处停下来,根据异常的类型去执行异常处理语句
    • SQL标准对数据库服务器提供什么样的异常处理做出了建议,要求过程化SQL管理器提供完善的异常处理机制

存储过程和函数

存储过程

  • 过程化SQL块类型
    • 命名块:编译后保存在数据库中,可以被反复调用,运行速度较快,过程和函数是命名块
    • 匿名块:每次执行时都要编译,它不能被存储到数据库中,也不能在其他过程化SQL块中调用
  • 存储过程:由过程化SQL语句书写的过程,经编译和优化后存储在数据库服务器中,使用时只要调用即可

存储过程的优点

  1. 运行效率高
  2. 降低了客户机和服务器之间的通信量
  3. 方便实施企业规则

存储过程的用户接口

  1. 创建存储过程

    CREATE OR REPLACE PROCEDURE 过程名([参数1,参数2,...])  /*产存储过程首部*/
    AS <过程化SQL块>;                       /*存储过程体,描述该存储过程的操作*/
    

    过程名:数据库服务器合法的对象标识

    参数列表:用名字来标识调用时给出的参数值,必须指定值的数据类型。参数也可以定义输入参数、输出参数或输入/输出参数,默认为输入参数

    过程体:是一个<过程化SQL块>,包括声明部分和可执行语句部分

    • 例8:利用存储过程来实现下面的应用:从账户1转指定数额的款项到账户2中

      /*定义存储过程TRANSFER,其参数为转入账户、转出账户、转账额度*/
      CREATE OR REPLACE PROCEDURE TRANSFER(inAccount INT, outAccount INT, mount FLOAT)
      AS
      DECLARE		/*定义变量*/
      	totalDepositOut Float;
      	totalDepositIn Float;
      	inAccountnum INT;
      BEGIN           /*检查转出账户的余额 */	                       
      	SELECT Total INTO totalDepositOut FROM Accout 
      	WHERE accountnum=outAccount;
      	IF totalDepositOut IS NULL THEN     /*如果转出账户不存在或账户中没有存款*/
      		ROLLBACK; 	   /*回滚事务*/
      		RETURN;
          END IF;
          IF totalDepositOut < amount THEN    	/*如果账户存款不足*/
              ROLLBACK;      /*回滚事务*/
              RETURN;
          END IF;
          SELECT Accountnum INTO inAccountnum FROM Account
          WHERE accountnum = inAccount;
          IF inAccount IS NULL THEN  		 /*如果转入账户不存在*/                        
              ROLLBACK; 	  /*回滚事务*/
              RETURN;
          ENDIF;
          /* 修改转出账户余额,减去转出额 */
          UPDATE Account SET total = total - amount WHERE accountnum=outAccount;
          /* 修改转入账户余额,增加转入额 */
          UPDATE Account SET total = total + amount WHERE accountnum=inAccount; 
          COMMIT;           /* 提交转账事务 */
      END;
      
  2. 执行存储过程

    CALL/PERFORM PROCEDURE 过程名([参数1,参数2,...]);
    
    • 使用CALL或者PERFORM等方式激活存储过程的执行

    • 在过程化SQL中,数据库服务器支持在过程体中调用其他存储过程

    • 例9:从账户01003815868转10000元到01003813828账户中。

      CALL PROCEDURE TRANSFER(01003813828,01003815868,10000);
      
  3. 修改存储过程

    ALTER PROCEDURE 过程名1 RENAME TO 过程名2;
    
  4. 删除存储过程

    DROP PROCEDURE 过程名();
    

函数

  • 函数和存储过程的异同
    • 同:都是持久性存储模块
    • 异:函数必须指定返回的类型
  1. 函数的定义语句格式

    CREATE OR REPLACE FUNCTION 函数名 ([参数1,参数2,…]) RETURNS <类型> AS <过程化SQL块>;
    
  2. 函数的执行语句格式

    CALL/SELECT 函数名 ([参数1,参数2,…]);
    
  3. 修改函数

    • 重命名

      ALTER FUNCTION 过程名1 RENAME TO 过程名2;
      
    • 重新编译

      ALTER FUNCTION 过程名 COMPILE;
      
  • 举例:计算传入参数的平均值

    CREATE OR REPLACE FUNCTION calculate_average(num1 INT, num2 INT)
    RETURNS FLOAT
    AS
    DECLARE
        avg FLOAT;
    BEGIN
        avg = (num1 + num2) / 2.0;
        RETURN avg;
    END;
    

ODBC编程

  • ODBC优点:移植性好、能同时访问不同的数据库、共享多个数据资源

ODBC概述

  • ODBC产生的原因
    • 由于不同的数据库管理系统的存在,在某个关系数据库管理系统下编写的应用程序就不能在另一个关系数据库管理系统下运行
    • 许多应用程序需要共享多个部门的数据资源,访问不同的关系数据库管理系统
  • ODBC是微软公司开放服务体系(Windows Open Services Architecture,WOSA)中有关数据库的一个组成部分,提供了一组访问数据库的应用程序编程接口(Application Programming Interface,API)
  • ODBC约束力:规范应用开发规范关系数据库管理系统应用接口

ODBC工作原理概述

  • ODBC应用系统的体系结构

    1. 用户应用程序
    2. ODBC驱动程序管理器
    3. 数据库驱动程序
    4. 数据源

用户应用程序

  • ODBC应用程序包括的内容
    • 请求连接数据库
    • 向数据源发送SQL语句
    • 为SQL语句执行结果分配存储空间,定义所读取的数据格式
    • 获取数据库操作结果或处理错误
    • 进行数据处理并向用户提交处理结果
    • 请求事务的提交和回滚操作
    • 断开与数据源的连接

ODBC驱动程序管理器

  • 驱动程序管理器:用来管理各种驱动程序
    • 包含在ODBC32.DLL中
    • 管理应用程序和驱动程序之间的通信
    • 建立、配置或删除数据源,并查看系统当前所安装的数据库ODBC驱动程序
  • 主要功能:装载ODBC驱动程序、选择和连接正确的驱动程序、管理数据源、检查ODBC调用参数的合法性、记录ODBC函数的调用等

数据库驱动程序

  • ODBC通过驱动程序来提供应用系统与数据库平台的独立性
  • ODBC应用程序不能直接存取数据库
    • 其各种操作请求由驱动程序管理器提交给某个关系数据库管理系统的ODBC驱动程序
    • 通过调用驱动程序所支持的函数来存取数据库
    • 数据库的操作结果也通过驱动程序返回给应用程序
    • 如果应用程序要操纵不同的数据库,就要动态地链接到不同的驱动程序上
  • ODBC驱动程序类型
    • 单束
      • 数据源和应用程序在同一台机器上
      • 驱动程序直接完成对数据文件的I/O操作
      • 驱动程序相当于数据管理器
    • 多束
      • 支持客户机—服务器、客户机—应用服务器/数据库服务器等网络环境下的数据访问
      • 由驱动程序完成数据库访问请求的提交和结果集接收
      • 应用程序使用驱动程序提供的结果集管理接口操纵执行后的结果数据

ODBC数据源管理

  • 数据源是最终用户需要访问的数据,包含了数据库位置和数据库类型等信息,是一种数据连接的抽象
  • 数据源对最终用户是透明的
    • ODBC给每个被访问的数据源指定唯一的数据源名(Data Source Name,简称DSN),并映射到所有必要的、用来存取数据的低层软件
    • 在连接中,用数据源名来代表用户名、服务器名、所连接的数据库名等
    • 最终用户无须知道数据库管理系统或其他数据管理软件、网络以及有关ODBC驱动程序的细节
  • 例如,假设某个学校在SQL Server和KingbaseES上创建了两个数据库:学校人事数据库和教学科研数据库
    • 学校的信息系统要从这两个数据库中存取数据
    • 为了方便地与两个数据库连接,为学校人事数据库创建一个数据源名PERSON,为教学科研数据库创建一个名为EDU的数据源
    • 当要访问每一个数据库时,只要与PERSON和EDU连接即可,不需要记住使用的驱动程序、服务器名称、数据库名

ODBC API基础

  • ODBC应用程序编程接口的一致性
    • API一致性
      • 核心级:提供基本的API函数,用于建立连接、执行SQL语句、获取结果等基本数据库操作
      • 扩展1级:提供了一些额外功能,例如事务管理、元数据查询等
      • 扩展2级:提供了更多高级功能,如游标管理、存储过程调用
    • 语法一致性
      • 最低限度SQL语法级:提供了SQL语句的最基本的语法支持,包括SELECT、INSERT、UPDATE和DELETE等基本操作
      • 核心SQL语法级:提供了更广泛的SQL语法支持,包括连接(JOIN)、聚合函数(AVG、SUM等)和子查询等更高级的功能
      • 扩展SQL语法级:提供了更多的SQL语法扩展,如存储过程、触发器、游标等

函数概述

  • ODBC 3.0标准提供了76个函数接口,大致分为:
    • 分配和释放环境句柄、连接句柄、语句句柄
    • 连接函数(SQLDriverconnect等)
    • 与信息相关的函数(SQLGetinfo、SQLGetFuction等)
    • 事务处理函数(如SQLEndTran)
    • 执行相关函数(SQLExecdirect、SQLExecute等)
    • 编目函数,ODBC 3.0提供了11个编目函数,如SQLTables、SQLColumn等。应用程序可以通过对编目函数的调用来获取数据字典的信息,如权限、表结构等

句柄及其属性

句柄(Handle)是指一个特定类型的标识符或引用,用于表示和操作某种资源或对象

  • 句柄是32位整数值,代表一个指针。ODBC 3.0中句柄分为环境句柄、连接句柄、语句句柄、描述符句柄

  • 应用程序句柄之间的关系

    • 每个ODBC应用程序需要建立一个ODBC环境,分配一个环境句柄,存取数据的全局性背景,如环境状态、当前环境状态诊断、当前在环境上分配的连接句柄等
    • 一个环境句柄可以建立多个连接句柄,每一个连接句柄实现与一个数据源之间的连接
    • 在一个连接中可以建立多个语句句柄,它不只是一个SQL语句,还包括SQL语句产生的结果集以及相关的信息等
    • 在ODBC 3.0中又提出了描述符句柄的概念,它是描述SQL语句的参数、结果集列的元数据集合
  • ODBC数据类型

    • SQL数据类型:用于数据源
    • C数据类型 :用于应用程序的C代码
  • 应用程序可以通过SQLGetTypeInfo来获取不同的驱动程序对于数据类型的支持情况

  • SQL数据类型和C数据类型之间的转换规则

ODBC的工作流程

  • ODBC的工作流程

  • 例11:将KingbaseES数据库中Student表的数据备份到SQL Server数据库中

    • 该应用涉及两个不同的关系数据库管理系统中的数据源
    • 使用ODBC来开发应用程序,只要改变应用程序中连接函数(SQLConnect)的参数,就可以连接不同关系数据库管理系统的驱动程序,连接两个数据源
    • 在应用程序运行前,已经在KingbaseES和SQL Server中分别建立了Student关系表
    • 应用程序要执行的操作
      • 在KingbaseES上执行 SELECT * FROM Student;
      • 把获取的结果集,通过多次执行INSERT语句插入到SQL Server的Student表中
  1. 配置数据源

    • 配置数据源有两种方法

      1. 运行数据源管理工具来进行配置
      2. 使用Driver Manager提供的ConfigDsn函数来增加、修改或删除数据源
    • 例12:采用第一种方法创建数据源。因为要同时用到KingbaseES和SQL Server,所以分别建立两个数据源,将其取名为KingbaseES ODBC和SQL Server

      #include <stdlib.h>
      #include <stdio.h>
      #include <windows.h>
      #include <sql.h>
      #include <sqlext.h>
      #include <Sqltypes.h>
      #define SNO_LEN 30
      #define NAME_LEN 50
      #define DEPART_LEN 100
      #define SSEX_LEN 5
      int main()
      {
          /* Step 1 定义句柄和变量 */
          /*以king开头的表示的是连接KingbaseES的变量*/
          /*以server开头的表示的是连接SQLServer的变量*/
          SQLHENV kinghenv, serverhenv;    /*环境句柄*/
          SQLHDBC kinghdbc, serverhdbc;    /*连接句柄*/
          SQLHSTMT kinghstmt, serverhstmt; /*语句句柄*/
          SQLRETURN ret;
          SQLCHAR sName[NAME_LEN], sDepart[DEPART_LEN],
                  sSex[SSEX_LEN], sSno[SNO_LEN];
          SQLINTEGER sAge;
          SQLINTEGER cbAge = 0, cbSno = SQL_NTS, cbSex = SQL_NTS,
                     cbName = SQL_NTS, cbDepart = SQL_NTS;
          /* Step 2 初始化环境 */
          /* 给kinghenv和serverhenv分配内存空间 */
          /* 参数1表示分配的句柄类型,参数2是一个输入句柄,通常是环境句柄,参数3用于接收 */
          ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &kinghenv);
          ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &serverhenv);
          /* 设置环境属性,这里将ODBC版本设置为SQL_OV_ODBC3,满足ODBC 3.0的要求 */
          ret = SQLSetEnvAttr(kinghenv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
          ret = SQLSetEnvAttr(serverhenv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
          /* Step 3 建立连接 */
          /* 将连接句柄分配给kinghdbc和serverhdbc*/
          ret = SQLAllocHandle(SQL_HANDLE_DBC, kinghenv, &kinghdbc);
          ret = SQLAllocHandle(SQL_HANDLE_DBC, serverhenv, &serverhdbc);
          /* 连接KingbaseES和SQLServer数据库 */
          /* 指定了数据源名称为"KingbaseES ODBC",用户名为"SYSTEM",密码为"MANAGER" 
          *  参数SQL_NTS代表空终止字符串,ODBC驱动程序可以自动确定字符串的长度,直到遇到空字符为止
          */
          ret = SQLConnect(kinghdbc, "KingbaseES ODBC", SQL_NTS, "SYSTEM", SQL_NTS, "MANAGER", SQL_NTS);
          if (!SQL_SUCCEEDED(ret)) /*连接失败时返回错误值*/
              return -1;
          /* 指定了数据源名称为"SQLServer",用户名和密码都为"sa" */
          ret = SQLConnect(serverhdbc, "SQLServer", SQL_NTS, "sa", SQL_NTS, "sa", SQL_NTS);
          if (!SQL_SUCCEEDED(ret)) /*连接失败时返回错误值*/
              return -1;
          /* Step 4 初始化语句句柄 */
          /* 分配了一个语句句柄用于KingbaseES数据库,并将其赋值给kinghstmt */
          ret = SQLAllocHandle(SQL_HANDLE_STMT, kinghdbc, &kinghstmt);
          /* 设置语句句柄的属性,将SQL_ATTR_ROW_BIND_TYPE属性设置为SQL_BIND_BY_COLUMN,以指定每列的绑定方式 */
          ret = SQLSetStmtAttr(kinghstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)SQL_BIND_BY_COLUMN, SQL_IS_INTEGER);
          ret = SQLAllocHandle(SQL_HANDLE_STMT, serverhdbc, &serverhstmt);
          /* Step 5 两种方式执行语句 */
          /* 预编译带有参数的语句 */
          ret = SQLPrepare(serverhstmt, "INSERT INTO STUDENT(SNO,SNAME,SSEX, SAGE,SDEPT) VALUES (?, ?, ?, ?, ?)", SQL_NTS);
          if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
          {
              ret = SQLBindParameter(serverhstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, SNO_LEN, 0, sSno, 0, &cbSno);
              ret = SQLBindParameter(serverhstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, NAME_LEN, 0, sName, 0, &cbName);
              ret = SQLBindParameter(serverhstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 2, 0, sSex, 0, &cbSex);
              ret = SQLBindParameter(serverhstmt, 4, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &sAge, 0, &cbAge);
              ret = SQLBindParameter(serverhstmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, DEPART_LEN, 0, sDepart, 0, &cbDepart);
          }
          /*执行SQL语句*/
          ret = SQLExecDirect(kinghstmt, "SELECT * FROM STUDENT", SQL_NTS);
          if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
          {
              ret = SQLBindCol(kinghstmt, 1, SQL_C_CHAR, sSno, SNO_LEN, &cbSno);
              ret = SQLBindCol(kinghstmt, 2, SQL_C_CHAR, sName, NAME_LEN, &cbName);
              ret = SQLBindCol(kinghstmt, 3, SQL_C_CHAR, sSex, SSEX_LEN, &cbSex);
              ret = SQLBindCol(kinghstmt, 4, SQL_C_LONG, &sAge, 0, &cbAge);
              ret = SQLBindCol(kinghstmt, 5, SQL_C_CHAR, sDepart, DEPART_LEN, &cbDepart);
          }
          /* Step 6 处理结果集并执行预编译后的语句*/
          /* SQLFetch(kinghstmt)函数从结果集中获取一行数据 */
          while ((ret = SQLFetch(kinghstmt)) != SQL_NO_DATA_FOUND)
          {
              if (ret == SQL_ERROR)
                  printf("Fetch error\n");
              else
                  /* 将从KingbaseES数据库获取的查询的一行结果插入到SQLServer数据库中 */
                  ret = SQLExecute(serverhstmt);
          }
          /* Step 7 中止处理*/
          SQLFreeHandle(SQL_HANDLE_STMT, kinghstmt);
          SQLDisconnect(kinghdbc);
          SQLFreeHandle(SQL_HANDLE_DBC, kinghdbc);
          SQLFreeHandle(SQL_HANDLE_ENV, kinghenv);
          SQLFreeHandle(SQL_HANDLE_STMT, serverhstmt);
          SQLDisconnect(serverhdbc);
          SQLFreeHandle(SQL_HANDLE_DBC, serverhdbc);
          SQLFreeHandle(SQL_HANDLE_ENV, serverhenv);
          return 0;
      }
      
  2. 初始化环境

    • 没有和具体的驱动程序相关联,由Driver Manager来进行控制,并配置环境属性
    • 应用程序通过调用连接函数和某个数据源进行连接后,Driver Manager才调用所连的驱动程序中的SQLAllocHandle,来真正分配环境句柄的数据结构
  3. 建立连接

    • 应用程序调用SQLAllocHandle分配连接句柄,通过SQLConnect、SQLDriverConnect或SQLBrowseConnect与数据源连接
    • SQLConnect连接函数的输入参数为:配置好的数据源名称、用户ID、口令
    • 上例中KingbaseES ODBC为数据源名字,SYSTEM为用户名,MANAGER为用户密码
  4. 分配语句句柄

    • 处理任何SQL语句之前,应用程序还需要首先分配一个语句句柄
    • 语句句柄含有具体的SQL语句以及输出的结果集等信息
    • 应用程序还可以通过SQLtStmtAttr来设置语句属性(也可以使用默认值)
  5. 执行SQL语句

    • 应用程序处理SQL语句的两种方式
      • 预处理(SQLPrepare、SQLExecute适用于语句的多次执行)
      • 直接执行(SQLExecdirect)
    • 如果SQL语句含有参数,应用程序为每个参数调用SQLBindParameter,并把它们绑定至应用程序变量
    • 应用程序可以直接通过改变应用程序缓冲区的内容从而在程序中动态改变SQL语句的具体执行
    • 应用程序根据语句类型进行的处理
      • 有结果集的语句(select或是编目函数),则进行结果集处理
      • 没有结果集的函数,可以直接利用本语句句柄继续执行新的语句或是获取行计数(本次执行所影响的行数)之后继续执行
    • 在插入数据时,采用了预编译的方式,首先通过SQLPrepare来预处理SQL语句,然后将每一列绑定到用户缓冲区
  6. 结果集处理

    • 应用程序可以通过SQLNumResultCols来获取结果集中的列数
    • 通过SQL DescribeCol或是SQLColAttrbute函数来获取结果集每一列的名称、数据类型、精度和范围
    • ODBC中使用游标来处理结果集数据
    • ODBC中游标类型
      • Forward-only游标,是ODBC的默认游标类型
      • 可滚动(Scroll)游标
      • 静态(static)
      • 动态(dynamic)
      • 码集驱动(keyset-driven)
      • 混合型(mixed)
    • 结果集处理步骤
      • ODBC游标的打开方式不同于嵌入式SQL,不是显式声明而是系统自动产生一个游标,当结果集刚刚生成时,游标指向第一行数据之前
      • 应用程序通过SQLBindCol把查询结果绑定到应用程序缓冲区中,通过SQLFetch或是SQLFetchScroll来移动游标获取结果集中的每一行数据
      • 对于如图像这类特别的数据类型,当一个缓冲区不足以容纳所有的数据时,可以通过SQLGetdata分多次获取
      • 最后通过SQLClosecursor来关闭游标
  7. 中止处理

    • 应用程序中止步骤:释放语句句柄、释放数据库连接、与数据库服务器断开、释放ODBC环境

第九章 关系查询处理和查询优化

关系数据库系统的查询处理

查询处理步骤

  • 关系数据库管理系统查询处理阶段:查询分析、查询检查、查询优化、查询执行

  1. 查询分析
    • 查询分析的任务:对查询语句进行扫描、词法分析和语法分析
    • 词法分析:从查询语句中识别出正确的语言符号
    • 语法分析:进行语法检查
  2. 查询检查
    • 查询检查的任务:合法权检查、视图转换、安全性检查、完整性初步检查
    • 根据数据字典中有关的模式定义检查语句中的数据库对象,如关系名、属性名是否存在和有效
    • 如果是对视图的操作,则要用视图消解方法把对视图的操作转换成对基本表的操作
    • 根据数据字典中的用户权限和完整性约束定义对用户的存取权限进行检查
    • 检查通过后把SQL查询语句转换成内部表示,即等价的关系代数表达式
    • 关系数据库管理系统一般都用查询树,也称为语法分析树来表示扩展的关系代数表达式
  3. 查询优化
    • 查询优化:选择一个高效执行的查询处理策略
    • 查询优化分类
      • 代数优化/逻辑优化:指关系代数表达式的优化
      • 物理优化:指存取路径和底层操作算法的选择
    • 查询优化的选择依据:基于规则(rule based)、基于代价(cost based)、基于语义(semantic based)
  4. 查询执行
    • 依据优化器得到的执行策略生成查询执行计划,由代码生成器(code generator)生成执行查询计划的代码,然后加以执行,回送查询结果
    • 两种执行方法:自顶向下、自底向上

实现查询操作的算法示例

选择操作的实现

  • 选择操作典型实现方法
    1. 全表扫描方法(Table Scan):对查询的基本表顺序扫描,逐一检查每个元组是否满足选择条件,把满足条件的元组作为结果输出;适合小表,不适合大表
    2. 索引扫描方法(Index Scan):适合于选择条件中的属性上有索引(例如B+树索引或Hash索引),通过索引先找到满足条件的元组主码或元组指针,再通过元组指针直接在查询的基本表中找到元组
  • 例1:SELECT * FROM Student WHERE <条件表达式>;
    • 考虑<条件表达式>的几种情况:
      • C1:无条件
      • C2:Sno='201215121'
      • C3:Sage>20
      • C4:Sdept='CS' AND Sage>20
    • 全表扫描算法
      • 假设可以使用的内存为M块,全表扫描算法思想如下
        1. 按照物理次序读Student的M块到内存
        2. 检查内存的每个元组t,如果满足选择条件,则输出t
        3. 如果student还有其他块未被处理,重复1和2
    • 索引扫描算法
      • 例1-C2:SELECT * FROM Student WHERE Sno='201215121';
        • 假设Sno上有索引(或Sno是散列码)
        • 算法:使用索引(或散列)得到Sno为'201215121'元组的指针,通过元组指针在Student表中检索到该学生
      • 例1-C3:ELECT * FROM Student WHERE Sage>20;
        • 假设Sage上有B+树索引
        • 算法:使用B+树索引找到Sage=20的索引项,以此为入口点在B+树的顺序集上得到Sage>20的所有元组指针,通过这些元组指针到student表中检索到所有年龄大于20的学生
      • 例1-C4:SELECT * FROM Student WHERE Sdept='CS' AND Sage>20;
        • 假设Sdept和Sage上都有索引
        • 算法一:分别用Index Scan找到Sdept=’CS’的一组元组指针和Sage>20的另一组元组指针,求这两组指针的交集,到Student表中检索,得到计算机系年龄大于20的学生
        • 算法二:找到Sdept=’CS’的一组元组指针,通过这些元组指针到Student表中检索,并对得到的元组检查另一些选择条件(如Sage>20)是否满足,把满足条件的元组作为结果输出

连接操作的实现

  • 连接操作是查询处理中最耗时的操作之一,本节只讨论等值连接(或自然连接)最常用的实现算法

  • 例2:SELECT * FROM Student, SC WHERE Student.Sno=SC.Sno;

    1. 嵌套循环算法(nested loop join):对外层循环(Student表)的每一个元组(s),检索内层循环(SC表)中的每一个元组(sc)检查这两个元组在连接属性(Sno)上是否相等,如果满足连接条件,则串接后作为结果输出,直到外层循环表中的元组处理完为止

    2. 排序-合并算法(sort-merge join或merge join)

      1. 如果连接的表没有排好序,先对Student表和SC表按连接属性Sno排序
      2. 取Student表中第一个Sno,依次扫描SC表中具有相同Sno的元组
      3. 当扫描到Sno不相同的第一个SC元组时,返回Student表扫描它的下一个元组,再扫描SC表中具有相同Sno的元组,把它们连接起来
      • 重复上述步骤直到Student表扫描完。排序-合并连接方法示意图如下

      • Student表和SC表都只要扫描一遍,如果两个表原来无序,执行时间要加上对两个表的排序时间。对于大表,先排序后使用排序-合并连接算法执行连接,总的时间一般仍会减少

    3. 索引连接(index join)算法

      1. 在SC表上已经建立属性Sno的索引
      2. 对Student中每一个元组,由Sno值通过SC的索引查找相应的SC元组
      3. 把这些SC元组和Student元组连接起来
      4. 循环执行2、3,直到Student表中的元组处理完为止
    4. Hash Join算法(建议观看9.1节动画《连接操作的实现(3)--散列连接》)

      • 连接属性作为hash码,用同一个hash函数把Student表和SC表中的元组散列到hash表中
      • 划分阶段(building phase,也称为partitioning phase)
        • 对包含较少元组的表(如Student表)进行一遍处理
        • 把它的元组按hash函数分散到hash表的桶中
      • 试探阶段(probing phase,也称为连接阶段join phase)
        • 对另一个表(SC表)进行一遍处理
        • 把SC表的元组也按同一个hash函数(hash码是连接属性)进行散列
        • 把SC元组与桶中来自Student表并与之相匹配的元组连接起来
      • 上面hash join算法前提:假设两个表中较小的表在第一阶段后可以完全放入内存的hash桶中

关系数据库系统的查询优化

  • 查询优化在关系数据库系统中有着非常重要的地位
  • 关系查询优化是影响关系数据库管理系统性能的关键因素
  • 由于关系表达式的语义级别很高,使关系系统可以从关系表达式中分析查询语义,提供了执行查询优化的可能性

查询优化概述

  • 关系系统的查询优化是关系数据库管理系统实现的关键技术又是关系系统的优点所在,它减轻了用户选择存取路径的负担

  • 对于非关系系统有以下问题

    • 用户使用过程化的语言表达查询要求,执行何种记录级的操作,以及操作的序列是由用户来决定的
    • 用户必须了解存取路径,系统要提供用户选择存取路径的手段,查询效率由用户的存取策略决定
    • 如果用户做了不当的选择,系统是无法对此加以改进的
  • 查询优化的优点:用户不必考虑如何最好地表达查询以获得较好的效率,而且系统可以比用户程序的“优化”做得更好。原因是:

    1. 优化器可以从数据字典中获取许多统计信息,而用户程序则难以获得这些信息
    2. 如果数据库的物理统计信息改变了,系统可以自动对查询重新优化以选择相适应的执行计划。在非关系系统中必须重写程序,而重写程序在实际应用中往往是不太可能的
    3. 优化器可以考虑数百种不同的执行计划,程序员一般只能考虑有限的几种可能性
    4. 优化器中包括了很多复杂的优化技术,这些优化技术往往只有最好的程序员才能掌握。系统的自动优化相当于使得所有人都拥有这些优化技术
  • 关系数据库管理系统通过某种代价模型计算出各种查询执行策略的执行代价,然后选取代价最小的执行方案

    • 在集中式数据库中,查询执行开销主要包括:磁盘存取块数(I/O代价)、处理机时间(CPU代价)、查询的内存开销,其中I/O代价是最主要的

    • 在分布式数据库上还要加上通信代价,即

      总代价=I/O代价+CPU代价+内存代价+通信代价

  • 查询优化的总目标:选择有效的策略、求得给定关系表达式的值、使得查询代价最小(实际上是较小)

一个实例

  • 一个关系查询可以对应不同的执行方案,其效率可能相差非常大

  • 例3:求选修了2号课程的学生姓名。SQL表达为

    SELECT Student.Sname
    FROM Student, SC
    WHERE Student.Sno=SC.Sno AND SC.Cno='2';
    
  • 假定学生-课程数据库中有1000个学生记录,10000个选课记录,选修2号课程的选课记录为50个

  • 可以用多种等价的关系代数表达式来完成这一查询

    \[\begin{aligned} &Q_1=\Pi_{Sname}(\sigma_{Student.Sno=SC.Sno\wedge SC.Cno='2'}(Student\times SC))\\ &Q_2=\Pi_{Sname}(\sigma_{SC.Cno='2'}(Student\Join SC))\\ &Q_3=\Pi_{Sname}(Student\Join \sigma_{SC.Cno='2'}(SC))\\ \end{aligned} \]

  1. 第一种情况 \(Q_1=\Pi_{Sname}(\sigma_{Student.Sno=SC.Sno\wedge SC.Cno='2'}(Student\times SC))\)

    1. 计算广义笛卡尔积

      • 算法

        • 在内存中尽可能多地装入某个表(如Student表)的若干块,留出一块存放另一个表(如SC表)的元组

        • 把SC中的每个元组和Student中每个元组连接,连接后的元组装满一块后就写到中间文件上

        • 从SC中读入一块和内存中的Student元组连接,直到SC表处理完

        • 再读入若干块Student元组,读入一块SC元组

        • 重复上述处理过程,直到把Student表处理完

      • 设一个块能装10个Student元组或100个SC元组,在内存中存放5块Student元组和1块SC元组,则读取总块数为

        \[\frac{1000}{10}+\frac{1000}{10\times 5}\times\frac{100000}{100}=2100块 \]

      • 连接后的元组数为 \(10^3×10^4=10^7\)。设每块能装10个元组,则写出 \(10^6\)

    2. 作选择操作

      • 依次读入连接后的元组,按照选择条件选取满足要求的记录
      • 假定内存处理时间忽略。读取中间文件花费的时间(同写中间文件一样)需读入 \(10^6\)
      • 若满足条件的元组假设仅50个,均可放在内存
    3. 作投影操作

      • 把第2步的结果在Sname上作投影输出,得到最终结果
      • 第一种情况下执行查询的 \(总读写数据块=2100+10^6+10^6\)
  2. 第二种情况 \(Q_2=\Pi_{Sname}(\sigma_{SC.Cno='2'}(Student\Join SC))\)

    1. 计算自然连接
      • 执行自然连接,读取Student和SC表的策略不变,总的读取块数仍为2100块
      • 自然连接的结果比第一种情况大大减少,为 \(10^4\) 个元组
      • 写出数据块=\(10^3\)
    2. 读取中间文件块,执行选择运算,读取的数据块=\(10^3\)
    3. 把第2步结果投影输出
    • 第二种情况下执行查询的 \(总读写数据块=2100+10^3+10^3\)
    • 其执行代价大约是第一种情况的488分之一
  3. 第三种情况 \(Q_3=\Pi_{Sname}(Student\Join \sigma_{SC.Cno='2'}(SC))\)

    1. 先对SC表作选择运算,只需读一遍SC表,存取100块,因为满足条件的元组仅50个,不必使用中间文件
    2. 读取Student表,把读入的Student元组和内存中的SC元组作连接。也只需读一遍Student表共100块
    3. 把连接结果投影输出
    • 第三种情况总的读写数据块=100+100
    • 其执行代价大约是第一种情况的万分之一,是第二种情况的20分之一
  • 假如SC表的Cno字段上有索引,第一步就不必读取所有的SC元组而只需读取Cno='2'的那些元组(50个),存取的索引块和SC中满足条件的数据块大约总共3~4块
  • 若Student表在Sno上也有索引,不必读取所有的Student元组,因为满足条件的SC记录仅50个,涉及最多50个Student记录,读取Student表的块数也可大大减少
  • 把代数表达式Q1、Q2变换为Q3,有选择和连接操作时,先做选择操作,这样参加连接的元组就可以大大减少,这是代数优化
  • 在Q3中,SC表的选择操作算法有全表扫描或索引扫描,经过初步估算,索引扫描方法较优。对于Student和SC表的连接,利用Student表上的索引,采用索引连接代价也较小,这就是物理优化

代数优化

关系代数表达式等价变换规则

  • 代数优化策略:通过对关系代数表达式的等价变换来提高查询效率

  • 关系代数表达式的等价:指用相同的关系代替两个表达式中相应的关系所得到的结果是相同的

  • 两个关系表达式 \(E_1\)\(E_2\) 是等价的,可记为 \(E_1\equiv E_2\)

  • 下面是常用的等价变换规则,证明从略

    1. 连接、笛卡尔积交换律

      • \(E_1\)\(E_2\) 是关系代数表达式,F是连接运算的条件,则有

        \[E_1\times E_2\equiv E_2\times E_1\\ E_1\Join E_2\equiv E_2\Join E_1\\ E_1\underset{F}{\Join}E_2\equiv E_2\underset{F}{\Join}E_1\\ \]

    2. 连接、笛卡尔积的结合律

      • \(E_1,E_2,E_3\) 是关系代数表达式,\(F_1\)\(F_2\) 是连接运算的条件

        \[(E_1\times E_2)\times E_3\equiv E_1\times(E_2\times E_3)\\ (E_1\Join E_2)\Join E_3\equiv E_1\Join(E_2\Join E_3)\\ (E_1\underset{F_1}{\Join}E_2)\underset{F_2}{\Join}E_3\equiv E_1\underset{F_1}{\Join}(E_2\underset{F_2}{\Join}E_3) \]

    3. 投影的串接定律

      \[\Pi_{A_1,A_2,\cdots,A_n}(\Pi_{B_1,B_2,\cdots,B_m}(E))\equiv\Pi_{A_1,A_2,\cdots,A_n}(E) \]

      • E是关系代数表达式,\(A_i(i=1,2,\cdots,n),B_j(j=1,2,\cdots,m)\) 是属性名,\(\{A_1,A_2,\cdots,A_n\}\) 构成 \(\{B_1,B_2,\cdots,B_m\}\) 的子集
    4. 选择的串接定律

      \[\sigma_{F_1}(\sigma_{F_2}(E))\equiv\sigma_{F_1\wedge F_2}(E) \]

      • E是关系代数表达式,\(F_1\)\(F_2\) 是选择条件选,择的串接律说明选择条件可以合并,这样一次就可检查全部条件
    5. 选择与投影操作的交换律

      \[\sigma_F(\Pi_{A_1,A_2,\cdots,A_n}(E))\equiv\Pi_{A_1,A_2,\cdots,A_n}(\sigma_F(E)) \]

      • 选择条件F只涉及属性 \(A_1,A_2,\cdots,A_n\)

      • 若F中有不属于 \(A_1,A_2,\cdots,A_n\) 的属性 \(B_1,B_2,\cdots,B_m\) 有更一般规则:

        \[\Pi_{A_1,A_2,\cdots,A_n}(\sigma_F(E))\equiv\Pi_{A_1,A_2,\cdots,A_n}(\sigma_F(\Pi_{A_1,A_2,\cdots,A_n,B_1,\cdots B_m}(E))) \]

    6. 选择与笛卡尔积的交换律

      • 如果F中涉及的属性都是 \(E_1\) 中的属性,则

        \[\sigma_F(E_1\times E_2)\equiv\sigma_F(E_1)\times E_2 \]

      • 如果 \(F=F_1\wedge F_2\),并且 \(F_1\) 只涉及 \(E_1\) 中的属性,\(F_2\) 只涉及 \(E_2\) 中的属性,则由上面的等价变换规则1,4,6可推出

        \[\sigma_F(E_1\times E_2)\equiv\sigma_{F_1}(E_1)\times\sigma_{F_2}(E_2) \]

      • \(F_1\) 只涉及 \(E_1\) 中的属性,\(F_2\) 涉及 \(E_1\)\(E_2\) 两者的属性,则仍有

        \[\sigma_F(E_1\times E_2)\equiv\sigma_{F_2}(\sigma_{F_1}(E_1)\times E_2) \]

        它使部分选择在笛卡尔积前先做

    7. 选择与并的分配律

      • \(E=E_1\cup E_2\)\(E_1,E_2\) 有相同的属性名,则

        \[\sigma_F(E_1\cup E_2) \equiv\sigma_F(E_1)\cup\sigma_F(E_2) \]

    8. 选择与差运算的分配律

      • \(E_1\)\(E_2\) 有相同的属性名,则

        \[\sigma_F(E_1-E_2)\equiv\sigma_F(E_1)-\sigma_F(E_2) \]

    9. 选择对自然连接的分配律

      \[\sigma_F(E_1\Join E_1)\equiv\sigma_F(E_1)\Join\sigma_F(E_2) \]

      F只涉及 \(E_1\)\(E_2\) 的公共属性

    10. 投影与笛卡尔积的分配律

      • \(E_1\)\(E_2\) 是两个关系表达式,\(A_1,A_2,\cdots,A_n\)\(E_1\) 的属性,\(B_1,B_2,\cdots,B_m\)\(E_2\) 的属性,则

        \[\Pi_{A_1,A_2,\cdots,A_n,B_1,B_2,\cdots,B_m}(E_1\times E_2)\equiv\Pi_{A_1,A_2,\cdots,A_n}(E_1)\times\Pi_{B_1,B_2,\cdots,B_m}(E_2) \]

    11. 投影与并的分配律

      • \(E_1\)\(E_2\) 有相同的属性名,则

        \[\Pi_{A_1,A_2,\cdots,A_n}(E_1\cup E_2)\equiv\Pi_{A_1,A_2,\cdots,A_n}(E_1)\cup\Pi_{A_1,A_2,\cdots,A_n}(E_2) \]

查询树的启发式优化

双目运算符:并集、交集、差集、笛卡尔积、自然连接、θ连接、除

单目运算符:选择、投影

  • 典型的启发式规则

    1. 选择运算应尽可能先做。在优化策略中这是最重要、最基本的一条
    2. 把投影运算和选择运算同时进行。如有若干投影和选择运算,并且它们都对同一个关系操作,则可以在扫描此关系的同时完成所有的这些运算以避免重复扫描关系。
    3. 把投影同其前或其后的双目运算结合起来,没有必要为了去掉某些字段而扫描一遍关系
    4. 把某些选择同在它前面要执行的笛卡尔积结合起来成为一个连接运算,这句话的意思是:将选择操作和笛卡尔积操作结合起来,形成一个连接操作。连接(特别是等值连接)运算要比同样关系上的笛卡尔积省很多时间
    5. 找出公共子表达式。如果我们有两个查询都需要从同一个大表中选择满足相同条件的行,那么我们可以先计算这个公共子表达式,并将结果存储起来,以便后续查询使用。当查询的是视图时,定义视图的表达式就是公共子表达式的情况
  • 遵循这些启发式规则,应用等价变换公式来优化关系表达式的算法

    • 算法:关系表达式的优化

    • 输入:一个关系表达式的查询树

    • 输出:优化的查询树

    • 方法:

      1. 利用等价变换规则4把形如 \(\sigma_{F_1\wedge F_2\wedge\cdots\wedge F_n}(E)\) 变换为 \(\sigma_{F_1}(\sigma_{F_2}(\cdots(\sigma_{F_n}(E))\cdots))\),这样做可以更早地应用这些子条件,可能会减少后续操作需要处理的数据量

      2. 对每一个选择,利用等价变换规则4~9尽可能把它移到树的叶端,这个规则是说尽可能早地执行选择操作

      3. 对每一个投影利用等价变换规则3,5,10,11中的一般形式尽可能把它移向树的叶端,这个规则是说尽可能早地执行投影操作

        注意:等价变换规则3使一些投影消失或使一些投影出现,规则5把一个投影分裂为两个,其中一个有可能被移向树的叶端

      4. 利用等价变换规则3~5,把选择和投影的串接合并成单个选择、单个投影或一个选择后跟一个投影,使多个选择或投影能同时执行,或在一次扫描中全部完成

      5. 把上述得到的语法树的内节点分组。每一双目运算 \((\times,\Join,\cup,-)\) 和它所有的直接祖先为一组(这些直接祖先是(\(\sigma,\Pi\) 运算)。如果其后代直到叶子全是单目运算,则也将它们并入该组,但当双目运算是笛卡尔积(\(\times\)),而且后面不是与它组成等值连接的选择时,则不能把选择与这个双目运算组成同一组

  • 例4:下面给出例3中 SQL语句的代数优化示例

    SELECT Student.Sname FROM Student, SC WHERE Student.Sno=SC.Sno AND SC.Cno='2';
    
    1. 把SQL语句转换成查询树,如下图所示

      为了使用关系代数表达式优化法,假设内部表示是关系代数语法树,则上面的查询树如下图所示

    2. 对查询树进行优化

      利用规则4、6把选择 \(\sigma_{SC.Cno='2'}\) 移到叶端,上图查询树便转换成下图优化的查询树。这就是上一节一个实例中Q3的查询树表示

物理优化

  • 代数优化改变查询语句中操作的次序和组合,不涉及底层的存取路径
  • 对于一个查询语句有许多存取方案,它们的执行效率不同,仅仅进行代数优化是不够的
  • 物理优化就是要选择高效合理的操作算法或存取路径,求得优化的查询计划
  • 物理优化方法
    • 基于规则的启发式优化:启发式规则是指那些在大多数情况下都适用,但不是在每种情况下都是适用的规则
    • 基于代价估算的优化:优化器估算不同执行策略的代价,并选出具有最小代价的执行计划
    • 两者结合的优化方法:常常先使用启发式规则,选取若干较优的候选方案,减少代价估算的工作量,然后分别计算这些候选方案的执行代价,较快地选出最终的优化方案

基于启发式规则的存取路径选择优化

选择操作的启发式规则

  • 对于小关系,使用全表顺序扫描,即使选择列上有索引
  • 对于大关系,启发式规则有:
    1. 对于选择条件是“主码=值”的查询,查询结果最多是一个元组,可以选择主码索引。一般的关系数据库管理系统会自动建立主码索引
    2. 对于选择条件是“非主属性=值”的查询,并且选择列上有索引,要估算查询结果的元组数目
      如果比例较小(<10%)可以使用索引扫描方法,否则还是使用全表顺序扫描
    3. 对于选择条件是属性上的非等值查询或者范围查询,并且选择列上有索引,要估算查询结果的元组数目,如果比例较小(<10%)可以使用索引扫描方法,否则还是使用全表顺序扫描
    4. 对于用AND连接的合取选择条件,如果有涉及这些属性的组合索引,优先采用组合索引扫描方法;如果某些属性上有一般的索引,可以用例1-C4的索引扫描方法;其他情况使用全表顺序扫描
    5. 对于用OR连接的析取选择条件,一般使用全表顺序扫描

连接操作的启发式规则

  1. 如果2个表都已经按照连接属性排序,选用排序-合并算法
  2. 如果一个表在连接属性上有索引,选用索引连接算法
  3. 如果上面2个规则都不适用,其中一个表较小,选用Hash join算法
  4. 可以选用嵌套循环方法,并选择其中较小的表,确切地讲是占用的块数(b)较少的表,作为外表(外循环的表),理由如下:
    • 设连接表R与S分别占用的块数为Br与Bs
    • 连接操作使用的内存缓冲区块数为K
    • 分配K-1块给外表
    • 如果R为外表,则嵌套循环法存取的块数为Br+(Br/(K-1))*Bs
    • 显然应该选块数小的表作为外表

基于代价的优化

  • 启发式规则优化是定性的选择,适合解释执行的系统,因为解释执行的系统,优化开销包含在查询总开销之中
  • 编译执行的系统中查询优化和查询执行是分开的,可以采用精细复杂一些的基于代价的优化方法

统计信息

  • 基于代价的优化方法要计算查询的各种不同执行方案的执行代价,它与数据库的状态密切相关
  • 优化器需要的统计信息
    1. 对每个基本表:该表的元组总数(N)、元组长度(l)、占用的块数(B)、占用的溢出块数(BO)
    2. 对基表的每个列:该列不同值的个数(m)、列最大值、最小值、列上是否已经建立了索引、哪种索引(B+树索引、Hash索引、聚集索引),根据这些信息可以计算选择率(f),如果不同值的分布是均匀的,f=1/m,如果不同值的分布不均匀,则要计算每个值的选择率,f=具有该值的元组数/N
    3. 对索引:索引的层数(L)、不同索引值的个数、索引的选择基数S(有S个元组具有某个索引值)、索引的叶结点数(Y)

代价估算示例

  1. 全表扫描算法的代价估算公式
    • 如果基本表大小为B块,全表扫描算法的代价cost=B
    • 如果选择条件是“码=值”,那么平均搜索代价 cost=B/2
  2. 索引扫描算法的代价估算公式
    • 如果选择条件是“码=值”
      • 则采用该表的主索引,若为B+树,层数为L,需要存取B+树中从根结点到叶结点L块,再加上基本表中该元组所在的那一块,所以cost=L+1
    • 如果选择条件涉及非码属性
      • 若为B+树索引,选择条件是相等比较,S是索引的选择基数(有S个元组满足条件)
      • 满足条件的元组可能会保存在不同的块上,所以(最坏的情况)cost=L+S
    • 如果比较条件是>,>=,<,<=操作
      • 假设有一半的元组满足条件,就要存取一半的叶结点,通过索引访问一半的表存储块
        cost=L+Y/2+B/2
      • 如果可以获得更准确的选择基数,可以进一步修正Y/2与B/2
  3. 嵌套循环连接算法的代价估算公式
    • 嵌套循环连接算法的代价:cost=Br+BrBs/(K-1)、
    • 如果需要把连接结果写回磁盘:cost=Br+BrBs/(K-1)+(Frs×Nr×Ns)/Mrs
    • 其中Frs为连接选择性(join selectivity),表示连接结果元组数的比例
    • Mrs是存放连接结果的块因子,表示每块中可以存放的结果元组数目
  4. 排序-合并连接算法的代价估算公式
    • 如果连接表已经按照连接属性排好序,则cost=Br+Bs+(Frs×Nr×Ns)/Mrs
    • 如果必须对文件排序,还需要在代价函数中加上排序的代价,对于包含B个块的文件排序的代价大约是 \((2×B)+(2×B×log_2B)\)

第十章 数据库恢复技术

事务的基本概念

事务

  • 事务(Transaction)是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位

  • 事务和程序是两个概念

    • 在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序
    • 一个程序通常包含多个事务
  • 事务是恢复和并发控制的基本单位

  • 显式定义方式

    BEGIN TRANSACTION
    	SQL 语句1
    	SQL 语句2
    	...
    COMMIT
    

    COMMIT:表示事务正常结束,提交事务的所有操作(读+更新)。具体来说就是事务中所有对数据库的更新写回到磁盘上的物理数据库中

    BEGIN TRANSACTION
    	SQL 语句1
    	SQL 语句2
    	...
    ROLLBACK
    

    ROLLBACK:表示事务异常终止,事务运行的过程中发生了故障,不能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,事务滚回到开始时的状态

  • 隐式方式:当用户没有显式地定义事务时,数据库管理系统按缺省规定自动划分事务

事务的ACID特性

  • 事务的ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)
  1. 原子性:事务是数据库的逻辑工作单位,事务中包括的诸操作要么都做,要么都不做
  2. 一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态
    • 一致性状态:数据库中只包含成功事务提交的结果
    • 不一致状态:数据库系统运行中发生故障,有些事务尚未完成就被迫中断;这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态
    • 例如银行转帐:从帐号A中取出一万元,存入帐号B。定义一个事务,该事务包括两个操作:第一个操作是从账号A中减去一万元,第二个操作是向账号B中加入一万元。这两个操作要么全做,要么全不做,全做或者全不做,数据库都处于一致性状态。如果只做一个操作,用户逻辑上就会发生错误,少了一万元,数据库就处于不一致性状态
  3. 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰
  4. 持续性:持续性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响
  • 事务的特性
    • 保证事务ACID特性是事务处理的任务
    • 破坏事务ACID特性的因素
      1. 多个事务并行运行时,不同事务的操作交叉执行。数据库管理系统必须保证多个事务的交叉运行不影响这些事务的隔离性
      2. 事务在运行过程中被强行停止。数据库管理系统必须保证被强行终止的事务对数据库和其他事务没有任何影响

数据库恢复概述

  • 故障是不可避免的,如计算机硬件故障、软件的错误、操作员的失误、恶意的破坏
  • 故障的影响:运行事务非正常中断,影响数据库中数据的正确性;破坏数据库,全部或部分丢失数据
  • 数据库管理系统必须具有把数据库从错误状态恢复到某一已知的正确状态(亦称为一致状态或完整状态)的功能,这就是数据库的恢复管理系统对故障的对策
  • 恢复子系统是数据库管理系统的一个重要组成部分
  • 恢复技术是衡量系统优劣的重要指标

故障的种类

事务内部的故障

  • 有的是可以通过事务程序本身发现的(见下面转账事务的例子),有的是非预期的,不能由事务程序处理的

  • 例如,银行转账事务,这个事务把一笔金额从一个账户甲转给另一个账户乙

    BEGIN TRANSACTION
    	读账户甲的余额BALANCE;
    	BALANCE=BALANCE-AMOUNT;    /*AMOUNT 为转账金额*/
    	IF(BALANCE < 0) THEN
    		{打印‘金额不足,不能转账’;/*事务内部可能造成事务被回滚的情况*/
    			ROLLBACK;}         /*撤销刚才的修改,恢复事务*/
        ELSE
        	{读账户乙的余额BALANCE1;
        	BALANCE1=BALANCE1+AMOUNT;
        	写回BALANCE1;
        	COMMIT;}
    
  • 这个例子所包括的两个更新操作要么全部完成要么全部不做。否则就会使数据库处于不一致状态,例如只把账户甲的余额减少了而没有把账户乙的余额增加

  • 在这段程序中若产生账户甲余额不足的情况,应用程序可以发现并让事务滚回,撤销已作的修改,恢复数据库到正确状态

  • 事务内部更多的故障是非预期的,是不能由应用程序处理的。如运算溢出、并发事务发生死锁而被选中撤销该事务、违反了某些完整性限制而被终止等。以后,事务故障仅指这类非预期的故障

  • 事务故障意味着:事务没有达到预期的终点(COMMIT或者显式的ROLLBACK),数据库可能处于不正确状态

  • 事务故障的恢复:强行回滚(ROLLBACK)该事务,即撤销该事务已经作出的任何对数据库的修改,使得该事务象根本没有启动一样,这类恢复操作称为事务撤销(UNDO)

系统故障

  • 系统故障称为软故障,是指造成系统停止运转的任何事件,使得系统要重新启动
  • 例如:特定类型的硬件错误(如CPU故障)、操作系统故障、数据库管理系统代码错误、系统断电等
  • 这类故障影响正在运行的所有事务,但不破坏数据库。此时主存内容,尤其是数据库缓冲区(在内存)中的内容都被丢失,所有运行事务都非正常终止
  • 发生系统故障时,一些尚未完成的事务的结果可能已送入物理数据库,从而造成数据库可能处于不正确的状态。为保证数据一致性,需要清除这些事务对数据库的所有修改
    • 恢复策略:系统重新启动时,恢复程序让所有非正常终止的事务回滚,强行撤消(UNDO)所有未完成事务
  • 发生系统故障时,有些已完成的事务可能有一部分甚至全部留在缓冲区,尚未写回到磁盘上的物理数据库中,系统故障使得这些事务对数据库的修改部分或全部丢失
    • 恢复策略:系统重新启动时,恢复程序需要重做(REDO)所有已提交的事务

介质故障

  • 介质故障称为硬故障,指外存故障,如磁盘损坏、磁头碰撞、瞬时强磁场干扰
  • 介质故障破坏数据库或部分数据库,并影响正在存取这部分数据的所有事务
  • 介质故障比前两类故障的可能性小得多,但破坏性大得多

计算机病毒

  • 计算机病毒是一种人为的故障或破坏,是一些恶作剧者研制的一种计算机程序,可以繁殖和传播,造成对计算机系统包括数据库的危害
  • 计算机病毒种类:小的病毒只有20条指令,不到50B;大的病毒像一个操作系统,由上万条指令组成
  • 计算机病毒的危害:有的病毒传播很快,一旦侵入系统就马上摧毁系统;有的病毒有较长的潜伏期,计算机在感染后数天或数月才开始发病;有的病毒感染系统所有的程序和数据;有的只对某些特定的程序和数据感兴趣
  • 计算机病毒已成为计算机系统的主要威胁,自然也是数据库系统的主要威胁,数据库一旦被破坏仍要用恢复技术把数据库加以恢复

小结

  • 各类故障,对数据库的影响有两种可能性
    • 一是数据库本身被破坏
    • 二是数据库没有被破坏,但数据可能不正确,这是由于事务的运行被非正常终止造成的
  • 恢复操作的基本原理:冗余。就是利用存储在系统别处的冗余数据来重建数据库中已被破坏或不正确的那部分数据
  • 恢复的实现技术:复杂。一个大型数据库产品,恢复子系统的代码要占全部代码的10%以上

恢复的实现技术

  • 恢复机制涉及的关键问题
    1. 如何建立冗余数据:数据转储(backup)、登记日志文件(logging)
    2. 如何利用这些冗余数据实施数据库恢复

数据转储

什么是数据转储

  • 转储是指数据库管理员定期地将整个数据库复制到磁带、磁盘或其他存储介质上保存起来的过程,备用的数据文本称为后备副本(backup)或后援副本

  • 数据库遭到破坏后可以将后备副本重新装入,重装后备副本只能将数据库恢复到转储时的状态,要想恢复到故障发生时的状态,必须重新运行自转储以后的所有更新事务

  • 例如下图所示

  • 系统在Ta时刻停止运行事务,进行数据库转储,在Tb时刻转储完毕,得到Tb时刻的数据库一致性副本。系统运行到Tf时刻发生故障,为恢复数据库,首先由数据库管理员重装数据库后备副本,将数据库恢复至Tb时刻的状态,重新运行自Tb~Tf时刻的所有更新事务,把数据库恢复到故障发生前的一致状态

转储方法

  1. 静态转储与动态转储

    • 静态转储是在系统中无运行事务时进行的转储操作。即转储开始时数据库处于一致性状态,转储期间不允许对数据库的任何存取、修改活动,得到的一定是一个数据一致性的副本

    • 优点:实现简单;缺点:降低了数据库的可用性

    • 转储必须等待正运行的用户事务结束,新的事务必须等转储结束

    • 动态转储是指转储操作与用户事务并发进行,转储期间允许对数据库进行存取或修改

      • 优点:不用等待正在运行的用户事务结束,不会影响新事务的运行
      • 动态转储的缺点:不能保证副本中的数据正确有效
      • 例在转储期间的某时刻Tc,系统把数据A=100转储到磁带上,而在下一时刻Td,某一事务将A改为200。转储结束后,后备副本上的A已是过时的数据了
    • 利用动态转储得到的副本进行故障恢复

      • 需要把动态转储期间各事务对数据库的修改活动登记下来,建立日志文件
      • 后备副本加上日志文件就能把数据库恢复到某一时刻的正确状态
  2. 海量转储与增量转储

    • 海量转储:每次转储全部数据库
    • 增量转储:只转储上次转储后更新过的数据
    • 从恢复角度看,使用海量转储得到的后备副本进行恢复往往更方便;如果数据库很大,事务处理又十分频繁,则增量转储方式更实用更有效
  3. 转储方法小结

    • 转储方法分类

登记日志文件

日志文件的格式和内容

  • 日志文件(log file)是用来记录事务对数据库的更新操作的文件
  • 日志文件的格式有两种:以记录为单位的日志文件、以数据块为单位的日志文件
  • 以记录为单位的日志文件内容:
    • 各个事务的开始标记(BEGIN TRANSACTION)
    • 各个事务的结束标记(COMMIT或ROLLBACK)
    • 各个事务的所有更新操作
    • 以上均作为日志文件中的一个日志记录(log record)
  • 以记录为单位的日志文件,每条日志记录的内容:
    • 事务标识(标明是哪个事务)
    • 操作类型(插入、删除或修改)
    • 操作对象(记录ID、Block NO.)
    • 更新前数据的旧值(对插入操作而言,此项为空值)
    • 更新后数据的新值(对删除操作而言, 此项为空值)
  • 以数据块为单位的日志文件,每条日志记录的内容:事务标识、被更新的数据块

日志文件的作用

  • 可以进行事务故障恢复、进行系统故障恢复、协助后备副本进行介质故障恢复

  • 具体作用

    1. 事务故障恢复和系统故障恢复必须用日志文件

    2. 在动态转储方式中必须建立日志文件,后备副本和日志文件结合起来才能有效地恢复数据库

    3. 在静态转储方式中,也可以建立日志文件

      • 当数据库毁坏后可重新装入后援副本把数据库恢复到转储结束时刻的正确状态,然后利用日志文件,把已完成的事务进行重做处理,对故障发生时尚未完成的事务进行撤销处理
      • 不必重新运行那些已完成的事务程序就可把数据库恢复到故障前某一时刻的正确状态

登记日志文件

  • 为保证数据库是可恢复的,登记日志文件时必须遵循两条原则
    • 登记的次序严格按并发事务执行的时间次序
    • 必须先写日志文件,后写数据库
  • 为什么要先写日志文件,这是因为写数据库和写日志文件是两个不同的操作,在这两个操作之间可能发生故障。如果先写了数据库修改,而在日志文件中没有登记下这个修改,则以后就无法恢复这个修改了。如果先写日志,但没有修改数据库,按日志文件恢复时只不过是多执行一次不必要的UNDO操作,并不会影响数据库的正确性

恢复策略

事务故障的恢复

  • 事务故障是事务在运行至正常终止点前被终止,由恢复子系统利用日志文件撤消(UNDO)此事务已对数据库进行的修改。事务故障的恢复由系统自动完成,对用户是透明的,不需要用户干预
  • 事务故障的恢复步骤
    1. 反向扫描文件日志(即从最后向前扫描日志文件),查找该事务的更新操作
    2. 对该事务的更新操作执行逆操作。即将日志记录中“更新前的值”写入数据库
      • 插入操作,“更新前的值”为空,则相当于做删除操作
      • 删除操作,“更新后的值”为空,则相当于做插入操作
      • 若是修改操作,则相当于用修改前值代替修改后值
    3. 继续反向扫描日志文件,查找该事务的其他更新操作,并做同样处理
    4. 如此处理下去,直至读到此事务的开始标记,事务故障恢复就完成了

系统故障的恢复

  • 系统故障造成数据库不一致状态的原因:未完成事务对数据库的更新可能已写入数据库、已提交事务对数据库的更新可能还留在缓冲区没来得及写入数据库
  • 恢复方法:Undo故障发生时未完成的事务;Redo已完成的事务
  • 系统故障的恢复由系统在重新启动时自动完成,不需要用户干预
  • 系统故障的恢复步骤
    1. 正向扫描日志文件(即从头扫描日志文件)
      • 重做(REDO)队列:在故障发生前已经提交的事务,这些事务既有BEGIN TRANSACTION记录,也有COMMIT记录
      • 撤销(UNDO)队列:故障发生时尚未完成的事务,这些事务只有BEGIN TRANSACTION记录,无相应的COMMIT记录
    2. 对撤销(UNDO)队列事务进行撤销(UNDO)处理
      • 反向扫描日志文件,对每个撤销事务的更新操作执行逆操作,即将日志记录中“更新前的值”写入数据库
    3. 对重做(REDO)队列事务进行重做(REDO)处理
      • 正向扫描日志文件,对每个重做事务重新执行登记的操作,即将日志记录中“更新后的值”写入数据库

介质故障的恢复

  • 步骤是重装数据库,然后重做已完成的事务
    1. 装入最新的后备数据库副本(离故障发生时刻最近的转储副本),使数据库恢复到最近一次转储时的一致性状态
      • 对于静态转储的数据库副本,装入后数据库即处于一致性状态
      • 对于动态转储的数据库副本,还须同时装入转储时刻的日志文件副本,利用恢复系统故障的方法(即REDO+UNDO),才能将数据库恢复到一致性状态
    2. 装入有关的日志文件副本(转储结束时刻的日志文件副本),重做已完成的事务
      • 首先扫描日志文件,找出故障发生时已提交的事务的标识,将其记入重做队列
      • 然后正向扫描日志文件,对重做队列中的所有事务进行重做处理。即将日志记录中“更新后的值”写入数据库
  • 介质故障的恢复需要数据库管理员介入,数据库管理员只需重装最近转储的数据库副本和有关的各日志文件副本,执行系统提供的恢复命令,具体的恢复操作仍由数据库管理系统完成

具有检查点的恢复技术

问题的提出

  • 在使用日志技术恢复数据库时,需要确定哪些事务要重做,哪些要撤销,这样做有两个问题:一是搜索整个日志将耗费大量的时间,二是很多需要重做处理的事务实际上已经将它们的更新操作结果写到了数据库中,然而恢复子系统又重新执行了这些操作,浪费了大量时间
  • 具有检查点(checkpoint)的恢复技术,这种技术在日志文件中增加检查点记录(checkpoint),增加重新开始文件,并让恢复子系统在登录日志文件期间动态地维护日志

检查点技术

  • 检查点记录的内容

    • 建立检查点时刻所有正在执行的事务清单
    • 这些事务最近一个日志记录的地址
  • 重新开始文件记录各个检查点记录在日志文件中的地址

  • 动态维护日志文件的方法是,周期性地执行如下操作:建立检查点,保存数据库状态。具体步骤是:

    1. 将当前日志缓冲区中的所有日志记录写入磁盘的日志文件上
    2. 在日志文件中写入一个检查点记录
    3. 将当前数据缓冲区的所有数据记录写入磁盘的数据库中
    4. 把检查点记录在日志文件中的地址写入一个重新开始文件
  • 恢复子系统可以定期或不定期地建立检查点,保存数据库状态

    • 定期:按照预定的一个时间间隔,如每隔一小时建立一个检查点
    • 不定期:按照某种规则,如日志文件已写满一半建立一个检查点

利用检查点的恢复策略

  • 使用检查点方法可以改善恢复效率。当事务T在一个检查点之前提交,T对数据库所做的修改已写入数据库,写入时间是在这个检查点建立之前或在这个检查点建立之时,在进行恢复处理时,没有必要对事务T执行重做操作

  • 系统出现故障时,恢复子系统将根据事务的不同状态采取不同的恢复策略

    • T1:在检查点之前提交
    • T2:在检查点之前开始执行,在检查点之后故障点之前提交
    • T3:在检查点之前开始执行,在故障点时还未完成
    • T4:在检查点之后开始执行,在故障点之前提交
    • T5:在检查点之后开始执行,在故障点时还未完成
    • T3和T5在故障发生时还未完成,所以予以撤销;T2和T4在检查点之后才提交,它们对数据库所做的修改在故障发生时可能还在缓冲区中,尚未写入数据库,所以要重做;T1在检查点之前已提交,所以不必执行重做操作
  • 利用检查点的恢复步骤

    1. 从重新开始文件中找到最后一个检查点记录在日志文件中的地址,由该地址在日志文件中找到最后一个检查点记录
    2. 由该检查点记录得到检查点建立时刻所有正在执行的事务清单ACTIVE-LIST
      • 建立两个事务队列:UNDO-LIST、REDO-LIST
      • 把ACTIVE-LIST暂时放入UNDO-LIST队列,REDO队列暂为空
    3. 从检查点开始正向扫描日志文件,直到日志文件结束
      • 如有新开始的事务Ti,把Ti暂时放入UNDO-LIST队列
      • 如有提交的事务Tj,把Tj从UNDO-LIST队列移到REDO-LIST队列;直到日志文件结束
    4. 对UNDO-LIST中的每个事务执行UNDO操作,对REDO-LIST中的每个事务执行REDO操作

数据库镜像

  • 介质故障是对系统影响最为严重的一种故障,严重影响数据库的可用性

    • 介质故障恢复比较费时
    • 为预防介质故障,数据库管理员必须周期性地转储数据库
  • 数据库镜像

    • 数据库管理系统自动把整个数据库或其中的关键数据复制到另一个磁盘上
    • 数据库管理系统自动保证镜像数据与主数据的一致性,每当主数据库更新时,数据库管理系统自动把更新后的数据复制过去
  • 出现介质故障时,可由镜像磁盘继续提供使用,同时数据库管理系统自动利用镜像磁盘数据进行数据库的恢复,不需要关闭系统和重装数据库副本

  • 没有出现故障时,可用于并发操作,一个用户对数据加排他锁修改数据,其他用户可以读镜像数据库上的数据,而不必等待该用户释放锁

  • 频繁地复制数据自然会降低系统运行效率,在实际应用中用户往往只选择对关键数据和日志文件镜像,不是对整个数据库进行镜像

第十一章 并发控制

  • 数据库是一个共享资源,允许多个用户同时使用的数据库系统。如飞机定票数据库系统、银行数据库系统

  • 特点:在同一时刻并发运行的事务数可达数百上千个

  • 事务串行执行:每个时刻只有一个事务运行,其他事务必须等到这个事务结束以后方能运行。不能充分利用系统资源,发挥数据库共享资源的特点

  • 交叉并发方式(Interleaved Concurrency):在单处理机系统中,事务的并行执行是这些并行事务的并行操作轮流交叉运行。单处理机系统中的并行事务并没有真正地并行运行,但能够减少处理机的空闲时间,提高系统的效率

  • 同时并发方式(simultaneous concurrency):多处理机系统中,每个处理机可以运行一个事务,多个处理机可以同时运行多个事务,实现多个事务真正的并行运行。最理想的并发方式,但受制于硬件环境

  • 本章讨论的数据库系统并发控制技术是以单处理机系统为基础的

  • 事务并发执行带来的问题

    • 会产生多个事务同时存取同一数据的情况
    • 可能会存取和存储不正确的数据,破坏事务隔离性和数据库的一致性
  • 数据库管理系统必须提供并发控制机制,并发控制机制是衡量一个数据库管理系统性能的重要标志之一

并发控制概述

  • 事务是并发控制的基本单位。为了保证事务的隔离性和一致性,数据库管理系统需要对并发操作进行正确调度

  • 并发操作带来数据的不一致性实例

  • 例1:飞机订票系统中的一个活动序列

    1. 甲售票点(事务T1)读出某航班的机票余额A,设A=16
    2. 乙售票点(事务T2)读出同一航班的机票余额A,也为16
    3. 甲售票点卖出一张机票,修改余额A←A-1,所以A为15,把A写回数据库
    4. 乙售票点也卖出一张机票,修改余额A←A-1,所以A为15,把A写回数据库
    • 结果明明卖出两张机票,数据库中机票余额只减少1
  • 这种情况称为数据库的不一致性,是由并发操作引起的

  • 在并发操作情况下,对T1、T2两个事务的操作序列的调度是随机的

  • 若按上面的调度序列执行,T1事务的修改就被丢失。因为第4步中T2事务修改A并写回后覆盖了T1事务的修改

  • 下面把事务读数据x记为R(x),写数据x记为W(x)

  1. 丢失修改

    • 两个事务T1和T2读入同一数据并修改,T2的提交结果破坏了T1提交的结果,导致T1的修改被丢失。上面飞机订票例子就属此类
  2. 不可重复读

    • 不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果
    • 不可重复读包括三种情况:
      1. 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值
      2. 事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录神秘地消失了
      3. 事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录
    • 后两种不可重复读有时也称为幻影现象(Phantom Row)
    • T1读取B=100进行运算,T2读取同一数据B,对其进行修改后将B=200写回数据库。T1为了对读取值校对重读B,B已为200,与第一次读取值不一致
  3. 读“脏”数据

    • 读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,T2读到的数据就为“脏”数据,即不正确的数据
    • T1将C值修改为200,T2读到C为200,T1由于某种原因撤销,其修改作废,C恢复原值100,这时T2读到的C为200,与数据库内容不一致,就是“脏”数据
  • 数据不一致性:由于并发操作破坏了事务的隔离性。并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性
  • 对数据库的应用有时允许某些不一致性,例如有些统计工作涉及数据量很大,读到一些“脏”数据对统计精度没什么影响,可以降低对一致性的要求以减少系统开销
  • 并发控制的主要技术:封锁(Locking)、时间戳(Timestamp)、乐观控制法、多版本并发控制(MVCC)

封锁

  • 封锁就是事务T在对某个数据对象(例如表、记录等)操作之前,先向系统发出请求,对其加锁,加锁后事务T就对该数据对象有了一定的控制,在事务T释放它的锁之前,其它的事务不能更新此数据对象。封锁是实现并发控制的一个非常重要的技术

  • 一个事务对某个数据对象加锁后究竟拥有什么样的控制由封锁的类型决定。基本封锁类型有:排它锁(Exclusive Locks,简记为X锁)和共享锁(Share Locks,简记为S锁)

  • 排它锁又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这保证其他事务在T释放A上的锁之前不能再读取和修改A

  • 共享锁又称为读锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。保证其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改

  • 在锁的相容矩阵中,最左边一列表示事务T1已经获得的数据对象上的锁的类型,其中横线表示没有加锁。

  • 最上面一行表示另一事务T2对同一数据对象发出的封锁请求。T2的封锁请求能否被满足用矩阵中的Y和N表示。Y表示事务T2的封锁要求与T1已持有的锁相容,封锁请求可以满足;N表示T2的封锁请求与T1已持有的锁冲突,T2的请求被拒绝

封锁协议

  • 封锁协议:在运用X锁和S锁对数据对象加锁时,需要约定一些规则,这些规则为封锁协议(Locking Protocol)。如:何时申请X锁或S锁、持锁时间、何时释放
  • 对封锁方式规定不同的规则,就形成了各种不同的封锁协议,它们分别在不同的程度上为并发操作的正确调度提供一定的保证

一级封锁协议

  • 一级封锁协议是指,事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放

  • 一级封锁协议可防止丢失修改,并保证事务T是可恢复的。例如

    • 事务T1在读A进行修改之前先对A加X锁,当T2再请求对A加X锁时被拒绝,T2只能等待T1释放A上的锁后获得对A的X锁,这时T2读到的A已经是T1更新过的值15,T2按此新的A值进行运算,并将结果值A=14写回到磁盘,避免了丢失T1的更新
  • 在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读“脏”数据

二级封锁协议

  • 二级封锁协议是指,在一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁

  • 二级封锁协议可以防止丢失修改和读“脏”数据。在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读,例如

    • 事务T1在对C进行修改之前,先对C加X锁,修改其值后写回磁盘,T2请求在C上加S锁,因T1已在C上加了X锁,T2只能等待,T1因某种原因被撤销,C恢复为原值100,T1释放C上的X锁后T2获得C上的S锁,读C=100。避免了T2读“脏”数据

三级封锁协议

  • 三级封锁协议是指,在一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放

  • 三级封锁协议可防止丢失修改、读脏数据和不可重复读,例如

    • 事务T1在读A,B之前,先对A,B加S锁,其他事务只能再对A,B加S锁,而不能加X锁,即其他事务只能读A,B,而不能修改。
    • 当T2为修改B而申请对B的X锁时被拒绝只能等待T1释放B上的锁,T1为验算再读A,B,这时读出的B仍是100,求和结果仍为150,即可重复读,T1结束才释放A,B上的S锁,T2才获得对B的X锁

封锁协议小结

  • 三级协议的主要区别在于什么操作需要申请封锁以及何时释放锁

  • 不同的封锁协议使事务达到的一致性级别不同,封锁协议级别越高,一致性程度越高

活锁和死锁

  • 封锁技术可以有效地解决并行操作的一致性问题,但也带来一些新的问题:死锁和活锁

活锁

  • 事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待。T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求....T2有可能永远等待,这就是活锁的情形

  • 避免活锁可以采用先来先服务的策略。当多个事务请求封锁同一数据对象时,按请求封锁的先后次序对这些事务排队,该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁

死锁

  • 事务T1封锁了数据R1,T2封锁了数据R2,T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁,接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁,这样T1在等待T2,而T2又在等待T1,T1和T2两个事务永远不能结束,形成死锁

  • 解决死锁的方法:采用死锁的预防和死锁的诊断与解除

死锁的预防

  • 产生死锁的原因是两个或多个事务都已封锁了一些数据对象,然后又都请求对已为其他事务封锁的数据对象加锁,从而出现死等待。预防死锁的发生就是要破坏产生死锁的条件,有以下两种方法
  1. 一次封锁法
    • 要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行;一次就将以后要用到的全部数据加锁,势必扩大了封锁的范围,从而降低了系统的并发度
    • 数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象,为此只能扩大封锁范围,将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度
  2. 顺序封锁法
    • 顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁
    • 顺序封锁法存在的问题
      • 维护成本:数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作而不断地变化,要维护这样的资源的封锁顺序非常困难,成本很高
      • 难以实现:事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁
  • 在操作系统中广为采用的预防死锁的策略并不太适合数据库的特点,数据库管理系统在解决死锁的问题上更普遍采用的是诊断并解除死锁的方法

死锁的诊断与解除

  • 数据库系统中诊断死锁的方法与操作系统类似,一般使用超时法或事务等待图法
  1. 超时法

    • 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
    • 优点是实现简单;缺点是有可能误判死锁,时限若设置得太长,死锁发生后不能及时发现
  2. 等待图法

    • 用事务等待图动态反映所有事务的等待情况

    • 事务等待图是一个有向图G=(T,U),T为结点的集合,每个结点表示正运行的事务;U为边的集合,每条边表示事务等待的情况

    • 若T1等待T2,则T1,T2之间划一条有向边,从T1指向T2

    • 图(a)中,事务T1等待T2,T2等待T1,产生了死锁

    • 图(b)中,事务T1等待T2,T2等待T3,T3等待T4,T4又等待T1,产生了死锁

    • 图(b)中,事务T3可能还等待T2,在大回路中又有小的回路

    • 并发控制子系统周期性地(比如每隔数秒)生成事务等待图,检测事务。如果发现图中存在回路,则表示系统中出现了死锁

    • 解除死锁的方法:选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有的锁,使其它事务能继续运行下去

并发调度的可串行性

  • 数据库管理系统对并发事务不同的调度可能会产生不同的结果
  • 显然串行调度是正确的。执行结果等价于串行调度的调度也是正确的,称为可串行化调度

可串行化调度

  • 可串行化(Serializable)调度:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同

  • 可串行性(Serializability):是并发事务正确调度的准则,一个给定的并发调度,当且仅当它是可串行化的,才认为是正确调度

  • 例2:现在有两个事务,分别包含下列操作:

    事务T1:读B;A=B+1;写回A

    事务T2:读A;B=A+1;写回B

    现给出对这两个事务不同的调度策略

    • 假设A、B的初值均为2。按T1→T2次序执行结果为A=3,B=4;串行调度策略,正确的调度

    • 假设A、B的初值均为2。T2→T1次序执行结果为B=3,A=4,串行调度策略,正确的调度

    • 不可串行化调度,执行结果与前两种结果都不同,是错误的调度

    • 可串行化调度,执行结果与串行调度的执行结果相同,是正确的调度

冲突可串行化调度

  • 冲突操作是指不同的事务对同一数据的读写(写读)操作和写写操作

    Ri(x)与Wj(x)	       /*事务Ti读x,Tj写x,其中i≠j*/
    Wi(x)与Wj(x)        /*事务Ti写x,Tj写x,其中i≠j*/
    

    其他操作是不冲突操作

  • 一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度Sc',如果Sc'是串行的,称调度Sc是冲突可串行化的调度

  • 其中不能交换(Swap)的动作:同一事务的两个操作、不同事务的冲突操作

  • 若一个调度是冲突可串行化,则一定是可串行化的调度。可用这种方法判断一个调度是否是冲突可串行化的

  • 例3:今有调度

    Sc2等价于一个串行调度T1,T2。所以Sc1冲突可串行化的调度

  • 冲突可串行化调度是可串行化调度的充分条件,不是必要条件。还有不满足冲突可串行化条件的可串行化调度

  • 例4:有3个事务 \(T_1=W_1(Y)W_1(X),T_2=W_2(Y)W_2(X),T_3=W_3(X)\)

    • 调度 \(L_1=W_1(Y)W_1(X)W_2(Y)W_2(X)W_3(X)\) 是一个串行调度。
      调度 \(L_2=W_1(Y)W_2(Y)W_2(X)W_1(X)W_3(X)\) 不满足冲突可串行化。但是调度L2是可串行化的,因为L2执行的结果与调度L1相同,Y的值都等于T2的值,X的值都等于T3的值
  • 冲突可串行性的判定:优先图

    • 通过例子来解释:设调度S涉及三个事务:T1,T2,T3

      \(S:r_2(A)r_1(B){\color{red}w_2(A)r_3(A)}{\color{green}w_1(B)}w_3(A){\color{green}r_2(B)}w_2(B)\)

    • 优先图中:

      • 结点为S中的各个事务Ti
      • 有向弧:Ti->Tj(判定冲突关系 且 当Ti优先于Tj)
    • 判定冲突关系:对于事务调度中的每一对操作,判定它们之间是否存在冲突关系,即不同事务对相同数据读写冲突、写写冲突和写读冲突(两个进行判断时至少有一个写)

      • 在上例中,从 \({\color{red}w_2(A)r_3(A)}\) 可以看出:\(T_2\rightarrow T_3\)
      • \({\color{green}w_1(B)r_2(B)}\) 可以看出:\(T_1\rightarrow T_2\)
    • 因此S的调度优先图:

    • 判断其中是否有环:

      • 有,S不是冲突可串行化
      • 无,S冲突可串行化,而且任意一个拓扑顺序就是一个冲突等价串行顺序
    • 反例 \(S_1:r_2(A){\color{red}r_1(B)}{\color{green}w_2(A)}{\color{brown}r_2(B)}{\color{green}r3(A)}{\color{brown}w_1(B)}w_3(A){\color{red}w_2(B)}\)

      • \({\color{green}w_2(A)r_2(A)}\) 可以看出:\(T_2\rightarrow T_3\)

      • \({\color{red}r_1(B)w_2(B)}\) 可以看出:\(T_1\rightarrow T_2\)

      • \({\color{brown}r_2(B)w_1(B)}\) 可以看出:\(T_2\rightarrow T_1\)

      • 调度优先图有环,S1不是冲突可串行化的

两段锁协议

  • 数据库管理系统普遍采用两段锁协议的方法实现并发调度的可串行性,从而保证调度的正确性

  • 两段锁协议是指所有事务必须分两个阶段对数据项加锁和解锁

    • 在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁
    • 在释放一个封锁之后,事务不再申请和获得任何其他封锁
  • “两段”锁的含义是事务分为两个阶段

    • 第一阶段是获得封锁,也称为扩展阶段。事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁
    • 第二阶段是释放封锁,也称为收缩阶段。事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁
  • 例:事务Ti遵守两段锁协议,其封锁序列是:

    事务Tj不遵守两段锁协议,其封锁序列是:

  • 下图所示的调度是遵守两段锁协议的,因此一定是一个可行化调度

    • 如何验证?使用优先图,写出序列 \(R_1(A)R_2(C)W_1(A)W_2(C)R_1(B)W_1(B)R_2(A)W_2(A)\)
    • 其中的冲突只能得到:\(T_1\rightarrow T_2\)
  • 事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件

  • 若并发事务都遵守两段锁协议,则对这些事务的任何并发调度策略都是可串行化的

  • 若并发事务的一个调度是可串行化的,不一定所有事务都符合两段锁协议

    • 例如:有两个事务T1和T2,执行顺序是T1访问A,T2访问B,T1访问B,T2访问A。这个调度是可串行化的,因为它等价于T1先执行,然后T2执行。但是,它并不遵守两段锁协议,因为T1在访问B之前,已经释放了对A的锁
  • 两段锁协议与防止死锁的一次封锁法

    • 一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议

    • 但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁

    • 例:遵守两段锁协议的事务发生死锁

封锁的粒度

  • 封锁对象的大小称为封锁粒度(Granularity)
  • 封锁的对象可以是逻辑单元、物理单元
  • 例:
    • 在关系数据库中,封锁对象可以是这样一些逻辑单元:属性值、属性值的集合、元组、关系、索引项、整个索引、整个数据库等
    • 封锁对象也可以是物理单元:页(数据页或索引页)、物理记录等
  • 封锁粒度与系统的并发度和并发控制的开销密切相关
    • 封锁的粒度越大,数据库所能够封锁的数据单元就越少,并发度就越小,系统开销也越小
    • 封锁的粒度越小,并发度较高,但系统开销也就越大
  • 例:
    • 若封锁粒度是数据页,事务T1需要修改元组L1,则T1必须对包含L1的整个数据页A加锁。如果T1对A加锁后事务T2要修改A中元组L2,则T2被迫等待,直到T1释放A
    • 如果封锁粒度是元组,则T1和T2可以同时对L1和L2加锁,不需要互相等待,提高了系统的并行度
    • 又如,事务T需要读取整个表,若封锁粒度是元组,T必须对表中的每一个元组加锁,开销极大
  • 多粒度封锁(Multiple Granularity Locking):在一个系统中同时支持多种封锁粒度供不同的事务选择
  • 选择封锁粒度时应该考虑封锁开销和并发度两个因素,适当选择封锁粒度
    • 需要处理多个关系的大量元组的用户事务:以数据库为封锁单位
    • 需要处理大量元组的用户事务:以关系为封锁单元
    • 只处理少量元组的用户事务:以元组为封锁单位

多粒度封锁

  • 多粒度树

    • 以树形结构来表示多级封锁粒度
    • 根结点是整个数据库,表示最大的数据粒度
    • 叶结点表示最小的数据粒度
  • 例:如下图,三级粒度树。根结点为数据库,数据库的子结点为关系,关系的子结点为元组

  • 允许多粒度树中的每个结点被独立地加锁。对一个结点加锁意味着这个结点的所有后裔结点也被加以同样类型的锁

  • 在多粒度封锁中一个数据对象可能以两种方式封锁:显式封锁和隐式封锁

    • 显式封锁:直接加到数据对象上的封锁
    • 隐式封锁:是该数据对象没有独立加锁,是由于其上级结点加锁而使该数据对象加上了锁
    • 显式封锁和隐式封锁的效果是一样的
  • 因此系统检查封锁冲突时,要检查显式封锁,还要检查隐式封锁

    • 例如事务T要对关系R1加X锁,系统必须搜索其上级结点数据库、关系R1,还要搜索R1的下级结点,即R1中的每一个元组,如果其中某一个数据对象已经加了不相容锁,则T必须等待
  • 对某个数据对象加锁,系统要检查该数据对象有无显式封锁与之冲突;再检查所有上级结点,检查本事务的显式封锁是否与该数据对象上的隐式封锁冲突(由上级结点已加的封锁造成的);还要检查所有下级结点,看上面的显式封锁是否与本事务的隐式封锁(将加到下级结点的封锁)冲突

  • 这样的检查方法效率很低。为此人们引进了一种新型锁,称为意向锁(intention lock)

意向锁

  • 引进意向锁(intention lock)目的就是提高对某个数据对象加锁时系统的检查效率
  • 如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加基本锁,必须先对它的上层结点加意向锁
    • 例如,对任一元组加锁时,必须先对它所在的数据库和关系加意向锁
  • 常用意向锁:意向共享锁(Intent Share Lock,简称IS锁)、意向排它锁(Intent Exclusive Lock,简称IX锁)、共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁)
  1. IS锁:如果对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁
    • 例如:事务T1要对R1中某个元组加S锁,则要首先对关系R1和数据库加IS锁
  2. IX锁:如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁
    • 例如:事务T1要对R1中某个元组加X锁,则要首先对关系R1和数据库加IX锁
  3. SIX锁:如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX = S + IX
    • 例:对某个表加SIX锁,则表示该事务要读整个表(所以要对该表加S锁),同时会更新个别元组(所以要对该表加IX锁)
  • 意向锁的相容矩阵

  • 锁的强度

    • 锁的强度是指它对其他锁的排斥程度

    • 一个事务在申请封锁时以强锁代替弱锁是安全的,反之则不然

  • 具有意向锁的多粒度封锁方法

    • 申请封锁时应该按自上而下的次序进行
    • 释放封锁时则应该按自下而上的次序进行
    • 例如:事务T1要对关系R1加S锁
      • 要首先对数据库加IS锁
      • 检查数据库和R1是否已加了不相容的锁(X或IX)
      • 不再需要搜索和检查R1中的元组是否加了不相容的锁(X锁)
  • 具有意向锁的多粒度封锁方法,提高了系统的并发度,减少了加锁和解锁的开销,在实际的数据库管理系统产品中得到广泛应用