2022-3-26 由于Gitee开启了防盗链机制,本笔记所有图片全部失效,且本笔记已经是旧版笔记了,博主在CSDN已经整理好了最新的笔记,欢迎前往观看点赞关注,指路→数据库杂谈

引言

教材:

  • 《数据库系统教程》—东南大学王能斌

  • 《数据库原理及其应用教程》—黄德才

  • 《数据库系统概念》—原书

视频:

  • 数据库基本原理—东南大学徐立臻

  • MySQL—动力结点

  • 华中科技大学数据库系统原理网课

【注:本笔记包含研究生部分课程和本科生全部课程,只供学习用途,不可用作商业需求。】

作者:尘鱼好美【Misaki】


第一章、数据库系统引论

1.1、课程简介和内容安排

在这个课程中,我们将学到数据库系统的基本原理,特别是数据库系统的联系,主要内容包括以下几点:

数据模型、SQL语句和用户接口。(重点学习!)

DBMS的关键原理。

数据库的安全性和完整性约束

介绍数据库系统的贡献

一些关于数据库技术新的研究和应用领域,比如数据仓库,数据挖掘,XML数据管理。


1.2、数据库基本概念

什么是数据库?

数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。


什么是数据库管理系统?

数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户通过DBMS访问数据库中的数据,数据库管理员也通过DBMS进行数据库的维护工作。它可以支持多个应用程序和用户用不同的方法在同时或不同时刻去建立,修改和询问数据库。大部分DBMS提供数据定义语言DDL(Data Definition Language)和数据操作语言DML(Data Manipulation Language),供用户定义数据库的模式结构与权限约束,实现对数据的追加、删除等操作。


文件和数据库的区别(这里我们讲的是在应用开发上的区别)

其实文件和数据库我们都可以用来存数据,但是文件是操作系统提供的一个最简单最基本的一个存取数据的机制;在操作系统里,数据在文件里没有结构,而是一串平滑的字符流。所以,文件系统明显地有下列5个缺点:

  • 数据访问困难;编写应用程序很不方便。由于操作系统只提供打开、关闭、读、写等几个低级的文件操作系统,对文件的查询、修改等处理都必须在应用程序解决。
  • 数据冗余不一致;文件的设计很难满足多种应用程序的不同要求,数据冗余往往是不可避免的 。
  • 维护艰难;对文件结构的每个修改将导致应用程序的修改,应用程序的维护工作量很大。
  • 并发访问异常;文件系统一般不支持对文件的并发访问。
  • 数据孤立;由于数据缺少同一管理,在数据的结构、编码、表示格式、命名以及输出格式等方面不容易做到规范化、标准化;在数据的安全和保密方面,也难以采取有效的措施。
  • 原子性问题;传统的文件处理系统能够中,保持原子性很难做到。

为什么要使用数据库管理系统?

  • 数据独立性和有效访问。

  • 减少应用开发时间。

  • 数据完整性和安全性。

  • 对数据库的统一管理。

  • 故障恢复。


为什么要学习和研究数据库?

从计算转向信息

数据的规模正在急剧的膨胀,迫切的需要有效地管理。

数据库管理系统包含了计算机科学技术的绝大部分成果。

  • 操作系统、编译、人工智能、逻辑、多媒体查询、云识别

数据、数据模型和数据模式

  • 数据:就是描述现实世界的符号,他是信息存在的形式。

  • 数据模型:数据模型就是来描述数据的一组概念和定义;也可以理解为数据结构,实际上就是描述现实世界的方法。

  • 数据模式:用一个给定的数据模型对一个具体数据的描述

你也可以这么理解:数据模型相当于编程语言,比如C++,Java等,数据模式就是用对应的编程语言写出来的软件。


数据模型的分类

数据模型可以分为四类:

  1. 关系模型

    下面有论述,这里我们不过多提及

  2. 实体联系模型(E-R模型)

    下面论述,我们这里不过多提及

  3. 基于对象的数据模型

    面向对象的程序设计已经目前称为主流的软件开发方法。这导致面向对象数据模型的发展,面向对象的数据类型可以看成是E-R模型增加了封装,方法(函数)和对象标识等概念的扩展。对象-关系数据模型结合了面向对象的数据模型和关系数据模型的特征。

  4. 半结构化数据模型

    这里我们不过多累述,但是我们要知道可扩展标记语言被广泛地用来表示半结构化数据。

    【注:除此之外,实际上还有网状数据模型和层次数据模型,下面我们也有讲述,不过由于他们已经过时了,现在已经很少使用了。】


1.3、数据库三级模式和两级数据独立性

三级模式指的是:许多视图、简单的概念(逻辑)模式和物理模式。

image-20211004004634420
  • 外模式(视图)描述的是如何去看数据。

  • 概念模式定义逻辑结构。

  • 物理模式描述文件和索引的使用。


表只是一个逻辑概念,也就是我们说的概念模式,那么表以后在磁盘上会怎么存呢;比如说学生基本信息表,你是用一个堆文件来存,还是用一个哈希文件来存,还是用一个簇集,这都是取决于我们用什么结构来存;而存法,就是physical Schema(物理模式)。概念模式,就是我们刚刚说的表,表的结构是什么,属性有多少,长度如何,这就是概念模式,我们把概念模式里的表叫做基表,基表是用真正的某种结构存储在磁盘上的。而外模式(视图),是通过对基表的改造,来呈现不同的样子给用户看,不同权限的用户看到的视图是不一样的。


例子:大学数据库

概念模式

Student(sid:string,name:string,login:string,age:integer,gpa:real)
Course(cid:string,cname:string,credits:integer)
Enrolled(sid:string,cid:string,grade:integer)

物理模式

  • 以堆文件来存放

  • 在学生表的第一列建一个B+树索引

视图

Course_info(cid:string,enrollment:integer)

说明:视图可以不是基表的信息,可以是通过基表的信息计算出来的。


数据独立性

应用程序可以不在意数据是怎么组成的和存放的。

  • 逻辑数据独立性:保护数据的逻辑结构的改变

    一个在数据库的基础之上开发的应用程序不受数据逻辑结构的影响

    假如基表变动(也就是数据结构变动),那么通过改变一些逻辑,能够使得原来基表还未变动之前的视图不受改变。即基表虽然改变,但是视图为了保证不变,我修改基表和视图的映射。

  • 物理数据独立性:保护数据的物理结构的改变

    一个表结构变化,比如我前面使用哈希文件存的,但是现在用堆文件存,只要表逻辑结构不变,物理结构变化是没事的。


数据独立性是DBMS最大的好处之一。因为文件系统是没有这样的优点的。


1.4、数据库发展历史及分类

  • 1964年,第一个DBMS:美国通用电气公司Bachman等人开发的IDS,采用网状数据模型。

  • 1969年,IBM公司推出了IMS,采用层次数据模型。

  • 1970年,E.Fcodd(IBM)提出了关系数据模型,以关系(表)作为描述数据的基础。


根据DBMS发展

在计算机发展早期,那时候没有网络,一台主机带着一堆终端,大家通过终端来访问一台机器,很明显,那时候数据库也是集中放在一台机器上,所以当时DBMS早期是一种集中式数据库结构。

后来出现了并行式计算机系统,所谓并行计算机系统就是在一台计算机上,可能会有多个CPU,每个CPU可能带有自己的内存甚至带有自己的硬盘。在考虑并行计算机系统的软硬件特点,前人做出了能够提高效率的并行式数据库系统。

70年代中后期,随着局域网的发展还有其他网络的普及,出现了分布式数据库系统;由于当时电脑配置还不是很高级,很多时候用一个分布式操作系统通过网络控制多台机器,使其协调共同完成一个数据库系统的工作。

随着无线网络的发展,后来又出现了移动数据库的概念,也就是充分利用移动网络的能力,在随时随地都能够访问到数据库。网格计算和云计算都是这方面的技术。


数据库架构

CS结构

C/S又称Client/Server或客户/服务器模式,服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、Informix或 SQL Server。客户端需要安装专用的客户端软件。image-20211004015417778

BS结构(三层数据库结构)

B/S是Brower/Server的缩写,客户机上只要安装一个浏览器(Browser),如Netscape Navigator或Internet Explorer,服务器安装Oracle、Sybase、Informix或 SQL Server等数据库。浏览器通过Web Server 同数据库进行数据交互。image-20211004014511738

拿VPN来说,现在比如说校园网,都是通过IE浏览器然后访问互联网,然后互联网给Web服务端发请求,如果成功,则通过App服务端访问校园网数据库,如果失败则进入不了校园网。

C/S的优点是能充分发挥客户端PC的处理能力,很多工作可以在客户端处理后再提交给服务器。对应的优点就是客户端响应速度快。缺点主要有以下几个: 只适用于局域网。而随着互联网的飞速发展,移动办公和分布式办公越来越普及,这需要我们的系统具有扩展性。这种方式远程访问需要专门的技术,同时要对系统进行专门的设计来处理分布式的数据。 客户端需要安装专用的客户端软件。首先涉及到安装的工作量,其次任何一台电脑出问题,如病毒、硬件损坏,都需要进行安装或维护。特别是有很多分部或专卖店的情况,不是工作量的问题,而是路程的问题。


1.5、数据库系统的组成与生命周期

数据库系统组成

image-20211004020310273

数据库系统=应用+DBMS+数据库+DBA


为什么要用DBMS?

  1. 提供了一个高级的用户接口

  2. 查询处理和优化

  3. 数据目录管理

  4. 并发控制

  5. 恢复功能

  6. 完整性约束检查


数据库和软件一样,也有一个生存周期,它包含下列五个阶段。

  1. 数据库系统的规划

    它包含系统的应用和功能的确认、应用环境的分析、DBMS及其支持环境的选择和配置、人员的配置和培训以及投资估算和效益分析等活动。

  2. 数据库设计

    数据库设计实际上主要是数据的表示方法和存储结构的设计。

  3. 数据库建立

  4. 数据库的运行、管理和维护

    数据库投入运行后,还必须监视和调优其性能,听取用户的反馈,必要时对数据库做相应的调整

  5. 数据库的扩充和重构

    一个单位的组成、结构和功能是会变化的,其对应的数据模式也须做相应的改变。


1.6、作业

1、试着比较文件系统和数据库系统,并指出其主要差别。


2、何为数据独立性?试着说明其重要性


3、试着区别下面的名词

(1)数据模型和数据模式。

(2)概念数据模型和概念数据模式。

(3)数据库系统和数据库管理系统。


4、解释物理独立性的概念,以及他在数据库系统中的重要性。


5、数据库管理员的五种主要作用是什么。


6、两层和三层体系结构之间的区别,对web应用来说哪一种更合适?为什么?


第二章、数据模型

2.1、层次数据模型

在现实世界中,有很多数据都是具有层次的特征的,由数据结构我们自然想到可以用树来表达现实世界,层次数据模型就是基于这种想法提出来的。

层次数据模型的提出,首先是为了模拟这种按层次组织起来的事物。


什么叫记录和字段?

假如现在有个学生表

学生名 学号 班级 年龄 生日

那么我们称上述的为记录(的)型

其中学生名或者学号或者班级等我们称为字段

学生名 学号 班级 年龄 生日
baka爱 15 9年一班 15 2000.1.25

这个表格里面具体到了某个人,也就是记录的实例,我们简称记录。在数据结构中我们也叫作数据元素。


层次数据模型中的PCR关系?

这里的PCR不是高中生物课本的PCR,而是指双亲子女关系(Parent-Chlid-Relationship,PCR),他是层次模型中最基本的数据关系,他代表了两个记录型之间一对多的关系。例如学生和课程,一个学生可以选修多个课程。


层次数据模式长什么样?

由PCR可知,从最开始出发,一对多往下发散,那么层次数据模型应该是一棵树。

因此,除了根,每个记录型都应该有他自己对应且唯一的父母,但是每个父母可以有多个子女。


虚(拟)记录?

其实抛开这个问题不讲,在现实世界中,不是说所有的关系都是一对多的,也有可能是多对一,或者多对多,假设我们有这么一个例子,一个学生可以选择多门课,一门课可以被多个学生选择。假设学生是父,那么课就是子,多门课只能对应一个父,也就是说想要一门课要想能被多个学生选择,那么根据PCR关系,他必须复制多份一模一样的课出来,如图所示。

问题的关系:

image-20211002135718773

用PCR处理后:

image-20211002135745456

所以上述的情况直接导致了一个问题就是,明明不需要三个人工智能,却复制了三份,造成了数据冗余。

再比如上面说到的多对一关系。

image-20211002135920954

为了符合子女们只能有一个双亲,所以学生得复制一份。

image-20211002135942505

由此可知,PCR会导致数据冗余,不但浪费存储空间,还导致数据不一致。所以我们这时候就回到了上面的问题,什么是虚记录?

虚记录(Virtual Record),其实就是如果遇见上述的关系,我们无须复制一份,只需要在引用的地方用其指针来代替即可,用指针替代的记录叫做虚记录,虚记录通常用下标V表示,指针用虚线箭头表示。

例如学生和课程的关系:

image-20211002140339852

由图可知,学生指向课程,但是指过去无法指回来;所以我们指向他的虚记录,虚记录指回本体;课程本体如果指向学生,那学生无法指回课程;所以课程本体指向学生的虚记录,学生虚记录指向学生本体。

同理:

image-20211002140514286

运动队和班级都指向学生,那么只需要班或者运动队其中一个指向学生的虚记录,学生的虚记录再指回本体即可。


2.2、网状数据模型

网状数据模型以记录为数据的存储单位,以系作为基本数据结构。

现实世界中的实体我们在网状数据模型中叫做记录,同类的记录我们叫数据型,在记录型里面同类的记录我们叫作数据项。如在数据结构中,我们对数据项的定义是:构成数据元素的不可分割的最小单位。


记录里面只能有一个值吗?

不一定,例如还是学生这张表

学生名 学号 班级 年龄 生日 地址
baka爱 15 9年一班 15 2000.1.25

这个学生的地址可能有两个,一个是他家,一个是他爸妈工作的地方,那么我们称这个数据项为多值数据项,可以用有序的集合来表示,简单的多值数据项我们称为向量


记录在数据结构里面有没有对应的地址?

有,每个记录有一个唯一地标识他的数据库码(DataBase Key,DBK)。DBK可以看成记录的逻辑地址,可做记录的替身或用来寻找记录。


网状数据结构中的系(set)是什么?

所谓的系就是联系的意思,一般来说,系代表两个记录型之间1:N联系。

比如:

班这个表和学生这类表,其中班和学生有关系,一个班表包含多个班学生表,我们称“班级-学生”的系为他们之间的联系。

我们通常叫1为首记录,N为属记录。

体现在上面的案例上就是班级是首记录,学生是属记录。


在一个系的属记录里,如果有多种类型的属记录,我们叫做多属系。

我们可以这么理解,还是上面这个例子,如果我们把那些学生细分,可以分为男学生和女学生,那么我们这时候“班级-学生”这个系就构成了一个多属系。

或者再举一个例子:一个银行账户可以作为首记录,而属记录是这个账户的一笔笔的账,这些账可以是存款的、提款的或转账的,他们分属于不同的记录型。


多属系的优点是查找方便。


关于系的首记录和属记录几个注意的

在层次数据模型中,我们有说到虚记录,在网状数据模型中,我们不需要考虑虚记录,因为他并没有PCR关系,就拿下面这个例子来说:

image-20211002142839738

班和学生构成一个系,运动队和学生构成一个系,学生既是“班-学生”的属记录,也是“运动队-学生”的属记录,无须用PCR复制,无须用指针创建虚记录。


  • 一个记录型可以作为几个系的首记录,也可以作为几个系的属记录。

比如说:

“班级-学生”,“家庭-学生”和“学生-选课”这三个系中。

学生作为中转站,他可以做多个系的属纪录,也可以做另外多个系的主记录。

  • 一个记录型可以作为一个系的首记录,也可以作为另一个系的属记录
  • 但是!一个记录型不能做一个系的首记录,又做这个系的属记录

即:

“班级-学生”,你不能说学生又是属记录又是主记录。

或者再举一个例子:

职员和领导,职员是领导的下属,但是领导也是职员,也就是说在领导也在职员记录型里面,这显然是不符合要求的。

所以,我们在这里引入了一个叫做联系记录的概念。


什么是联系记录(link记录)?

比如职员和领导的关系,我们中间引入一个link变量,来接收职员的箭头,再把箭头指向领导。

这样就可以避免同一记录型的自身联系。

image-20211004023817363

这上面是系形(通俗地说)的定义,那系值(具体来说)怎么定义?

image-20211004023911708

Leo是雇员,Li也是雇员,雇员肯定不能自己指自己,为了表示Leo是Li的上级,就需要通过link记录来表示,Lin和Mary的情况也都如此。

这里要插入一个点,联系记录还有一个用法。由于一个记录值不能出现在同一系型的多个系值中,

image-20211002143357832

所以我们可以如图所示:

image-20211002143414976

其实看到上图我们可以知道,为了避免课4被两个学生用同一个记录,所以我们将每个课和link挂钩,用两个link来当课4的中转站。这样相当于是层次数据模型中的多元关系,


网状数据模型长什么样?

对比于层次数据模型,用班-学生的关系来说,如果是层次数据模型,那么就会是下图:

image-20211002143526626

如果是网状,那么他采用的不是树的形式,而是链表的形式:

image-20211002143550129

在这里要提到一个,链表用指针串联也是有讲究的,我们一般分为三类,前向指针后向指针首记录指针,其中前向指针是必备的。

前向指针:就是依次从根开始,依次指向“下”一个记录。

后向指针:和前向指针相反。

首记录指针:属记录指向首记录。


单值系和无首系

实际上,两个名词指的是同一个东西,这种系通常没有首记录,所以叫无首系,或者具体点说,一个单位里面有很多部门,但是单位只有一个,我们可以直接省略,所以无首系又叫做单值系。

再比如“班级-学生”,班级有很多个,我们不能说“班级-学生”是单值系,但是“学校-班级”中的学校只有一个,总而言之,单值系和无首系都是相对的,要视具体问题来看。


2.3、关系数据模型

关系数据模型是以集合论中的关系概念为基础发展起来的数据模型,他的基本数据结构是表,我们一般叫做关系。我们把现实世界的实体和实体之间的联系用表来表示。


关系模型具有如下特点

  1. 基于集合论,拥有很高的复杂等级

  2. 忽略很多底层的细节,简单又明了,方便理解。

  3. 可以引入代数系统

  4. 可以用非过程化的语言描述——SQL

  5. 软连接

之前我们说过层次和网状数据模型,他们都是通过指针表达现实世界实体集之间的联系,而关系数据模型不用,如果我们想表达两个(实体)表之间的联系,那我们只需要再做一张表,在这种表里体现刚刚那两张表的联系即可。


拿个表出来做示范先:

	+-------+----------+-----------+
	| ename | max(sal) | job       |
	+-------+----------+-----------+
	| SCOTT |  3000.00 | ANALYST   |
	| SMITH |  1300.00 | CLERK     |
	| JONES |  2975.00 | MANAGER   |
	| KING  |  5000.00 | PRESIDENT |
	| ALLEN |  1600.00 | SALESMAN  |
	+-------+----------+-----------+

这个表是员工表,这个写了三个字段,对应三个员工特征,我们一般把这种特征叫做属性(Attribute)

属性的值可以在一个定义域里面取到,比如说ename里面可以是合法姓名的集合,job可以是所有工作的集合,我们一般把属性值的定义域叫做属性的域(Domain)

这里要说明一下:数学里面有些域是无穷大或者无穷小的,在计算机里,数据都是离散的,长度都是有限的,所以属性的域可以看成是有限的集合。


域的其他限制

这就要分成两个年代来讲了。

在传统关系数据模型里,域是不允许再分的,即所有域都得是原子数据,比如是int,String,double,不可以是{(int),(String)}这种组合数据,如数组({1,2,3,4})、集合({1,a,b})、记录等等,你可以理解为不允许表中套表。但是之所以这样做是因为图简单,并不是一定不能这么做。这种比较传统的我们叫做遵守第一范式的传统关系数据模型,当然,有特殊需要时,我们可以选用不遵守第一范式的扩充关系数据模型。当然,这些都是后话,这里稍微提一下。


让我们看下面这个表

+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
+-------+--------+-----------+------+------------+---------+---------+--------+

在这个表里面,COMM里面有几个是NULL,我们说关系数据模型他是有条件地允许使用空缺符NULL的,记住NULL不是一个值,他只是说明这个地方没有东西,不是0。


关系和元组

	+-------+----------+-----------+
	| ename | max(sal) | job       |
	+-------+----------+-----------+
	| SCOTT |  3000.00 | ANALYST   |
	| SMITH |  1300.00 | CLERK     |
	| JONES |  2975.00 | MANAGER   |
	| KING  |  5000.00 | PRESIDENT |
	| ALLEN |  1600.00 | SALESMAN  |
	+-------+----------+-----------+

还是这个表,这里是个员工表,有三个属性,假设一个对象有n个属性,我们把n的个数叫做关系的目,如上图,员工表的关系的目是3。

关系是什么呢?一个关系里面包含所有的属性。或者通俗来讲,关系就是所有属性的集合。如上图,这是员工的关系,一个对象可以用一个或多个关系来描述。

不同属性是可以有相同的域的,比如出生年份和生日,他们的域都是有限集合的时间,但是虽然域相同,但是他们所指的意思不一样。

所以,我们通常把特征叫做属性,实例叫元组,所有的内容(也就是这张表)就做关系。关系用R或r(R)表示,他是n目元组的集合。

用上表来解释就是,R是三目元组的集合。

我们用A来表示属性,用Ai表示属性名,用D表示域,用R表示关系,用v表示对象,我们可以写出以下式子。

\[R = (A1/D1,A2/D2,…An/Dn)或者R = (A1,A2,…,An)\\ R = {t1,t2,…,tm}\\ t = <v1,v2,…,vn> \]

这里也要说明一个问题,在假定的R(A1,A2)中,A1和A2在数学逻辑上是不能对调的,但是在关系数据里面是可以对调的,也就是R(A1,A2)和R(A2,A1)是等价的。


关系和表的是等价的吗?之间有什么联系和区别?

从形式来看,关系就是表,不过关系对应的表就是个简单的二维表,不允许组合数据或者表中套表。并且,关系是元组的集合,集合三大特点(无序性,确定性,互异性)中的无序性里面就说明了,关系中的元组可以互换,即上表中的SCOTT和SMITH互换一下,完全不影响。互异性说明了元组一定是不同的。所以,关系就是个加了限制的表。


元组和属性

我们看回这张表

	+-------+----------+-----------+
	| ename | max(sal) | job       |
	+-------+----------+-----------+
	| SCOTT |  3000.00 | ANALYST   |
	| SMITH |  1300.00 | CLERK     |
	| JONES |  2975.00 | MANAGER   |
	| KING  |  5000.00 | PRESIDENT |
	| ALLEN |  1600.00 | SALESMAN  |
	+-------+----------+-----------+

每个属性都是每个列的列名,除了属性名外;总共有五个元组,所以我们习惯性地叫属性叫做列,元组叫做行,元组可以看做是一个对象在关系上的实例。

有时候我们取某个元组的其中一些属性,这一些属性的子集我们叫做关系在这些属性上的投影,表示R[X]或者t[X],其中X∈[A1,A2,…,An]。


这里就要提到好几个概念了,我们用一堆例子来解释这几个概念。


候选键

含义:候选键也叫键,通俗来讲他是某个属性或属性组(多个属性),通过这个属性我们可以唯一地确定某一个元组。

范例:

+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
		…
+-------+--------+-----------+------+------------+---------+---------+--------+

说明:在这个表中,EMPNO代表员工的编号,确定了这个编号,基本上这个员工的其他信息就确定了,但是如果你选ENAME,你并不能保证员工的名字不会重复,所以,我们叫EMPNO为候选键(键),而员工名不是。

注意:

  1. 候选键至少有一个,可以有多个。

  2. 候选键的真子集不能包含候选键。


主属性

候选键包含的属性就叫主属性。


非主属性

不包含候选键的就是非主属性。


超键

还是上面的例子,EMPNO可以唯一确定一条元组,但是{EMPNO,ENAME}也可以,但是需要说明的是,他不符合候选键的真子集不能包含候选键这个性质,这种属性组合我们一般有个名字给他,叫做超键。有些书叫超码或者超值。


主键

拥有多个候选键的时候,在里面挑一个作为主键,其他作为候补键(这里不是候选哦,是候补哦,看好字),有些书叫做主码,关键值。


外键

用这个关系1(表)里的某个键,可以引用(寻找)其他关系(表)中的一条元组,即可以引用其他表的主键,那我们叫这个其他表的主键叫外键。有些书叫做外码或外值。

		mysql> select empno,ename from emp;(表1)
		+-------+--------+
		| empno | ename  |
		+-------+--------+
		|  7369 | SMITH  |
		|  7499 | ALLEN  |
		……
		+-------+--------+
		
		mysql> select empno,job from emp;(表2)
		+-------+-----------+
		| empno | job       |
		+-------+-----------+
		|  7369 | CLERK     |
		|  7499 | SALESMAN  |
		…
		+-------+-----------+

说明:通过表一的empno,我们可以找到表2的对应的其他属性的值,并且,empno在表2担任主键,所以我们称两张表的empno为外键。

注意:外键实际上是一个逻辑指针,也就是表1的empno通过表2empno的指针(地址)去寻找对应的元组,所以,指针是必须非空的,即empno必须非空。


2.4、关于键的约束问题

一、域完整性约束

简而言之,你的属性值必须得是域中的值。比如说,学号是int类型的,你把它的值填了个char的,一定会报错。


二、实体完整性约束

每个关系都应该有一个主键,每个元组的主键的值都是唯一地,主键的值不能为NULL,否则无从区别和识别元组。


第三章、关系代数

3.1、概述

关系数据模型中提供了一系列操作的定义,这些操作叫做关系代数操作,简称关系操作。

关系操作一般包含五个基本操作,如果一个系统或者数据库产品包含这五个基本操作,那我们认为认为这个系统的功能是完备的。

任何的操作都可以通过这五个操作来组合完成。

\[select operation选择操作σ \]

\[project operation投影操作π \]

\[Cross product笛卡尔乘积× \]

\[Set - diffrent集合差操作− \]

\[union 集合并操作∪ \]

事实上,目前流行的关系数据库中,除了满足关系完备性以外,还增加了不少关系代数所不支持的操作,如排序、分组、聚集函数,甚至是传递闭包的计算。


3.2、选择操作

选择操作,实际上就是加限制条件来选元组,他是一种一元关系操作。

例如:

	mysql> select* from emp;
	+-------+--------+-----------+------+------------+---------+---------+--------+
	| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
	+-------+--------+-----------+------+------------+---------+---------+--------+
	|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
	|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
	|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
	|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
	|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
	|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
	|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
	|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
	|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
	|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
	|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
	|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
	|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
	|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
	+-------+--------+-----------+------+------------+---------+---------+--------+

【说明:这是完整的一张表,现在我们要找出表中符合工资为1500到3000的元组。】

	mysql> select* from emp where sal>=1500&&sal<=3000 order by sal;
	//说明:找出表中符合工资为1500到3000的元组,按照升序排列。
	
	+-------+--------+----------+------+------------+---------+--------+--------+
	| EMPNO | ENAME  | JOB      | MGR  | HIREDATE   | SAL     | COMM   | DEPTNO |
	+-------+--------+----------+------+------------+---------+--------+--------+
	|  7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 |   0.00 |     30 |
	|  7499 | ALLEN  | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 |     30 |
	|  7782 | CLARK  | MANAGER  | 7839 | 1981-06-09 | 2450.00 |   NULL |     10 |
	|  7698 | BLAKE  | MANAGER  | 7839 | 1981-05-01 | 2850.00 |   NULL |     30 |
	|  7566 | JONES  | MANAGER  | 7839 | 1981-04-02 | 2975.00 |   NULL |     20 |
	|  7788 | SCOTT  | ANALYST  | 7566 | 1987-04-19 | 3000.00 |   NULL |     20 |
	|  7902 | FORD   | ANALYST  | 7566 | 1981-12-03 | 3000.00 |   NULL |     20 |
	+-------+--------+----------+------+------------+---------+--------+--------+

当然,在学选择操作时,我们要学会写这个符号。

如果要求把上面的要求用选择操作表达的话,即\(\sigma_{sal>1500 and sal <300}(emp)\)


3.3、投影操作

投影是一元关系操作,投影就是选取关系中的某些列,即选取某些属性。

比如:

	+-------+--------+-----------+------+------------+---------+---------+--------+
	| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
	+-------+--------+-----------+------+------------+---------+---------+--------+
	|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
	|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
	|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
	……
	+-------+--------+-----------+------+------------+---------+---------+--------+

【说明:这是一整张表,但是我现在只对这个表中员工的名字和员工的编号感兴趣。】

	mysql> select empno,ename from emp;
	+-------+--------+
	| empno | ename  |
	+-------+--------+
	|  7369 | SMITH  |
	|  7499 | ALLEN  |
	|  7521 | WARD   |
	……
	+-------+--------+

【说明:这时候我只选取了某些属性来看,即选取了某些列,这就是投影。】


这里有几个需要注意的点:

  1. 投影后的表由于有可能不包含候选键,这就会导致有些元组可能是重复的,这时候系统会自动消除重复元组,所得关系(表)的元组数小于原关系的元组数。如果投影后的表包含候选键,那么元组数和投影前一样。

  2. 在实际的数据库产品中,数据库投影后他是不会自动去重的,因为数据库他不能保证用户对重复的数据是否有应用价值,所以,除非你要求,不然他不会自动去重。


当然,在学投影操作时,我们要学会写这个符号。

如果要求把上面的要求用投影操作表达的话,即\(\Pi_{empno,ename}(emp)\)


3.4、集合操作

集合操作就是三个:

并、交、差和笛卡尔乘积

首先先说笛卡尔乘积:

在表的连接查询方面有一种现象被称为:笛卡尔积现象(笛卡尔乘积现象)


案例:找出每一个员工的部门名称,要求显示员工名和部门名

下面我们验证下连接查询的原理:

mysql> select ename,dname from emp,dept;
		
		+--------+------------+
		| ename  | dname      |
		+--------+------------+
		| SMITH  | ACCOUNTING |
		| SMITH  | RESEARCH   |
		| SMITH  | SALES      |
		| SMITH  | OPERATIONS |
		| ALLEN  | ACCOUNTING |
		| ALLEN  | RESEARCH   |
		| ALLEN  | SALES      |
		………………………
		+--------+------------+
共56条记录

笛卡尔积现象:当两张表进行连接查询的时候,没有任何条件进行限制,最终的查询结果条数是两张表记录条数的乘积。


并交差操作

其实这个操作和数学上面的定义是一样的,但是他在数据库里面有限制。这里就要引出一个概念叫做并兼容限制。


并兼容限制:参与并交差的两个关系的元组必须限制为同类型,也就是相同目,对应的属性的域也相同。


2.5、连接操作

连接操作是一个二元关系操作。

连接其实和笛卡尔乘积很像,连接实际上就是一个加了条件的笛卡尔乘积,他只找出那些满足条件的元组。

案例:要求每个员工的工资等级,要求显示员工名、工资、工资等级。

首先先把员工表的员工名和工资拿出来:

		mysql> select ename,sal from emp;
		+--------+---------+
		| ename  | sal     |
		+--------+---------+
		| SMITH  |  800.00 |
		| ALLEN  | 1600.00 |
		| WARD   | 1250.00 |
		| JONES  | 2975.00 |
		| MARTIN | 1250.00 |
		| BLAKE  | 2850.00 |
		| CLARK  | 2450.00 |
		| SCOTT  | 3000.00 |
		| KING   | 5000.00 |
		| TURNER | 1500.00 |
		| ADAMS  | 1100.00 |
		| JAMES  |  950.00 |
		| FORD   | 3000.00 |
		| MILLER | 1300.00 |
		+--------+---------+

然后再找出工资等级表;

		mysql> select * from salgrade;
		+-------+-------+-------+
		| GRADE | LOSAL | HISAL |
		+-------+-------+-------+
		|     1 |   700 |  1200 |
		|     2 |  1201 |  1400 |
		|     3 |  1401 |  2000 |
		|     4 |  2001 |  3000 |
		|     5 |  3001 |  9999 |
		+-------+-------+-------+

拿史密斯来说,如果不加任何限制条件,那么两张表会做笛卡尔乘积,当你拿了史密斯之后,史密斯的薪资只有在工资等级的最底层,所以找表的时候,史密斯只能匹配下表的第一行。

	mysql> select
	    -> e.ename,e.sal,s.grade
	    -> from
	    -> emp e
	    -> (inner)join
	    -> salgrade s
	    -> on
	    -> e.sal between s.losal and s.hisal;
	+--------+---------+-------+
	| ename  | sal     | grade |
	+--------+---------+-------+
	| SMITH  |  800.00 |     1 |
	| ALLEN  | 1600.00 |     3 |
	| WARD   | 1250.00 |     2 |
	| JONES  | 2975.00 |     4 |
	| MARTIN | 1250.00 |     2 |
	| BLAKE  | 2850.00 |     4 |
	| CLARK  | 2450.00 |     4 |
	| SCOTT  | 3000.00 |     4 |
	| KING   | 5000.00 |     5 |
	| TURNER | 1500.00 |     3 |
	| ADAMS  | 1100.00 |     1 |
	| JAMES  |  950.00 |     1 |
	| FORD   | 3000.00 |     4 |
	| MILLER | 1300.00 |     2 |
	+--------+---------+-------+

实际上,根据连接的条件,我们可以分为以下的连接:

  • 内连接

    • 等值连接
    • 非等值连接
    • 自连接
  • 外连接

    • 左外连接(左连接)
    • 右外连接(右连接)
  • 全连接(很少见)


而每个条件的普遍形式我们都可以表示为:\(A_iθB_j\)

其中\(A_i\)是R表的一个属性,\(B_j\)是S表的一个属性,他和\(A_i\)对应,θ是运算符的集合(包括加减乘除大于小于不等于等),这种连接我们通常叫θ连接


2.6、除操作

除法操作不好用定义来解释,看下图。

比如:

image-20211002151226452

在<a1,b1>里和<a2,b2>里,明显对应的R.3和R.4都包含了S表,所以{<a1,b1>,<a2,b2>}就是除法的结果。其他不含S表的<a3,b3>可以看做结果的余数。


如何用五个基本操作表达除法?


2.7、外连接操作

详见5.8.6,这里不过多累述


2.8、外并操作

外并操作?

针对:针对那些不符合并兼容要求的。

结果:结果的属性是两表属性的总和,如果其他表没有另外一个表的属性,那么那一列会被填上NULL。


第四章、关系演算

4.1、概述

概念:除了用关系代数表示关系操作外,还可以用谓词演算来表达关系的操作,这称为关系演算。


和关系代数的区别?

关系代数表示关系的操作,须标明关系操作的次序,哪个操作先哪个后你要说清楚,所以以关系代数为基础的数据库语言是过程语言。用关系演算表示关系的操作,只要说明所要得到关系的结果,而不必标明操作的过程,因而他是非过程的。


分类:元组关系演算和域关系演算。


4.2、元组关系演算

元组关系演算就是以元组为变量,一般形式是:

\[{t[<属性表>]|P(t)} \]

利用关系演算是可以做关系代数的所有操作的。


案例:查询江苏籍女大学生的姓名。

\[{t[姓名]|t∈STUDENT AND t.性别 = '女' AND t.籍贯 = '江苏'} \]

元组关系演算与关系代数具有同等表达能力,也是关系完备的。用谓词演算表示关系操作时,只有结果是有限集才有意义。

也就是说,一个表达式的结果如果是有限的,我们叫做表达式是安全的。

反之,我们说他是不安全的。

例如{t|t∉STUDENT}。

宇宙中不属于学生的东西是无限的,这个表达式是不安全的。


4.3、域关系演算

域关系演算一般以域为变量。

一般形式为:

\[{<x1,x2,…,xn>|P(x1,x2,…x(n+m)} \]

这样说我们可能有点费解。举个例子:

假设现在有个表

mysql> select* from emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
……
+-------+--------+-----------+------+------------+---------+---------+--------+

其中有很多域变量,比如sal里面,有300,500等等,但是我们现在要找一个工资为500-3000的,那么他会找出里面符合条件的,其他的舍弃掉。所以,查询的域结果是原域结果的真子集。

所以不难看出,域关系演算也是完备的,实际上这个案例和选择操作很像。

\[{<x1,x2,…,xn>|P(x1,x2,…x(n+m)} \]

在这里面的P实际上被称为原子公式

原子公式是什么?假如说\(<x_1,x_2,…,x_n>∈R\),我们称他为元组公式,并且,XopY,或者Xop常量都是原子公式。

op is one of <,>,=,<=,>=,!=


公式

一个原子公式,或者否定P,p∩q,p∪q也叫公式。

\[∃X(p(X)),其中变量X是一个自由变量或者∀X(p(X)),其中变量X是一个自由变量,那我们也称其为一个公式。 \]

自由变量:\(自由变量是指没有被量词(∃、∀)绑定的变量。\)


4.4、对传统数据模型的评价

一般来说,我们把层次,网状和关系数据模型称为传统数据模型。


传统数据模型有四个弱点

1、以记录为基础,不能很好地面向用户和应用。

比如衣服,有一个衣服的表,里面有个属性是衣服的袖口类型,但是在现实世界中有很多连袖口都没有的衣服,虽然有NULL可以作为填补,但是用NULL是迫不得已,并不是一种自然的表示。

2、不能以自然的方式表示实体之间的联系。

有些联系本来不是那种PCR关系,不是链表关系,但是我们还是要引入什么link,什么虚记录来表示这些关系,这些表示是很不自然的。

3、语义贫乏。

比如你定义了一个表,表里面有学生的性别、年龄、身高、体重,但是你却不知道他们本身代表什么意思,我可以说这是学生的医疗记录,也可以说这是入学信息报告。还有表与表直接的联系,由于没有标明联系,有时候用户还需要查帮助文档和一些文件才能了解。所以,由于语义不明,导致就算语义理解错了也要用户自己负责。

4、数据类型太少,难以满足应用需要。


4.5、E-R数据模型

E-R数据模型,也叫实体联系数据模型。


提出目的

  1. 企图建立一个统一的数据模型,以概括三种传统的数据模型;

  2. 作为三种传统数据模型互相转换的中间概念

  3. 作为超脱DBMS的一种概念数据模型,以比较自然的方式模拟现实世界。


三个概念:实体,属性,联系

实体

概念:凡是可以被人互相区别的东西就叫实体。

范例:飞机,春游,神,梦。

注意:同类实体我们可以归为一类,称为实体集

范例:用E代表学生的实体集,那么E = {e|e是学生}


属性

概念:实体的特征叫实体的属性,属性取值的范围叫值集。值集的说法相当于关系数据模型里的属性域。


联系

概念:实体之间(也包括和实体集)的各种关系我们称之为联系。

范例:某个学生(实体)和课程(实体)之间有选课关系。学生(实体集)和课程之间的选课关系。人与人直接有领导关系,夫妻关系。

表示方法:如果实体e1,实体e2之间有联系,我们用<e1,e2>来表示,扩展到n个实体,我们用<e1,e2…,en>来表示,当n>2的时候,我们称为多元联系。


联系集

概念:比如说夫妻是一种联系,那么具体的比如:<e1,e2>两个人就是这个联系的实例,生活中是夫妻的不止一对,所以多个实例我们用集合包括起来,称为联系集。


有时为了表明实体在联系中的作用,我们会这么表示,以夫妻为例:

\[<r1/e1,r2/e2,…,rn/en> \]

其中r表示哪个是夫,哪个是妻。

说明:联系虽然用元组表示,但实体间的次序不是重要的,尤其标明作用后,他们的次序可以任意。


E-R图

用E-R数据模型对一个单位的模拟,称为一个单位的E-R数据模式。E-R数据模式可以用非常直观地E-R**图来表示。

image-20211002153926103

其中矩形框代表实体,菱形代表联系,椭圆代表属性,双线矩形框框代表弱实体


弱实体是什么?

弱实体实际上可以当成实体,可以当成属性,由数据库库设计者决定。

范例:比如一个职工,他的家属实体集可能有姓名,出生日期,和职工的关系这些属性,但是家属的姓名是可以重复的。什么意思呢,简而言之就是家属实体集中的表没有键(没有实体键),导致失去了辨认家属的能力了,所以一般来说弱实体可以不做实体,只当做拥有这个实体集的那个实体(所有者实体)的组合属性。


E-R图相关约束

基数比约束

比如联系有1:1,1:N,N:1,M:N。我们把这个数量上实体间的约束称为基数比约束。

范例:一个员工只能在一个工作部门工作,这就是1:1。


参与度约束

比如这个银行卡账号你最多只能取钱5次,比如这门课最多容纳学生150人,这就是参与度约束。

表达方式:(min,max)


基数比约束和参与度约束构成联系约束。


4.5.1、重要题型总结

一、如何画E-R图

步骤总结:

  1. 找出实体并用矩形表示
  2. 找出实体的属性用椭圆表示
  3. 找出实体间的关系,用菱形表示

image-20211230212433335

image-20211230220040718


二、E-R图转关系模式

步骤总结:

  1. 实体转换为一个关系模式

  2. 实体的属性就是关系的属性,实体的码就是关系的码

  3. 实体间联系的转换

    • 1:1联系:在任意一方加入对方的主码变为外码,并加入联系本身的属性。

    • 1:n联系:将1方的主键加入n方作为其外键,并同时将联系的属性也加入n方。

    • m:n联系:将联系本身本身转化为一个关系模式,联系双方的主码加入其中,并将联系的属性加入其中。

image-20211230212450984


image-20211230220054757


4.6、扩充E-R数据模型

扩充E-R数据模型我们通常称为EER。

EER引入以下的几个概念:弱实体、特殊化和普遍化、聚集、范畴。

扩充E-R数据模型我们通常称为EER。

EER引入以下的几个概念:弱实体、特殊化和普遍化、聚集、范畴。


  • 弱实体(Weak entity)

这个上一节已经说过了。


  • 特殊化和普遍化(Specialization and Generalization)

举个例子你就明白了。

范例:从普遍到特殊叫特殊化,如学生是普遍,划分为研究生高中生本科生是特殊,研究生还可以继续划分为学术型研究生和专业性研究生。反之叫普遍化。

对比:和java中的继承很像

概念:

image-20211007134322741

说明:全特殊化可以理解为等价,部分特殊化可以理解为就是我们口中说的特殊化。


图例:带U符号线表示特殊化,圆圈d表示不相交特殊化,圆圈o表示重叠特殊化,线上带一条线表示实体键。

image-20211007134012615
  • 聚集(Aggregation)

就是把实体集和联系看成新的一个实体集,新的实体集中的属性是原来实体集的属性和联系的属性的并。

范例:

image-20211007134053250
  • 范畴(Category)

在模拟现实世界时,有时候要用到不同类型的实体组成的实体集。我们把这类实体集叫做范畴。

范例:车主有可能是一个人,有可能是一个企业的单位。

表达方式:T(E1,E2,E3,…,En)

说明:其中里面的不同的实体集我们叫做超实体集,例如E1是超实体集。

图例:圆圈∪代表并操作。

image-20211007134142274

这里有一点需要说明一下,我们前面说过特殊化,特殊化也就是一个子类对于父类的继承,并且继承了所有的属性,但是范畴不一样。拿这个例子来说,单位和人构成范畴,但是这个账户不是说既是单位又是人的,他的继承具有选择性,如果账户是单位的,则继承单位的属性,是个人的继承个人的属性,这叫选择性继承

反观上面特殊化图的这个部分

image-20211007134215252

在职进修生既是教职工,也是学生,他拥有两者的属性。


第五章、关系数据库语言

这里稍微提一下。我们最开始先讲MySQL。

5.1、MySQL简介

SQL的发展历程

1986年发布了SQL-86

1989年发布了SQL-89

然后发布了92、99、03、06、08


什么是数据库的用户接口?

用户使用数据库时对数据库进行的操作,DBMS要想方设法满足,其提供的命令和语言就叫用户和数据库的接口。

DBMS提供多种用户接口来针对不同的适用人群。


接口包括什么?

  • 查询语言

  • 图形化工具(GUI)

  • APIs

  • 类库


什么是数据库语言?

DBMS所提供的语言一般局限于对数据库的操作,有别于计算完备的程序设计语言,称为数据库语言。

也就是说:数据库语言不是程序设计语言,不能编程,不能进行高难度的计算。


SQL、DB、DBMS分别是什么?他们之间的关系?

DB:DataBase(数据库,数据库实际上在硬盘上以文件的形式存在)

DBMS: DataBase Managemeng System(数据库管理系统,常见的有:MySQL Oracle DB2 Sybase sqlServer。。。)


SQL

  • 结构化查询语言,是一门标准通用的语言。标准的sql适合于所有的数据库产品。
  • SQL属于高级语言,只要能看得懂英语单词的,写出来的sql语句,可以读懂什么意思。
  • SQL语句在执行的时候,实际上内部也会进行编译,然后再执行sql(sql语句的编译由DBMS完成。)
  • SQL是一个非过程化的查询语言

学习MySQL主要还是学习通用的SQL语句,那么SQL语句包括增删改查,sql语句该怎么分类呢?

SQL按其功能可以分为四大部分:

  • DDL:数据定义语言,用于定义、撤销和修改数据模式,creat、drop、alter。

  • DQL:查询语句,凡是select语句都是DQL。

  • DML:用于增删改数据,insert、delete、update。

  • DCL:用于数据访问权限的控制。grant授权,revoke撤销权限。

  • TCL:commit提交事务,rollback回滚事务。


5.2、查询语句基本结构

基表和虚表

基表是真真实实存在的,他的数据显式地存储在数据库中,或者换一种说法就是,你当时存的时候什么样基表就长什么样。

而虚表是仅有逻辑定义,可以根据其定义从其他表(包括视图)中导出,但不作为一个表显式地存储在数据库中。换一种说法就是,比如你数据库里面已经有个基表了,然后我通过某些要求过滤了一些条件,查询出来的表就是虚表,虚表实际上不存在数据库里,他只是通过一些计算和逻辑语言提取出来的。

当基表的模式修改时,通过定义适当的视图,仍可以为用户提供修改前的数据模式,避免修改应用程序,从而有利于提高数据的逻辑独立性。也就是说,即使你基表改了,但是为了视图还是和以前一样,我们可以在基表的基础上做一些其他的操作,使他改变操作后算出来的虚表和之前没改的虚表一模一样。


NULL

代表空值,但是他不是一个值,不等于0,而是代表这里没有值。在以前我们是二值逻辑,即要么true要么false,引入NULL后,我们由二值逻辑变为三值逻辑。

NOT NULL

不允许对应的属性值为空。

Unique

不允许属性值重复,即列值不能重复。

DEFAULT

如果属性值为空,则填个默认值上去。

一般默认值有三种情况:

第一种是事先定义好的字符,比如如果为空就补个0上去。

第二种是置为空格字符串,把NULL变成长度为0的字符串或者变成某一个不可能出现的值。

第三种就是变成NULL,如果你要这么做,那你 前面不能再加一个NOT NULL不然语义矛盾。

CHECK

利用他,可以说明属性值的范围,也就是加限制条件,比如人的年龄不可能为负数,你可以加age>0。


5.3、数据库概述及数据准备

这里下面的笔记由于要使用到下列的数据库,特此附加

DROP TABLE IF EXISTS EMP;
DROP TABLE IF EXISTS DEPT;
DROP TABLE IF EXISTS SALGRADE;

CREATE TABLE DEPT
       (DEPTNO int(2) not null ,
	DNAME VARCHAR(14) ,
	LOC VARCHAR(13),
	primary key (DEPTNO)
	);
CREATE TABLE EMP
       (EMPNO int(4)  not null ,
	ENAME VARCHAR(10),
	JOB VARCHAR(9),
	MGR INT(4),
	HIREDATE DATE  DEFAULT NULL,
	SAL DOUBLE(7,2),
	COMM DOUBLE(7,2),
	primary key (EMPNO),
	DEPTNO INT(2) 
	)
	;

CREATE TABLE SALGRADE
      ( GRADE INT,
	LOSAL INT,
	HISAL INT );

//插入数据
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
10, 'ACCOUNTING', 'NEW YORK'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
20, 'RESEARCH', 'DALLAS'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
30, 'SALES', 'CHICAGO'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
40, 'OPERATIONS', 'BOSTON'); 
commit;
 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7369, 'SMITH', 'CLERK', 7902,  '1980-12-17'
, 800, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7499, 'ALLEN', 'SALESMAN', 7698,  '1981-02-20'
, 1600, 300, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7521, 'WARD', 'SALESMAN', 7698,  '1981-02-22'
, 1250, 500, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7566, 'JONES', 'MANAGER', 7839,  '1981-04-02'
, 2975, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7654, 'MARTIN', 'SALESMAN', 7698,  '1981-09-28'
, 1250, 1400, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7698, 'BLAKE', 'MANAGER', 7839,  '1981-05-01'
, 2850, NULL, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7782, 'CLARK', 'MANAGER', 7839,  '1981-06-09'
, 2450, NULL, 10); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7788, 'SCOTT', 'ANALYST', 7566,  '1987-04-19'
, 3000, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7839, 'KING', 'PRESIDENT', NULL,  '1981-11-17'
, 5000, NULL, 10); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7844, 'TURNER', 'SALESMAN', 7698,  '1981-09-08'
, 1500, 0, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7876, 'ADAMS', 'CLERK', 7788,  '1987-05-23'
, 1100, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7900, 'JAMES', 'CLERK', 7698,  '1981-12-03'
, 950, NULL, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7902, 'FORD', 'ANALYST', 7566,  '1981-12-03'
, 3000, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7934, 'MILLER', 'CLERK', 7782,  '1982-01-23'
, 1300, NULL, 10); 
commit;
 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
1, 700, 1200); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
2, 1201, 1400); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
3, 1401, 2000); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
4, 2001, 3000); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
5, 3001, 9999); 
commit;

5.3.1、常用命令

查看当前使用的是哪个数据库?

select database();

查询数据库版本也可以使用

select version();

结束一条语句?

\c

退出mysql?

exit

查看创建表的语句

show create table emp;

5.3.2、导入数据

第一步:登录mysql数据库管理系统。

第二步:查看有哪些数据库

show databases;(这个不是sql语句,属于MySQL命令)

第三步:创建属于我们自己的数据库

Create database bjpowernode;(这个不是sql语句,属于MySQL命令)

第四步:使用bjpowernode数据

Use bjpowernode;(这个不是sql语句,属于MySQL命令)

第五步:查看当前使用的数据库有哪些表?

Show tables;

第六步:初始化数据

Mysql>source D:\MySQL\bjpowernode.sql


注意:数据初始化完成之后,有三张表:

	mysql> show tables;
	+-----------------------+
	| Tables_in_bjpowernode |
	+-----------------------+
	| dept                  |
	| emp                   |
	| salgrade              |
	+-----------------------+
	3 rows in set (0.00 sec)

5.3.3、SQL脚本

一、bjpowernode.sql

这个文件以sql结尾,这样的文件被称为“sql脚本”。

当一个文件的扩展名是.sql,并且该文件中编写了大量的sql语句,我们称这样的文件为脚本。


注意:直接使用source命令可以执行sql脚本。


sql脚本中的数据量太大的时候,无法打开,请使用source命令初始化。


5.4、简单的查询

5.4.1、查表操作

一、删除数据库:drop database bjpowernode;


二、查看表结构

+-----------------------+

| Tables_in_bjpowernode |

+-----------------------+

| dept         | (部门表)

| emp          | (员工表)

| salgrade       | (工资等级表)

+-----------------------+

desc (其中写表的名字即可查看结构)

如:desc dept;

+--------+-------------+------+-----+---------+-------+

| Field | Type    | Null | Key | Default | Extra |

+--------+-------------+------+-----+---------+-------+

| DEPTNO | int(2)   | NO  | PRI | NULL  |    |(部门编号)

| DNAME | varchar(14) | YES |   | NULL  |    |(部门名称)

| LOC  | varchar(13) | YES |   | NULL  |    |   (部门位置)

+--------+-------------+------+-----+---------+-------+

表名称:emp

描述:员工信息表

英文字段名称 中文描述 类型
EMPNO 员工编号 INT (4)
ENAME 员工姓名 VARCHAR(10)
JOB 工作岗位 VARCHAR(9)
MGR 上级领导 INT (4)
HIREDATE 入职日期 DATE
SAL 薪水 DOUBLE(7,2)
COMM 津贴 DOUBLE (7,2)
DEPTNO 部门编号 INT(2)

注:DEPTNO字段是外键,DEPTNO的值来源于dept表的主键,起到了约束的作用


表名称:salgrade

描述:薪水等级信息表

英文字段名称 中文描述 类型
GRADE 等级 INT
LOSAL 最低薪水 INT
HISAL 最高薪水 INT

三、表中的数据?

利用select*from 表名即可查看某表的所有数据

mysql>select*from emp;

+-------+--------+-----------+------+------------+---------+---------+--------+

| EMPNO | ENAME | JOB    | MGR | HIREDATE  | SAL   | COMM  | DEPTNO |

+-------+--------+-----------+------+------------+---------+---------+--------+

| 7369 | SMITH | CLERK   | 7902 | 1980-12-17 | 800.00 |  NULL |   20 |

| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 |   30 |

| 7521 | WARD  | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 |   30 |

| 7566 | JONES | MANAGER  | 7839 | 1981-04-02 | 2975.00 |  NULL |   20 |

| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 |   30 |

| 7698 | BLAKE | MANAGER  | 7839 | 1981-05-01 | 2850.00 |  NULL |   30 |

| 7782 | CLARK | MANAGER  | 7839 | 1981-06-09 | 2450.00 |  NULL |   10 |

| 7788 | SCOTT | ANALYST  | 7566 | 1987-04-19 | 3000.00 |  NULL |   20 |

| 7839 | KING  | PRESIDENT | NULL | 1981-11-17 | 5000.00 |  NULL |   10 |

| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 |  0.00 |   30 |

| 7876 | ADAMS | CLERK   | 7788 | 1987-05-23 | 1100.00 |  NULL |   20 |

| 7900 | JAMES | CLERK   | 7698 | 1981-12-03 | 950.00 |  NULL |   30 |

| 7902 | FORD  | ANALYST  | 7566 | 1981-12-03 | 3000.00 |  NULL |   20 |

| 7934 | MILLER | CLERK   | 7782 | 1982-01-23 | 1300.00 |  NULL |   10 |

+-------+--------+-----------+------+------------+---------+---------+--------+

mysql> select*from dept;

+--------+------------+----------+

| DEPTNO | DNAME   | LOC   |

+--------+------------+----------+

|   10 | ACCOUNTING | NEW YORK |

|   20 | RESEARCH  | DALLAS  |

|   30 | SALES   | CHICAGO |

|   40 | OPERATIONS | BOSTON  |

+--------+------------+----------+

mysql> select*from salgrade;

+-------+-------+-------+

| GRADE | LOSAL | HISAL |

+-------+-------+-------+

|   1 |  700 | 1200 |

|   2 | 1201 | 1400 |

|   3 | 1401 | 2000 |

|   4 | 2001 | 3000 |

|   5 | 3001 | 9999 |

+-------+-------+-------+

5.4.2、DQL查询语句

语法格式

Select 字段名1,字段名2,字段名3,…from 表名;


提示

1、任何一条sql语句都以";"结尾。

2、sql语句不区分大小写


案例:查询员工的年薪?(字段可以参与数学运算)

	mysql> select ename,sal*12 from emp;
	
	+--------+----------+
	| ename  | sal*12   |
	+--------+----------+
	| SMITH  |  9600.00 |
	| ALLEN  | 19200.00 |
	| WARD   | 15000.00 |
	| JONES  | 35700.00 |
	| MARTIN | 15000.00 |
	| BLAKE  | 34200.00 |
	| CLARK  | 29400.00 |
	| SCOTT  | 36000.00 |
	| KING   | 60000.00 |
	| TURNER | 18000.00 |
	| ADAMS  | 13200.00 |
	| JAMES  | 11400.00 |
	| FORD   | 36000.00 |
	| MILLER | 15600.00 |
	+--------+----------+

看着sal*12 难受,想重命名怎么办?

mysql> select ename,sal*12 as yearsal from emp;

注意:as可以省略!

mysql> select ename,sal*12 yearsal from emp;
	+--------+----------+
	| ename  | yearsal  |
	+--------+----------+
	| SMITH  |  9600.00 |
	| ALLEN  | 19200.00 |
	| WARD   | 15000.00 |
	| JONES  | 35700.00 |
	| MARTIN | 15000.00 |
	| BLAKE  | 34200.00 |
	| CLARK  | 29400.00 |
	| SCOTT  | 36000.00 |
	| KING   | 60000.00 |
	| TURNER | 18000.00 |
	| ADAMS  | 13200.00 |
	| JAMES  | 11400.00 |
	| FORD   | 36000.00 |
	| MILLER | 15600.00 |
	+--------+----------+	

别名想起中文怎么办?

mysql> select ename,sal*12 as "年薪" from emp;
	+--------+----------+
	| ename  | 年薪        |
	+--------+----------+
	| SMITH  |  9600.00 |
	| ALLEN  | 19200.00 |
	| WARD   | 15000.00 |
	| JONES  | 35700.00 |
	| MARTIN | 15000.00 |
	| BLAKE  | 34200.00 |
	| CLARK  | 29400.00 |
	| SCOTT  | 36000.00 |
	| KING   | 60000.00 |
	| TURNER | 18000.00 |
	| ADAMS  | 13200.00 |
	| JAMES  | 11400.00 |
	| FORD   | 36000.00 |
	| MILLER | 15600.00 |
	+--------+----------+

注意:这里用中文单引号和双引号都可以,但是建议单引号,因为单引号其他数据库也通用,不然你这里的数据库可以用其他不能用就会导致很尴尬。


查询所有字段?

select*from emp;

注意:实际开发中不建议用*,效率较低。


5.4.3、去重操作

关于查询结果集的去重?

	mysql> select distinct job from emp;
	
	+-----------+
	| job       |
	+-----------+
	| CLERK     |
	| SALESMAN  |
	| MANAGER   |
	| ANALYST   |
	| PRESIDENT |
	+-----------+

但是不能像下面这么写:

mysql>select ename,distinct job from emp;

以上的sql语句是错误的,为什么?因为job是去重了,但是ename没有去重,导致两条属性值结果不一样多。

记住:distinct只能出现在所有字段的最前面。

mysql> select distinct deptno,job from emp;
注意:distinct出现在最前面表示后面的字段联合起来去重。
+--------+-----------+
| deptno | job       |
+--------+-----------+
|     20 | CLERK     |
|     30 | SALESMAN  |
|     20 | MANAGER   |
|     30 | MANAGER   |
|     10 | MANAGER   |
|     20 | ANALYST   |
|     10 | PRESIDENT |
|     30 | CLERK     |
|     10 | CLERK     |
+--------+-----------+

案例:统计岗位的数量?

mysql> select count(distinct job) from emp;
+---------------------+
| count(distinct job) |
+---------------------+
|                   5 |
+---------------------+

5.5、条件查询


5.5.1、条件查询格式

语法格式:

Select

字段,字段……

From

表名

Where

条件;


例如:查询工资待遇5000的员工姓名?

mysql> select ename from emp where sal = 5000 ;
	+-------+
	| ename |
	+-------+
	| KING  |
	+-------+

例如:想smith的工资?

mysql> select sal from emp where ename = 'smith';
	+--------+
	| sal    |
	+--------+
	| 800.00 |
	+--------+

注意:不只是中文要加单引号,只要是字符串都要加单引号,不知道是不是字符串可以用desc去查询表的结构。


例如:找出工资高于3000的员工?

mysql> select ename from emp where sal>3000;
	
	+-------+
	| ename |
	+-------+
	| KING  |
	+-------+

例如:找出工资不等于3000的?

mysql> select ename from emp where sal <> 3000;

也可以用固定思维:

mysql> select ename from emp where sal != 3000;

5.5.2、Between and

例如:找出工资在1100和3000之间的员工,包括1100和3000?

mysql> select ename from emp where sal >=1100 and sal<=3000;

也可以这样:

mysql> select ename from emp where sal between 1100 and 3000;

这里要注意下,between必须左小右大,并且所划的区间是闭区间。


Between and 除了可以使用在数字方面之外,还可以使用在字符串方面。

mysql> select ename from emp where ename between 'A' and 'D';
+-------+
| ename |
+-------+
| ALLEN |
| BLAKE |
| CLARK |
| ADAMS |
+-------+

这里要注意的是,字符串用between and 取头字母二十四字母区间,而且所划区间左闭右开。


5.5.3、is null

案例:找出哪些人没有津贴?

在数据库当中NULL不是一个值,代表什么也没有,为空。

空不是一个值,不能用等号衡量。

必须使用is null或者is not null

mysql> select ename,sal,comm from emp where comm is null;
	+--------+---------+------+
	| ename  | sal     | comm |
	+--------+---------+------+
	| SMITH  |  800.00 | NULL |
	| JONES  | 2975.00 | NULL |
	| BLAKE  | 2850.00 | NULL |
	| CLARK  | 2450.00 | NULL |
	| SCOTT  | 3000.00 | NULL |
	| KING   | 5000.00 | NULL |
	| ADAMS  | 1100.00 | NULL |
	| JAMES  |  950.00 | NULL |
	| FORD   | 3000.00 | NULL |
	| MILLER | 1300.00 | NULL |
	+--------+---------+------+

案例:找出哪些人津贴不为NULL?

	mysql> select ename,sal,comm from emp where comm is not null;
	
	+--------+---------+---------+
	| ename  | sal     | comm    |
	+--------+---------+---------+
	| ALLEN  | 1600.00 |  300.00 |
	| WARD   | 1250.00 |  500.00 |
	| MARTIN | 1250.00 | 1400.00 |
	| TURNER | 1500.00 |    0.00 |
	+--------+---------+---------+

5.5.4、or

案例:找出哪些人没有津贴?

mysql> select ename,sal,comm from emp where comm is null or comm = 0 ;
	+--------+---------+------+
	| ename  | sal     | comm |
	+--------+---------+------+
	| SMITH  |  800.00 | NULL |
	| JONES  | 2975.00 | NULL |
	| BLAKE  | 2850.00 | NULL |
	| CLARK  | 2450.00 | NULL |
	| SCOTT  | 3000.00 | NULL |
	| KING   | 5000.00 | NULL |
	| TURNER | 1500.00 | 0.00 |
	| ADAMS  | 1100.00 | NULL |
	| JAMES  |  950.00 | NULL |
	| FORD   | 3000.00 | NULL |
	| MILLER | 1300.00 | NULL |
	+--------+---------+------+

案例:找出工作岗位是MANAGER和SALESMAN的员工?

	mysql> select ename,job from emp where job = 'MAnager' or job ='salesman';
	
	+--------+----------+
	| ename  | job      |
	+--------+----------+
	| ALLEN  | SALESMAN |
	| WARD   | SALESMAN |
	| JONES  | MANAGER  |
	| MARTIN | SALESMAN |
	| BLAKE  | MANAGER  |
	| CLARK  | MANAGER  |
	| TURNER | SALESMAN |
	+--------+----------+

5.5.5、And和or的优先级问题

And和or联合起来用:找出薪资大于1000的并且部门编号是20或30部门的员工。

mysql> select ename,sal,deptno from emp where sal>1000 and deptno=20 or deptno = 30;
这里观察下表发现,有一项数据出了问题。

	+--------+---------+--------+
	| ename  | sal     | deptno |
	+--------+---------+--------+
	| ALLEN  | 1600.00 |     30 |
	| WARD   | 1250.00 |     30 |
	| JONES  | 2975.00 |     20 |
	| MARTIN | 1250.00 |     30 |
	| BLAKE  | 2850.00 |     30 |
	| SCOTT  | 3000.00 |     20 |
	| TURNER | 1500.00 |     30 |
	| ADAMS  | 1100.00 |     20 |
	| JAMES  |  950.00 |     30 |
	| FORD   | 3000.00 |     20 |
	+--------+---------+--------+

这说明and的运算优先级大于or运算优先级,在实际运用中并不需要记住这些优先级,不确定的优先级直接加小括号括起来先运算即可。

mysql> select ename,sal,deptno from emp where sal>1000 and (deptno=20 or deptno = 30);

5.5.6、in

in等同于or:找出工作岗位是MANAGER和SALESMAN的员工?

mysql> select ename,job from emp where job = 'salesman' or job = 'manager';

实际上,也可以用下面的这一句:

mysql> select ename,job from emp where job in ('SALESMAN','MANAGER');
	+--------+----------+
	| ename  | job      |
	+--------+----------+
	| ALLEN  | SALESMAN |
	| WARD   | SALESMAN |
	| JONES  | MANAGER  |
	| MARTIN | SALESMAN |
	| BLAKE  | MANAGER  |
	| CLARK  | MANAGER  |
	| TURNER | SALESMAN |
	+--------+----------+

注意:这里in后面的是值,不是区间。


5.5.7、模糊查询like

找出名字当中含有o的?

(在模糊查询当中,必须掌握两个特殊的符号,一个是%,一个是_)

%代表任意多个字符,_代表任意一个字符。

	mysql> select ename from emp where ename like '%o%';
	+-------+
	| ename |
	+-------+
	| JONES |
	| SCOTT |
	| FORD  |
	+-------+

找出名字中第二个字母是A的?

	mysql> select ename from emp where ename like '_a%';
	
	+--------+
	| ename  |
	+--------+
	| WARD   |
	| MARTIN |
	| JAMES  |
	+--------+

如果要找出名字里面有下划线的,为了避免功能混淆,我们可以用_来作为转义字符。


char和varchar的模糊查询问题:

由于varchar可变,导致模糊查询查出来是我们日常理解的正确的结果;而char不可变,导致一旦你定的字节超过你里面存放的字节,那么多余的字节全部空格补齐,也就是说,你用like去查,比如查l_%,那么“ _ ”处很有可能是空格,比如char类型的“刘云 ”,那么like _ _ 是可以把云后面的空格也查出来的,“刘云 ”也是like _ _ 查询出来的结果。


5.6、排序

案例:按照工资升序,找出员工名和薪资?

	mysql> select
	    -> ename,sal
	    -> from
	    -> emp
	    -> order by
	    -> sal;
	
	+--------+---------+
	| ename  | sal     |
	+--------+---------+
	| SMITH  |  800.00 |
	| JAMES  |  950.00 |
	| ADAMS  | 1100.00 |
	| WARD   | 1250.00 |
	| MARTIN | 1250.00 |
	| MILLER | 1300.00 |
	| TURNER | 1500.00 |
	| ALLEN  | 1600.00 |
	| CLARK  | 2450.00 |
	| BLAKE  | 2850.00 |
	| JONES  | 2975.00 |
	| FORD   | 3000.00 |
	| SCOTT  | 3000.00 |
	| KING   | 5000.00 |
	+--------+---------+

【注意:这是默认升序,如果要指定升序用asc,指定降序用desc。】

	mysql> select
	    -> ename,sal
	    -> from
	    -> emp
	    -> order by
	    -> sal desc;
	
	+--------+---------+
	| ename  | sal     |
	+--------+---------+
	| KING   | 5000.00 |
	| SCOTT  | 3000.00 |
	| FORD   | 3000.00 |
	| JONES  | 2975.00 |
	| BLAKE  | 2850.00 |
	| CLARK  | 2450.00 |
	| ALLEN  | 1600.00 |
	| TURNER | 1500.00 |
	| MILLER | 1300.00 |
	| MARTIN | 1250.00 |
	| WARD   | 1250.00 |
	| ADAMS  | 1100.00 |
	| JAMES  |  950.00 |
	| SMITH  |  800.00 |
	+--------+---------+

按照工资的升序降序排列,当工资相同的时候再按照名字的升序排列。

mysql> select ename,sal from emp order by sal desc,ename asc;
	+--------+---------+
	| ename  | sal     |
	+--------+---------+
	| KING   | 5000.00 |
	| FORD   | 3000.00 |
	| SCOTT  | 3000.00 |
	| JONES  | 2975.00 |
	| BLAKE  | 2850.00 |
	| CLARK  | 2450.00 |
	| ALLEN  | 1600.00 |
	| TURNER | 1500.00 |
	| MILLER | 1300.00 |
	| MARTIN | 1250.00 |
	| WARD   | 1250.00 |
	| ADAMS  | 1100.00 |
	| JAMES  |  950.00 |
	| SMITH  |  800.00 |
	+--------+---------+

【注意:后面字段比前面字段地位更低,只有在前面字段规则排序不出来的时候,才会采用后面字段的排序规则。】


例如:查找工作岗位是salesman的员工,并且要求按照薪资的降序排列。


	mysql> select
	    -> ename
	    -> from
	    -> emp
	    -> where
	    -> job = 'salesman'
	    -> order by
	    -> sal desc;
	
	+--------+
	| ename  |
	+--------+
	| ALLEN  |
	| TURNER |
	| WARD   |
	| MARTIN |
	+--------+

这里要说明一下执行顺序

mysql> select
  -> 字段 3
  -> from
  -> 表名 1
  -> where
  -> 条件 2
  -> order by 4
  -> 

5.7、分组函数

5.7.1、概述

分组函数一共5个。

count 计数

sum 求和

avg 平均值

max 最大值

min 最小值

在有些书上,也把分组函数叫做聚集函数。

而且他还有例外一个名字,叫多行处理函数

多行处理函数的特点:输入多行,最终输出一行


记住:所有的分组函数都是对“某一组”数据进行操作的。

与之对应的有个叫单行处理函数,也就是,先找到一行,处理一行,然后再继续下一行。


分组函数自动忽略NULL。

mysql> select count(comm) from emp;
	+-------------+
	| count(comm) |
	+-------------+
	|           4 |
	+-------------+

也就是说,我们不需要写成下面的形式

mysql> select count(comm) from emp where comm is not null;

5.7.2、单行处理函数

什么是单行处理函数?

输入一行,输出一行。


例如:计算每个员工的年薪?

mysql> select ename,(800+null)*12 as yearsal from emp;
	+--------+---------+
	| ename  | yearsal |
	+--------+---------+
	| SMITH  |    NULL |
	| ALLEN  |    NULL |
	| WARD   |    NULL |
	| JONES  |    NULL |
	| MARTIN |    NULL |
	| BLAKE  |    NULL |
	| CLARK  |    NULL |
	| SCOTT  |    NULL |
	| KING   |    NULL |
	| TURNER |    NULL |
	| ADAMS  |    NULL |
	| JAMES  |    NULL |
	| FORD   |    NULL |
	| MILLER |    NULL |
	+--------+---------+

为什么会出现都空呢?

因为在数据库里面有个死规定(针对所有数据库),如果有个数据为空,那么参与空的运算的一切结果都是空。


ifnull() 空处理函数?

ifnull(可能为NULL的数据,被当做什么处理):属于单行处理函数

	mysql> select ename,ifnull(comm,0) as comm from emp;
	
	+--------+---------+
	| ename  | comm    |
	+--------+---------+
	| SMITH  |    0.00 |
	| ALLEN  |  300.00 |
	| WARD   |  500.00 |
	| JONES  |    0.00 |
	| MARTIN | 1400.00 |
	| BLAKE  |    0.00 |
	| CLARK  |    0.00 |
	| SCOTT  |    0.00 |
	| KING   |    0.00 |
	| TURNER |    0.00 |
	| ADAMS  |    0.00 |
	| JAMES  |    0.00 |
	| FORD   |    0.00 |
	| MILLER |    0.00 |
	+--------+---------+

5.7.3、分组函数注意事项

例如:找出工资高于平均工资的员工?

mysql> select avg(sal) from emp where sal>avg(sal);

ERROR 1111 (HY000): Invalid use of group function

思考以上的错误信息:无效的使用了分组函数

原因:SQL语句当中有一个语法规则,分组函数不可直接使用where字句当中。

怎么解释?(学完group by再回来看)

因为group by是在where执行之后才会执行的。

用顺序来解释就是:

select 5
from 1
where 2
group by 3
having 4
order by 6

而在这时候你要用分组函数的时候,group by还没执行呢,你用不了函数。


所以!解锁新技巧!子查询!

两句sql语句可以合成一句来写!

还是以这个例子为例:

将一个问题拆分成两句,分成两句后合成一句即可,如下

mysql> select ename,sal from emp where sal>(*select avg(sal) from emp* ) ;
+-------+---------+
| ename | sal     |
+-------+---------+
| JONES | 2975.00 |
| BLAKE | 2850.00 |
| CLARK | 2450.00 |
| SCOTT | 3000.00 |
| KING  | 5000.00 |
| FORD  | 3000.00 |
+-------+---------+

count(*)和count(具体的字段),他们有什么区别?

count(*):不是统计某个字段中数据的个数,而是统计总记录条数。(与某个字段无关)

count(comm):表示统计comm字段中不为NULL的数据总量。


分组函数也能组合起来用

mysql> select count(*),sum(sal),avg(sal),max(sal),min(sal) from emp;

+----------+----------+-------------+----------+----------+
| count(*) | sum(sal) | avg(sal)    | max(sal) | min(sal) |
+----------+----------+-------------+----------+----------+
|       14 | 29025.00 | 2073.214286 |  5000.00 |   800.00 |
+----------+----------+-------------+----------+----------+

5.7.4、group by

group by:按照某个字段或者某些字段进行分组;

having:having是对分组之后的数据进行再次过滤。


案例:找出每个工作岗位的最高薪资

mysql> select job, max(sal) from emp group by job;

+-----------+----------+
| job       | max(sal) |
+-----------+----------+
| ANALYST   |  3000.00 |
| CLERK     |  1300.00 |
| MANAGER   |  2975.00 |
| PRESIDENT |  5000.00 |
| SALESMAN  |  1600.00 |
+-----------+----------+

注意:分组函数一般都会和group by联合使用,这也是为什么他被称为分组函数的原因,并且任何一个分组函数都是在group by语句执行结束之后才会执行的。当一条sql语句没有group by的话,整张表的数据会自成一组。


什么意思?

可以理解为:

select avg(sal) from emp;   =   select avg(sal) from emp group by;

5.7.5、多字段分组查询

找出每个工作岗位的最高薪资?

	mysql>  select max(sal),job from emp group by job;
	
	+----------+-----------+
	| max(sal) | job       |
	+----------+-----------+
	|  3000.00 | ANALYST   |
	|  1300.00 | CLERK     |
	|  2975.00 | MANAGER   |
	|  5000.00 | PRESIDENT |
	|  1600.00 | SALESMAN  |
	+----------+-----------+

但是假如我们这么操作:

	mysql> select ename,max(sal),job from emp group by job;
	
	+-------+----------+-----------+
	| ename | max(sal) | job       |
	+-------+----------+-----------+
	| SCOTT |  3000.00 | ANALYST   |
	| SMITH |  1300.00 | CLERK     |
	| JONES |  2975.00 | MANAGER   |
	| KING  |  5000.00 | PRESIDENT |
	| ALLEN |  1600.00 | SALESMAN  |
	+-------+----------+-----------+

以上SQL语句在Oracle数据库中无法执行,执行报错。

以上SQL语句在Mysql数据库中可以执行,但是执行结果矛盾。

在SQL语句中若有group by 语句,那么在select语句后面只能跟分组函数+参与分组的字段


例如:找出每个岗位的平均薪资

	select job,avg(sal) from emp group by job;
	+-----------+-------------+
	| job       | avg(sal)    |
	+-----------+-------------+
	| ANALYST   | 3000.000000 |
	| CLERK     | 1037.500000 |
	| MANAGER   | 2758.333333 |
	| PRESIDENT | 5000.000000 |
	| SALESMAN  | 1400.000000 |
	+-----------+-------------+

多个字段能不能联合起来一块分组?


案例:找出不同工作岗位的最高薪资?

	select deptno,job,max(sal)
	from
	emp
	group by
	deptno,job;
	
	+--------+-----------+----------+
	| deptno | job       | max(sal) |
	+--------+-----------+----------+
	|     10 | CLERK     |  1300.00 |
	|     10 | MANAGER   |  2450.00 |
	|     10 | PRESIDENT |  5000.00 |
	|     20 | ANALYST   |  3000.00 |
	|     20 | CLERK     |  1100.00 |
	|     20 | MANAGER   |  2975.00 |
	|     30 | CLERK     |   950.00 |
	|     30 | MANAGER   |  2850.00 |
	|     30 | SALESMAN  |  1600.00 |
	+--------+-----------+----------+

5.7.6、having 和 where

假如:找出每个部门的最高薪资,要求显示薪资大于2500的数据?

	mysql> select
	    -> max(sal),deptno,job
	    -> from
	    -> emp
	    -> group by
	    -> deptno
	    -> having
	    -> max(sal)>2500;
	+----------+--------+----------+
	| max(sal) | deptno | job      |
	+----------+--------+----------+
	|  5000.00 |     10 | MANAGER  |
	|  3000.00 |     20 | CLERK    |
	|  2850.00 |     30 | SALESMAN |
	+----------+--------+----------+

【注意:虽然能查找到,但是效率低,因为大于2500我们可以在where那里就过滤掉,比如下面这种写法。】

	mysql> select max(sal),deptno,job from emp where sal>2500 group by deptno;
	
	+----------+--------+-----------+
	| max(sal) | deptno | job       |
	+----------+--------+-----------+
	|  5000.00 |     10 | PRESIDENT |
	|  3000.00 |     20 | MANAGER   |
	|  2850.00 |     30 | MANAGER   |
    +----------+--------+-----------+

问题:找出每个部门的平均薪资,要求显示薪资大于2000的数据。

mysql> select avg(sal) ,deptno from emp group by deptno having avg(sal)>2000;
+-------------+--------+
| avg(sal)    | deptno |
+-------------+--------+
| 2916.666667 |     10 |
| 2175.000000 |     20 |
+-------------+--------+

但是却不能这么写

mysql> select avg(sal) ,deptno from emp where *avg(sal)>2000* group by deptno;

会报错,你懂得。


5.7.6、select语句总结

一个完整的select语句格式如下

select 字段  
from 表名  
where ……
group by ……  
having ……(就是为了过滤分组后的数据而存在的—不可以单独的出现)  
order by …… 

以上语句的执行顺序

首先执行where语句过滤原始数据

执行group by进行分组

执行having对分组数据进行操作

执行select选出数据

执行order by排序


原则:能在where中过滤的数据,尽量在where中过滤,效率较高。having的过滤是专门对分组之后的数据进行过滤的。(事实上这里设计到查询优化的问题)


5.8、连接查询

5.8.1、概述

什么是连接查询?

在实际开发中,大部分的情况下都不是从单表中查询数据,一般都是多张表联合查询取出最终的结果。在实际开发中,一般一个业务都会对应多张表。比如:学生和班级,起码两张表。


如果把学生表和班级信息存储到一张表中,结果就像上面一样,数据会存在大量的重复,导致数据的冗余


5.8.2、连接查询分类

分类?

根据语法出现的年代来划分分为两种。

SQL92(一些老的DBA可能还在使用这种语法。DBA:DataBase Administrator,数据库管理员)

SQL99(比较新的语法)


杂谈:DBA薪资很高,一般学数据库原理,数据结构等非常底层的东西,一般不接触业务。


根据表的连接方式来划分,包括:

  • 内连接

    • 等值连接
    • 非等值连接
    • 自连接
  • 外连接

    • 左外连接(左连接)
    • 右外连接(右连接)
  • 全连接(很少见)


5.8.3、连接查询原理以及笛卡尔现象

在表的连接查询方面有一种现象被称为:笛卡尔积现象(笛卡尔乘积现象)


案例:找出每一个员工的部门名称,要求显示员工名和部门名

下面我们验证下连接查询的原理:

	mysql> select ename,dname from emp,dept;
	
	+--------+------------+
	| ename  | dname      |
	+--------+------------+
	| SMITH  | ACCOUNTING |
	| SMITH  | RESEARCH   |
	| SMITH  | SALES      |
	| SMITH  | OPERATIONS |
	| ALLEN  | ACCOUNTING |
	| ALLEN  | RESEARCH   |
	| ALLEN  | SALES      |
	………………………
	+--------+------------+
共56条记录

笛卡尔积现象:当两张表进行连接查询的时候,没有任何条件进行限制,最终的查询结果条数是两张表记录条数的乘积。


在这里说一个事

关于表的别名:

mysql> select e.ename,d.dname from emp e,dept d;

表的别名有什么好处?

  1. 执行效率高。
  2. 可读性好。

那怎么避免笛卡尔积现象?当然是加条件过滤。


思考?避免了笛卡尔积现象,会减少记录的匹配次数吗?

不会,次数还是56次,只不过显示的是有效记录。


所以,回到最开始的问题:编写相关SQL如下(下面是SQL92,以后不用)

	mysql> select
	    -> e.ename,d.dname
	    -> from
	    -> emp e,dept d
	    -> where
	    -> e.deptno=d.deptno;
	
	+--------+------------+
	| ename  | dname      |
	+--------+------------+
	| CLARK  | ACCOUNTING |
	| KING   | ACCOUNTING |
	| MILLER | ACCOUNTING |
	| SMITH  | RESEARCH   |
	| JONES  | RESEARCH   |
	| SCOTT  | RESEARCH   |
	| ADAMS  | RESEARCH   |
	| FORD   | RESEARCH   |
	| ALLEN  | SALES      |
	| WARD   | SALES      |
	| MARTIN | SALES      |
	| BLAKE  | SALES      |
	| TURNER | SALES      |
	| JAMES  | SALES      |
	+--------+------------+

5.8.4、等值连接

内连接中的等值连接,最大特点就是条件是等量关系。


案例:查询每个员工的部门名称,要求显示员工们和部门名。

在SQL92里,我们是这么写的。

		mysql> select
		    -> e.ename,d.dname
		    -> from
		    -> emp e,dept d
		    -> where
    		-> e.deptno=d.deptno;

SQL92之所以舍弃,官方的说辞是,where里面写的是表连接的条件,不是过滤条件,条件结构不清晰。


而在SQL99里,我们这么写:

		mysql> select
		    -> e.ename,d.dname
		    -> from
		    -> emp e (inner)join dept d
		    -> on
    -> e.deptno=d.deptno;

注意:在这里inner可以省略,但是写上去人家一看上去就知道是内连接,可读性好。


总结出来SQL99语法就是:

	…
	A
	join
	B
	on
	连接条件
	where
	…

SQL语法结构更加清晰一些:表的连接条件和where过滤条件分离了。


5.8.4、非等值连接

内连接中的非等值连接最大的特点就是:连接条件中的关系是非等量关系。


案例:要求每个员工的工资等级,要求显示员工名、工资、工资等级。

首先先把员工表的员工名和工资拿出来:

		mysql> select ename,sal from emp;
		+--------+---------+
		| ename  | sal     |
		+--------+---------+
		| SMITH  |  800.00 |
		| ALLEN  | 1600.00 |
		| WARD   | 1250.00 |
		| JONES  | 2975.00 |
		| MARTIN | 1250.00 |
		| BLAKE  | 2850.00 |
		| CLARK  | 2450.00 |
		| SCOTT  | 3000.00 |
		| KING   | 5000.00 |
		| TURNER | 1500.00 |
		| ADAMS  | 1100.00 |
		| JAMES  |  950.00 |
		| FORD   | 3000.00 |
		| MILLER | 1300.00 |
		+--------+---------+

然后再找出工资等级表;

		mysql> select * from salgrade;
		+-------+-------+-------+
		| GRADE | LOSAL | HISAL |
		+-------+-------+-------+
		|     1 |   700 |  1200 |
		|     2 |  1201 |  1400 |
		|     3 |  1401 |  2000 |
		|     4 |  2001 |  3000 |
		|     5 |  3001 |  9999 |
		+-------+-------+-------+

拿史密斯来说,如果不加任何限制条件,那么两张表会做笛卡尔乘积,当你拿了史密斯之后,史密斯的薪资只有在工资等级的最底层,所以找表的时候,史密斯只能匹配下表的第一行。

	mysql> select
	    -> e.ename,e.sal,s.grade
	    -> from
	    -> emp e
	    -> (inner)join
	    -> salgrade s
	    -> on
    	-> e.sal between s.losal and s.hisal;
    		+--------+---------+-------+
	| ename  | sal     | grade |
	+--------+---------+-------+
	| SMITH  |  800.00 |     1 |
	| ALLEN  | 1600.00 |     3 |
	| WARD   | 1250.00 |     2 |
	| JONES  | 2975.00 |     4 |
	| MARTIN | 1250.00 |     2 |
	| BLAKE  | 2850.00 |     4 |
	| CLARK  | 2450.00 |     4 |
	| SCOTT  | 3000.00 |     4 |
	| KING   | 5000.00 |     5 |
	| TURNER | 1500.00 |     3 |
	| ADAMS  | 1100.00 |     1 |
	| JAMES  |  950.00 |     1 |
	| FORD   | 3000.00 |     4 |
	| MILLER | 1300.00 |     2 |
	+--------+---------+-------+

5.8.5、自连接

最大的特点是:一张表看做两张表,自己连接自己。


案例:找出每个员工的上级领导,要求显示员工名和对应的领导名。

现在先把原始数据拿出来。

备注:mgr经理,empno工号

	mysql> select empno,ename,mgr from emp ;
	+-------+--------+------+
	| empno | ename  | mgr  |
	+-------+--------+------+
	|  7369 | SMITH  | 7902 |
	|  7499 | ALLEN  | 7698 |
	|  7521 | WARD   | 7698 |
	|  7566 | JONES  | 7839 |
	|  7654 | MARTIN | 7698 |
	|  7698 | BLAKE  | 7839 |
	|  7782 | CLARK  | 7839 |
	|  7788 | SCOTT  | 7566 |
	|  7839 | KING   | NULL |
	|  7844 | TURNER | 7698 |
	|  7876 | ADAMS  | 7788 |
	|  7900 | JAMES  | 7698 |
	|  7902 | FORD   | 7566 |
	|  7934 | MILLER | 7782 |
	+-------+--------+------+
	mysql> select e.ename,e2.ename from emp e join emp e2 on e.mgr = e2.empno;
	+--------+-------+
	| ename  | ename |
	+--------+-------+
	| SMITH  | FORD  |
	| ALLEN  | BLAKE |
	| WARD   | BLAKE |
	| JONES  | KING  |
	| MARTIN | BLAKE |
	| BLAKE  | KING  |
	| CLARK  | KING  |
	| SCOTT  | JONES |
	| TURNER | BLAKE |
	| ADAMS  | SCOTT |
	| JAMES  | BLAKE |
	| FORD   | JONES |
	| MILLER | CLARK |
	+--------+-------+
	升级版:
	mysql> select e.ename "员工名",e2.ename "老板名" from emp e join emp e2 on e.mgr = e2.empno;
	+--------+--------+
	| 员工名      | 老板名      |
	+--------+--------+
	| SMITH  | FORD   |
	| ALLEN  | BLAKE  |
	| WARD   | BLAKE  |
	| JONES  | KING   |
	| MARTIN | BLAKE  |
	| BLAKE  | KING   |
	| CLARK  | KING   |
	| SCOTT  | JONES  |
	| TURNER | BLAKE  |
	| ADAMS  | SCOTT  |
	| JAMES  | BLAKE  |
	| FORD   | JONES  |
	| MILLER | CLARK  |
	+--------+--------+

5.8.6、外连接

什么是外连接?和内连接有什么区别?

内连接:假设A和B表进行连接,使用内连接的话,凡是A表和B表能够匹配上的记录查询出来,就是内连接,AB两张表没有主副之分,两张表是平等的。


外连接:假设A和B表进行连接,使用外连接的话,AB两张表中有一张表是主表,一张表是副表,主要查询主表中的数据,捎带着查询副表,当副表中的数据没有和主表中的数据匹配上,副表自动模拟出NULL与之匹配。


外连接的分类?

左外连接(左连接):表示左边的这张表是主表。

右外连接(右连接):表示右边的这张表是主表。


左连接有右连接的写法,右连接也会有对应的左连接的写法。

为什么这么说,当你左连接想写成右连接的时候,你把表对调一下就可以了。


案例:找出每个员工的上级领导?(所有员工,包括最顶级的上司)

	mysql> select empno,ename,mgr from emp ;
	+-------+--------+------+
	| empno | ename  | mgr  |
	+-------+--------+------+
	|  7369 | SMITH  | 7902 |
	|  7499 | ALLEN  | 7698 |
	|  7521 | WARD   | 7698 |
	|  7566 | JONES  | 7839 |
	|  7654 | MARTIN | 7698 |
	|  7698 | BLAKE  | 7839 |
	|  7782 | CLARK  | 7839 |
	|  7788 | SCOTT  | 7566 |
	|  7839 | KING   | NULL |
	|  7844 | TURNER | 7698 |
	|  7876 | ADAMS  | 7788 |
	|  7900 | JAMES  | 7698 |
	|  7902 | FORD   | 7566 |
	|  7934 | MILLER | 7782 |
	+-------+--------+------+

当时我们内连接是这么写的,这样写最顶级的上司查不出来。

mysql> select e.ename "员工名",e2.ename "老板名" from emp e join emp e2 on e.mgr = e2.empno;

但是我们使用外连接就能找出来了。

	mysql> select e.ename "员工名",e2.ename "老板名" from emp e left(out) join emp e2 on e.mgr = e2.empno;
	注意:out可以省略。
	+--------+--------+
	| 员工名      | 老板名      |
	+--------+--------+
	| SMITH  | FORD   |
	| ALLEN  | BLAKE  |
	| WARD   | BLAKE  |
	| JONES  | KING   |
	| MARTIN | BLAKE  |
	| BLAKE  | KING   |
	| CLARK  | KING   |
	| SCOTT  | JONES  |
	| KING   | NULL   |
	| TURNER | BLAKE  |
	| ADAMS  | SCOTT  |
	| JAMES  | BLAKE  |
	| FORD   | JONES  |
	| MILLER | CLARK  |
	+--------+--------+

外连接的特点就是,将主表的数据无条件的查找出来。


全连接就不讲了,没有实际用处。


5.8.7、三张表作连接

这里先解释一下:

A

jion

B

jion

C

on

表示:A表和B表先进行表连接,连接之后A表继续和C表进行连接。


5.9、子查询

5.9.1、概述

什么是查询?子查询都可以出现在哪里?

select语句当中嵌套select语句,被嵌套的select语句是子查询。

		select
			…(select)
		from
			…(select)
		where
			…(select)

5.9.2、where子查询

案例:找出高于平均薪资的员工信息

	mysql> select * from emp where sal>(select avg(sal) from emp);
		+-------+-------+-----------+------+------------+---------+------+--------+
	| EMPNO | ENAME | JOB       | MGR  | HIREDATE   | SAL     | COMM | DEPTNO |
	+-------+-------+-----------+------+------------+---------+------+--------+
	|  7566 | JONES | MANAGER   | 7839 | 1981-04-02 | 2975.00 | NULL |     20 |
	|  7698 | BLAKE | MANAGER   | 7839 | 1981-05-01 | 2850.00 | NULL |     30 |
	|  7782 | CLARK | MANAGER   | 7839 | 1981-06-09 | 2450.00 | NULL |     10 |
	|  7788 | SCOTT | ANALYST   | 7566 | 1987-04-19 | 3000.00 | NULL |     20 |
	|  7839 | KING  | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL |     10 |
	|  7902 | FORD  | ANALYST   | 7566 | 1981-12-03 | 3000.00 | NULL |     20 |
	+-------+-------+-----------+------+------------+---------+------+--------+

这里相当于复习,要注意的就是分组函数不能出现在where后面。


5.9.3、from子查询

案例:找出每个部门平均薪水的薪资等级。

第一步:找出每个部门平均薪水(按照部门编号分组,求sal的平均值)

	mysql> select deptno,avg(sal) as avgsal from emp group by deptno;
	
	+--------+-------------+
	| deptno | avgsal      |
	+--------+-------------+
	|     10 | 2916.666667 |
	|     20 | 2175.000000 |
	|     30 | 1566.666667 |
	+--------+-------------+

第二步:将以上的查询结构当做临时表,让t表和salgrade表连接,条件是t.avgsal between s.losal and s.hisal

	mysql> select
	    -> t.*,s.grade
	    -> from
	    -> (select deptno,avg(sal) as avgsal from emp group by deptno) t
	    -> join
	    -> salgrade s
	    -> on
	    -> t.avgsal between s.losal and s.hisal;
	
	+--------+-------------+-------+
	| deptno | avgsal      | grade |
	+--------+-------------+-------+
	|     30 | 1566.666667 |     3 |
	|     10 | 2916.666667 |     4 |
	|     20 | 2175.000000 |     4 |
	+--------+-------------+-------+

5.10、联合union


5.11、部分limit

5.11.1、概述

limit是重中之重,分页查询都是靠他的。

limit是mysql特有的,其他数据库中没有,不通用。(Oracle中有一个相同的机制,叫做rownum。)

limit取结果集中的部分数据,这是他的作用。


语法机制

limit startIndex , length

startIndex表示起始位置,从0开始,0表示第一条数据。

length表示取几个。(注意不是末尾位置,是取几个)


案例:找出工资排名在第4到第9名的员工?

	mysql> select ename,sal from emp order by sal desc limit 3,6;
	
	+--------+---------+
	| ename  | sal     |
	+--------+---------+
	| JONES  | 2975.00 |
	| BLAKE  | 2850.00 |
	| CLARK  | 2450.00 |
	| ALLEN  | 1600.00 |
	| TURNER | 1500.00 |
	| MILLER | 1300.00 |
	+--------+---------+

5.12、表

5.12.1、建表和删表

建表语句的语法格式

	create table 表名(
		字段名 1 数据类型,
		字段名 2 数据类型,
		字段名 3 数据类型,
	);

关于MySQL当中字段的数据类型?以下只说常见的

int 整数型 (java中的int)
bigint 长整型 (java中的long)
float 浮点型 (java中的 float double)
char 定长字符串 (String)
varchar 可变长字符串 (StringBuffer/StringBuilder)
data 日期类型 (对应java中的java.sql.data类型)
BLOB 二进制大对象 (存储图片、视频等流媒体信息)binary large object(对应java的object)
CLOB 字符大对象 (存储较大文本,比如,可以存储4G的字符串。)character large object(对应java的object)

char和varchar?

他们两个的区别在于,假如我指定char(6),那么不管我输入啥字符,只要不超6个空间,它都是给6个空间,但是如果是varchar(6),他是一种智能的类型,能判断你输入的字符是占多少个空间(前提是不超6),并且分配对应字符的空间。


那是不是一定就要用varchar呢?

不一定,像生日,性别的这种数据字段不发生改变的时候,是定长的,那我们就采用char。

而当一个字段的数据长度不确定的时候,例如:简介、姓名等都是采用varchar。


BLOB和CLOB类型的使用?

假设有个表

电影表:t_movie

id name(varchar) playtime(data/char) haibao(BLOB) history(CLOB)
1 2 3 蜘蛛侠 … …

说明:一般来说,我们不会直接将视频放到表里面,而是将硬盘里面的路径放到表里。图片可能会放到表里。


案例:创建学生表

学生信息包括:

学号、姓名、性别、班级编号、生日

学号:bigint

姓名:varchar

性别:char

班级编号:int

生日:char

	create table t_student(
		no bigint,
		name varchar(255),
		sex char(1),
		classno varchar(255),
		birth char(10)
	);

注意:创建表的时候可以指定表为NULL时的默认值,如:

	create table t_student(
		no bigint,
		name varchar(255),
		sex char(1) default 1,
		classno varchar(255),
		birth char(10)
	);

向表中插入数据

mysql> insert into t_student (no,name,classno,birth) values (1,'bakalove','gaosan2ban','2019-16-51');
	mysql> select* from t_student;
	+------+----------+------+------------+------------+
	| no   | name     | sex  | classno    | birth      |
	+------+----------+------+------------+------------+
	|    1 | bakalove | 1    | gaosan2ban | 2019-16-51 |
	+------+----------+------+------------+------------+

怎么删表?

格式:drop table if exists 表名;

范例:drop table if exists s_student;


5.12.2、向表中插入数据

insert 语句插入数据

语法格式:

insert into 表名(字段名1,字段名2,字段名3..)values(值1,值2,值3)

这里要求字段的数量和值的数量相同,并且数据类型要对应相同。


例如:
创建学生表:

学生信息包括:

学号、姓名、性别、班级编号、生日

学号:bigint

姓名:varchar

性别:char

班级编号:int

生日:char


创建学生表

	create table t_student(
		no bigint,
		name varchar(255),
		sex char(1),
		classno varchar(255),
		birth char(10)
	);

添加学生表

insert into t_student(no,name,sex,classno,birth)values(1,'zhangsan','1','gaosan1ban','1950-10-12');

【说明:这里要讲一下,只要字段名和值能对得上就行】

也就是说,上面语句int对应1,你可以写成

insert into t_student(name,sex,classno,birth,no)values('zhangsan','1','gaosan1ban','1950-10-12',2);

如果表中有(学号、姓名、性别、班级编号、生日)五个字段,如果你只插入一个字段和值,那么其他字段的值为空。

mysql> insert into t_student(name) values('lisi');

Query OK, 1 row affected (0.00 sec)
		mysql> select* from t_student;
		+------+----------+------+------------+------------+
		| no   | name     | sex  | classno    | birth      |
		+------+----------+------+------------+------------+
		|    1 | zhangsan | 1    | gaosan1ban | 1950-10-12 |
		| NULL | lisi     | NULL | NULL       | NULL       |
		+------+----------+------+------------+------------+

需要注意的是:

当一条insert语句执行成功之后,表格当中必然会多一行记录。即使多的这一行记录当中某些字段是NULL,后期也无法再执行insert语句插入数据了,只能使用update进行更新。


特别地,如果你写插入语句,不写字段名,那么后面的值必须写满表所有的字段,一个都不能漏。

比如说表中有(学号、姓名、性别、班级编号、生日)五个字段,那么你可以写:

insert into t_student values(1,'zhangsan','1','gaosan1ban','1950-10-12');

即:字段可以省略不写,但是后面的value对数量和顺序都有要求。


之前,我们都是一行一行的添加,那我们是否可以多行添加呢?

答案是可以的,如:

insert into t_student(name,sex,classno,birth,no)
values('zhangsan','1','gaosan1ban','1950-10-12',2),
('lisi','2','gaosan1ban','1955-09-28',4);

5.12.3、表的复刻

一、表的复制

语法:

create table 表名 as select 语句;

说明:将查询结果当做表创建出来。

范例:

		create table emp1 as select * from emp;
		+-------+--------+-----------+------+------------+---------+---------+--------+
		| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
		+-------+--------+-----------+------+------------+---------+---------+--------+
		|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
		|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
		|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
		|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
		|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
		|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
		|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
		|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
		|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
		|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
		|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
		|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
		|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
		|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
		+-------+--------+-----------+------+------------+---------+---------+--------+

二、将查询结果插入到一张表中?

格式:insert into 表名 select 语句

范例:insert into dept1 select * from dept;


5.12.4、修改表的数据

语法格式:

update 表名 set 字段名1 = 值1,字段名2 = 值2…where 条件;

说明:where条件必须要加,不然会根据你修改的东西将表中所要修改的属性全部更新。

注意:没有条件整张表数据全部更新。


范例:将t_student表中的高三二班替换为高三三班。

	mysql> select * from t_student;
	说明:还没有改表前的对表的查询
	+------+----------+------+------------+------------+
	| no   | name     | sex  | classno    | birth      |
	+------+----------+------+------------+------------+
	|    1 | bakalove | 1    | gaosan2ban | 2019-16-51 |
	+------+----------+------+------------+------------+
	
	update t_student set classno = 'gaosan3ban'  where no = 1;
	说明:对高三2班改为高三3班
	
		mysql> select * from t_student;
	说明:已经修改成功了,对表进行查询。
	+------+----------+------+------------+------------+
	| no   | name     | sex  | classno    | birth      |
	+------+----------+------+------------+------------+
	|    1 | bakalove | 1    | gaosan3ban | 2019-16-51 |
	+------+----------+------+------------+------------+

要注意哦,很多人喜欢把多个值修改中的逗号写成and。不能写and。


5.12.5、删除表中数据

语法格式:

delete from 表名 where 条件;

注意:没有条件全部删除


删除所有记录?

delete from 表名;


怎么删除大表?(重点)

truncate table 表名;

说明:表被截断,不可回滚,永久丢失。


5.12.6、关于表结构修改

说明:对于表中的结构去修改实际情况发生概率是很低的,除非在建表的时候你没有考虑好,而且就算真的结构安排不合理,我们也是可以用工具完成即可。修改表的结构,实际上就是对之前的设计进行了否定,并且,修改表结构的语句不会出现在java代码当中。


增删改查有一个术语:CRUD操作

C:create(增)Retrieve(检索) Update(修改) Delete(删除)


5.13、约束

5.13.1、概述

约束的英文:Constraint


什么是约束?常见的约束有哪些?

在创建表的时候,可以给表的字段添加相应的约束,添加约束的目的是为了保证表中数据的合法性、有效性、完整性。

如某个登录界面:

username(唯一性) password(非空有效性)

常见的约束有哪些呢?

非空约束(not null):结束的字段不能为NULL

唯一约束(unique):约束的字段不能重复

主键约束(primary key):约束的字段不能为NULL,也不能重复(简称PK)

外键约束(foreign key):约束的字段不能为NULL,也不能重复(简称FK)

检查约束(check):控制属性域

注意:Oracle数据库和SQLserver有check,但是mysql没有,目前mysql不支持该约束。


5.13.2、非空性约束

范例

	create table t_user(
		id int,
		username varchar(255) not null,
		password varchar(255)
	);

说明:这里对于用户名采用了非空约束。


这里如果插入语句写了:

insert into t_user(id,password) values(1,'123');

那么他会报错:

ERROR 1364 (HY000): Field 'username' doesn't have a default value

顺带一提 ,数据库管理员一般只需要听到错误代码就知道哪里出了问题。


5.13.2、唯一性约束

唯一约束修饰的字段具有唯一性,不能重复,但可以为NULL。因为NULL不是值。


案例:给某一列添加unique

create table t_user(
id int,
username varchar(255) unique
);

说明:像这种直接在一个类型后面加约束的我们叫列级约束


案例:给两个列或者多个列添加unique

	create table t_user(
		id int,
		usercode varchar(255),
		username varchar(255),
		unique(usercode,username)
	);

说明:unique()指的是联合起来不能重复。也就是新添加的元组要满足usercode和username和以前添加过的元组不会重复。这种约束我们也叫表级约束

【注意:非空约束没有表级约束这一操作。】

insert into t_user values(1,'139','943');
Query OK, 1 row affected (0.00 sec)


mysql> select * from t_user;
+------+----------+----------+
| id   | usercode | username |
+------+----------+----------+
|    1 | 139      | 943      |
+------+----------+----------+
1 row in set (0.00 sec)


mysql> insert into t_user values(2,'139','255');
Query OK, 1 row affected (0.00 sec)


mysql> select * from t_user;
+------+----------+----------+
| id   | usercode | username |
+------+----------+----------+
|    1 | 139      | 943      |
|    2 | 139      | 255      |
+------+----------+----------+
2 rows in set (0.00 sec)


mysql> insert into t_user values(1,'139','943');
ERROR 1062 (23000): Duplicate entry '139-943' for key 'usercode'

5.13.3、主键约束

怎么给一张表添加主键约束?

操作一:建表

	drop table if exists t_user;
	create table t_user(
		id int primary key,
		username varchar(255),
		email varchar(255)
	);

操作二:插入表

	insert into t_user values (1,'zs','zs@123.com');
	insert into t_user values (2,'ls','ls@123.com');
	insert into t_user values(3,'ww','ww@123.com');

操作三:查找添加后的表

	mysql> select* from t_user;
	+----+----------+------------+
	| id | username | email      |
	+----+----------+------------+
	|  1 | zs       | zs@123.com |
	|  2 | ls       | ls@123.com |
	|  3 | ww       | ww@123.com |
	+----+----------+------------+

操作四:往定为主键的属性中插入重复值,验证主键约束。

	mysql> insert into t_user values(1,'kq','kq@123.com');
	说明:结果报错,提示主键不能重复。
	ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
	mysql> insert into t_user(username,email) values('kq','kq@123.com');
	说明:结果报错,提示id是主键,不能没有值。
ERROR 1364 (HY000): Field 'id' doesn't have a default value

根据以上的测试得出:id是主键,因为添加了主键约束,主键字段中的数据不能为NULL,也不能重复。

主键的特点:不能为NULL,也不能重复。


主键相关的术语?

主键约束:primary key

主键字段:id字段添加主键约束后,id叫做主键字段

主键值:id字段的每一个值都是主键值


主键有什么作用?

表的设计三范式中有要求,第一范式要求任何一张表都应该有主键。

主键的作用:主键值是这行记录在这张表当中的唯一标识。(就像一个人的身份证号码一样)


主键的分类?

根据主键字段的字段数量来划分

单一主键

复合主键(多个字段联合起来添加一个主键约束)

说明:一般来说不建议使用复合主键,因为复合主键违背三范式。


根据主键的性质划分

自然主键:主键值最好就是一个和业务没有任何关系的自然数。

业务主键:主键值和系统的业务挂钩,比如:银行卡卡号,身份证号码(不推荐用)

说明:为什么不推荐用?因为选主键最好不要选和业务挂钩的字段,否则一旦业务改变,主键也随之丢失,举个例子,银行卡号报废,主键消失,整条元组也会消失。


mysql提供主键值自增(利用自然主键)

	drop table if exists t_user;
	create table t_user(
		id int primary key auto_increment,
		username varchar(255)
	);
	
	
		insert into t_user (username) values('a');
	insert into t_user (username) values('b');
	insert into t_user (username) values('c');
	insert into t_user (username) values('d');
	insert into t_user (username) values('e');

【说明:id字段自动维护一个自增的数字,从1开始,以1递增。】

结果展示:

		mysql> select * from t_user;
		
		+----+----------+
		| id | username |
		+----+----------+
		|  1 | a        |
		|  2 | b        |
		|  3 | c        |
		|  4 | d        |
		|  5 | e        |
		+----+----------+

提示:Oracle当中也提供了一个自增机制,叫做:序列(sequence)


5.13.4、外键约束

关于外键约束的相关术语

外键约束:foreign key

外键字段:添加有外键约束的字段

外键值:外键字段中的每一个值


业务背景

请设计数据库表,用来维护学生和班级的信息?

两张表(班级表和学生表)


t_class 班级表

cno pk cname
101 北京大兴区经济开发区亦庄二中高三一班
102 北京大兴区经济开发区亦庄二中高三二班

t_student 学生表

sno(pk) sname classno(这里是fk外键约束)
1 zs1 101
2 zs2 101
3 zs3 102
4 zs4 102
5 zs5 102

将以上表的建表语句写出来:

t_student中的classno字段引用t_class表中的cno字段,此时t_student表叫做子表,t_class表叫做父表。


顺序要求:

删除数据的时候,先删除子表,再删除父表。

添加数据的时候,先添加父表,再添加子表。

创建表的时候,先创建父表,再创建子表。

删除表的时候,先删除子表,再删除父表。

【说明:这实际上和数据依赖有关,我们后面会讲到。】


实例:

		drop table if exists t_student;
		drop table if exists t_class;
		
		create table t_class(
			cno int,
			cname varchar(255),
			primary key(cno)
		);
		
		create table t_student(
			sno int,
			sname varchar(255),
			classno int,
			foreign key(classno) references t_class(cno)
		);

5.14、存储引擎(了解)

首先说一下完整的建表语句:

create table `t_x`(

`id` int(11) default nul

)engine = innodb default charset =utf

8;

【注意:在MySQL当中,凡是标识符是可以使用飘号括起来的,但是最好别用,因为其他数据库不通用。】


建表的时候可以指定存储引擎,也可以指定字符集。

mysql默认使用的存储引擎是InnoDB方式。

默认采用的字符集是UTF8


什么是存储引擎?

存储引擎这个名字只有在mysql中存在(Oracle中有对应的机制,但是不叫存储引擎。Oracle中没有特殊的名字,就是“表的存储方式”)

mysql支持很多种存储引擎,每一个存储引擎都对应了一种不同的存储方式。每一个存储引擎都有自己的优缺点,需要在合适的时机选择合适的存储引擎。


如何查看当前mysql支持的存储引擎?

show engines \G


5.15、事务

5.15.1、概述

英文名:Transaction


什么是事务?

一个事务是一个完整的业务逻辑单元,不可再分。


比如:银行账户转账,从A账户向B账户转账10000,需要执行两条Update语句。

update t_act set balance = balance -10000 where actno = 'act_001';

update t_act set balance = balance + 10000 where actno = 'act_002';


以上这个过程是一起的,如果不一起,就会导致转账这件事失败。也就是说,以上两条DML语句必须同时成功,或者同时失败,不允许出现一条成功,一条失败。


要想保证以上的两条DML语句同时成功或者同时失败,那么就需要使用数据库的“事务机制”。


和事务相关的语句只有:DML语句。(insert delete update)

为什么?因为它们这三个语句都是和数据库表当中的“数据”相关的。事务的存在是为了保证数据的完整性,安全性


假设所有的事务都能使用一条DML语句搞定,还需要事务机制吗?

不需要事务

但实际情况不是这样的,通常一个事务需要多条DML语句共同联合完成。


5.15.2、事务的特性

事务可以保证多个操作原子性,要么全成功,要么全失败。对于数据库来说事务保证批量的DML要么全成功,要么全失败。事务具有四个特征:ACID

  • A原子性:整个事务中的所有操作,必须作为一个单元全部完成(或者全部取消)
  • C一致性:在事务开始之前和结束之后,数据库都保持一致状态
  • I隔离性:一个事务不会影响其他事务的运行
  • D持久性:持久性说的是最终数据必须持久化到硬盘文件中,事务才算成功的结束。

5.12.3、事务的隔离性

事务隔离性存在隔离级别,理论上隔离级别包括4个:

第一级别:读未提交(read uncommitted)

对方事务还没有提交,我们当前事务可以读取到对方未提交的数据。并且读未提交存在脏读(Dirty Read)现象:表示读到了脏的数据。


第二级别:读已提交(read committed)

对方事务提交之后的数据我方可以读取到。但是其存在的问题是:不可以重复读(每次读到的数据不一样)

这种隔离级别实际上解决了脏读现象。


第三级别:可重复读(repeatable read)

这种隔离级别解决了不可重复读问题。

这种隔离级别存在的问题是:读取到的数据是幻象。也就是说,我们读到了数据库中的备份。


第四级别:序列化读/串行化读

解决了所有问题

存在问题:效率低,需要事务排队。


说明:Oracle默认第二级别,mysql默认第三级别。


5.16、索引

什么是索引?有什么用?

索引就相当于一本书的目录,通过目录可以快速的找到相应的资源。在数据库方面,查询一张表的时候有两种检索方式:

  • 第一种方式:全表扫描

  • 第二种方式:根据索引检索(效率很高)


索引为什么可以提高检索效率呢?

其实最根本的原理是缩小了扫描的范围。


索引虽然可以提高检索效率,但是不能随意的添加索引,因为索引也是数据库当中的对象,也需要数据库不断的维护。是有维护成本的。比如,表中的数据经常被修改,这样就不适合添加索引,因为数据一旦修改,索引需要重新排序,进行维护。

添加索引是给某一个字段,或者说某些字段添加索引。

select ename,sal from emp where ename = 'SMITH';

当ename字段上没有索引的时候,以上sql语句会进行全表扫描,扫描ename字段中所有的值。

当ename字段上添加索引的时候,以上sql语句会根据索引扫描,快速定位。


什么时候考虑给字段添加索引?(满足什么条件)

  • 数据量庞大。(根据客户的需求,根据线上的环境)

  • 该字段很少的DML操作。(因为字段进行修改操作,索引也需要维护)

  • 该字段经常出现在where子句中。(经常根据哪个字段查询)


注意:主键和具有unique的约束的字段自动会添加索引。

根据主键查询效率较高。尽量根据主键检索。


如何查看sql语句的执行计划?

mysql> explain select ename,sal from emp where sal = 5000;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

【说明:通过添加explain关键字进行查看。(只有mysql才有),其中type项显示all,说明是顺序扫描。其中的rows显示其找了14行。】


怎么创建索引对象?怎么删除索引对象?

创建索引格式:

create index 索引名称 on 表名(字段名);

删除索引对象:

drop index 索引名称 on 表名;

给薪资sal字段添加索引:

create index emp_sal_index on emp(sal);

效果如下:

mysql> create index emp_sal_index on emp(sal);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select ename,sal from emp where sal = 5000;
+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key           | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
|  1 | SIMPLE      | emp   | ref  | emp_sal_index | emp_sal_index | 9       | const |    1 | Using where |
+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

【说明:我们可以看到type项显示用ref查询,并且rows只扫描了一行。】

给薪资字段删除索引及其效果:

mysql> drop index emp_sal_index on emp;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select ename,sal from emp where sal = 5000;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

【索引底层采用的数据结构是B+树,这个知识点我们会在后面详细提到,这里不作过多讲解。】


索引什么情况下会失效?

select ename from emp where ename like '%A%';

模糊查询的时候,第一个通配符使用的是%,这个时候索引是失效的。


5.17、视图

什么是视图?

站在不同的角度去看到数据。(同一张表的数据,通过不同的角度去看待)。视图是从一个或几个基本表(或视图)中导出的虚拟的表。在系统的数据字典中仅存放了视图的定义,不存放视图对应的数据。


怎么创建视图?怎么删除视图?

//创建视图
create view myview as select empno,ename from emp;

//删除视图
drop view myview;

注意:只有DQL语句才能以视图对象的方式创建出来。


创建视图后我们观看结果,发现结果是一张表,说明视图其实是一张表。

mysql> show tables;
+-----------------------+
| Tables_in_bjpowernode |
+-----------------------+
| dept                  |
| emp                   |
| myview                |
| salgrade              |
| t_user                |
+-----------------------+
5 rows in set (0.00 sec)

mysql> select * from myview;
+-------+--------+
| empno | ename  |
+-------+--------+
|  7369 | SMITH  |
|  7499 | ALLEN  |
|  7521 | WARD   |
|  7566 | JONES  |
|  7654 | MARTIN |
|  7698 | BLAKE  |
|  7782 | CLARK  |
|  7788 | SCOTT  |
|  7839 | KING   |
|  7844 | TURNER |
|  7876 | ADAMS  |
|  7900 | JAMES  |
|  7902 | FORD   |
|  7934 | MILLER |
+-------+--------+
14 rows in set (0.01 sec)

【注意:对视图进行操作,是会影响到基表的结构的。】


视图的作用?

视图可以隐蔽表中的实现细节。保密级别较高的系统,数据库只对外提供相关的视图,java程序员只对视图对象进行CRUD。


视图实际上创建后是不会数据冗余的,即不会在硬盘上创建一份视图。他的原理实际上是查询改写。


5.18、数据库数据的导入导出

导入整个数据库

在window的dos命令窗口中执行:
mysqldump 数据库名称>导出路径\完整文件名 -u用户名名称 -p密码

导出指定库下的某个表

在window的dos命令窗口中执行:
mysqldump 数据库名称 表名>导出路径\完整文件名 -u用户名名称 -p密码

5.19、嵌入式SQL

SQL是一门操作语言,他本身是不具备程序设计能力的,如果要以数据库为基础做一个应用系统开发,那就必须让SQL和其他程序设计语言相互配合,然后才能解决这个问题,比如说C++,C,Java。


所以目前我们需要解决的问题有以下几点

1、如何让一条SQL语句写进程序设计语言。

2、如何让DBMS和程序设计语言交换数据。

3、如何将数据库查询出来的那个集合传递给程序设计语言里的变量。(阻抗不匹配)

4、一个数据库系统支持的数据类型和程序设计的数据类型不一致。


针对以上的问题,我们提出了几个方案

嵌入式SQL:在早期,实际上是把SQL语句嵌入到程序设计语言里,最典型的就是C语言,通过预编译,可以把SQL语言转换为对库函数的调用。

应用编程接口API:一些厂家可能会提供一些接口,方便应用程序连接调用,最常见的是ODBC,JDBC等接口。

当面向对象语言出现后,编译器为了方便用户连接数据库,都封装了一些对数据库访问的类,利用封装的类可以有比以往更简单的方法访问数据库。


实际上,现在已经很少有人使用嵌入式SQL了,现在使用JDBC和ODBC用的比较多一点,但是即使如此为什么我们还要学习嵌入式SQL呢,因为其他的两个办法实际上都是在嵌入式SQL的基础上发展起来的,嵌入式SQL是最底层的原理,是所以后面两个办法是主流只是用户方便。


所以在C语言中,我们是这么使用SQL语句,使编译器知道我们输入的语句是SQL语句的

1、以EXEC SQL开头,以分号结尾。

2、利用一种宿主变量来传递数据库和应用程序之间的数据的,他是SQL中可引用的C语言变量。这个变量必须EXEC SQL开头的说明语句说明。

3、在SQL语句中,我们应该在宿主变量前添加“:”去和数据库本身的变量做区别。

4、在C语言里面,宿主变量就当成一个普通变量来用。

5、宿主变量不能定义成数组或者是结构体。

6、在宿主变量中,有一个系统定义的特殊变量,叫SQLCA(即SQL Communication Area),他是全局变量,供应用程序和DBMS通信之用。如果你想用这个全局变量,只需要在最开始输入

EXEC SQL INCLUDE SQLCA

即可。

7、使用SQLCA.SQLCODE可以判断返回结果的状态。

8、说明符。数据库允许有空值,而在C语言中没有空值这个概念,那我们通过C语言去访问数据库的时候返回空值是无法接收的,所以我们引入了indicator这个说明符(他是一个short int变量)来表示一个属性的值是否为空值。


宿主变量定义范例

EXEC SQL BEGIN DECLARE SECTION;
	char SNO[7];
	char GIVENSNO[7];
	char CNO[6];
	char GIVENCNO[6];
	float GRADE;
	short GRADEI;
EXEC SQL END DECLARE SECTION;

嵌入式SQL语句

EXEC SQL CONNECT :uid IDENTIFIED BY:pwd;

【说明:其中uid表示用户标识符,pwd表示用户口令。】


插入语句

EXEC SQL INSERT INTO SC(SNO,CNO,GRADE)
VALUES(:SNO,:CNO,:GRADE);

【说明:插入的元组由三个宿主变量构成。宿主变量由宿主程序语言程序赋值。】


查询语句

EXEC SQL SELECT GRADE
	INTO :GRADE,:GRADEI
	FROM SC
	WHERE SNO = :GIVENSNO AND CNO = :GIVENCNO;

【说明:由于{SNO,CNO}是SC的主键,本句的查询结果不超过一个元组,可以直接把这个元组赋给宿主变量。但是如果是其他查询呢?查出来是一个集合呢?如果集合直接赋给宿主变量,那系统会报错。】


5.19、使用游标处理查询结果

游标,英文名Cursor。

前面我们说过,如果在C语言中利用嵌入式SQL语句查询输出库,查出来的是个集合,直接赋给宿主变量是会报错的;这时候我们就需要在程序中开辟一个区域,存放查询的结果,然后逐个地取出每个元组给宿主变量赋值。为了在区域中把元组逐个取出,我们需要一个指示器,也就是游标。


定义游标

说明游标语句定义一个命名的游标,且将它与相应的查询语句相联系。

EXEC SQL DECLARE<cursor name>CURSOR FOR
SELECT...
FROM...
WHERE...

打开游标

EXEC SQL OPEN<游标名>

【说明:一旦你打开游标,那么编译器就会把游标定义的语句交给数据库去执行,然后执行完就会返回一个集合的结果,这个集合我们可以看做是一个文件,文件里有所有的查询结果;然后我们根据游标的名词,就可以访问查询的结果,相当于是访问文件一样。】


取数语句

前面我们说过利用游标可以把查询的结果集合开辟一片地址存放,用游标去访问区域中的元组,那么如何把区域中的元组取出赋给宿主变量呢,那就要用到接下来的取数语句了。

EXEC SQL FETCH <游标名> INTO :hostvar1,:hostvar2,...;

在每次执行取数语句后,首先把游标向前推进一个位置,然后按照游标的当前位置取一个元组,对宿主变量hostvar1,hostvar2,...赋值。对单元组的查询不一样,INTO子句不是放在查询语句中,而是放在取数语句中。要恢复游标的初始位置,必须关闭游标后重新打开。在新的SQL版本中,有游标后退和跳跃的功能,游标可以定位到任何一个位置。如果游标的数语句取完,若再执行取数语句,SQLCODE将返回代码100。


关闭游标语句

EXEC SQL CLOSE <游标名>;

下面给出一个游标使用的范例

EXEC SQL DECLARE C1 CURSOR FOR
	SELECT SNO,GRADE
	FROM SC
	WHERE CNO = :GIVENCNO;
EXEC SQL OPEN C1;
while(TRUE)
{
	EXEC SQL FRTCH C1 INTO :SNO,:GRADE,:GRADEI;
	if(SQLCA.SQLCODE==100)
		break;
	if(SQLCA.SQLCODE<0)
		break;
		/*以下处理从游标所取数据,从略*/
		...
}
EXEC SQL CLOSE C1;
...

5.20、动态SQL

动态SQL英文:Dynamic SQL。

在前面我们学习游标的时候,打开游标之前我们定义游标时是已经把查询的内容写死了,无法改变;可是在实际应用中,有写查询语句常常不能事先确定,而是需要用户根据分析、统计的要求在程序运行时确定。因此,我们引出了动态SQL的概念。


动态SQL的分类

直接执行的动态SQL

...
EXEC SQL BEGIN DECLARE SECTION
char sqlstring[200];
EXEC SQL END DECLARE SECTION;
char cond[150];
	/*填入SQL语句的固定部分*/
strcpy(sqlstring,"DELETE FROM STUDENT WHERE")
	/*提示用户输入查询条件*/
printf("Enter search condition");
scanf("%d",cond);
strcat(sqlstring,cond);
	/*执行sqlstring中的SQL语句*/
EXEC SQL EXECUTE IMMEDIATE :sqlstring;
...

【注:strcat是C语言的字符拼接函数】

实际上实现原理是什么呢,就是定义一个字符型宿主变量,然后在里面写SQL语句写一半,空着另一半,另一半由用户自己去写,要写的时候用prinf去提示用户,当用户写入剩下的部分,后续的代码就会把用户写的和前面写的拼接在一起,然后提交给数据库。


带动态参数的动态SQL

...
EXEC SQL BEGIN DECLARE SECTION
char sqlstring[200];
int birth-year;
EXEC SQL END DECLARE SECTION;
strcpy(sqlstring,"DELETE FROM STUDENT WHERE YEAR(BDATE)<= :y;");
	/*提示用户输出参数birth-year,用以指明删除何年以前出生的学生记录*/
printf("Enter birth year for deleting:");
scanf("%d,&birth-year");
	/*用PREPARE语句定义sqlstring中的SQL语句为命令PURGE*/
EXEC SQL PREPARE PURGE FROM:sqlstring;
	/*用参数birth-year取代y执行命令PURGE*/
EXEC SQL EXECUTE PURGE USING :birth-year;
...

在这里,y实现没有实现宿主变量的定义,导致在里面他仅仅只做一个占位符,而birth-year事先定义了,所以他是一个宿主变量;在执行时,将以用户输入的birth-year的值来替换y。其中USING就是用来替换的;当然,如果你事先包含了多个占位符,那么在执行时,将会根据其在语句中出现的先后顺讯,在USING后用宿主变量依次替换。


查询类的动态SQL

查询类的动态SQL须返回查询结果。因为查询结果是单元组还是多元组,往往不能在编写应用程序的时候确定,所以动态SQL一律以游标取数。

...
EXEC SQL BEGIN DECLARE SECTION;
char sqlstring[200];
char SNO[7];
float GRADE;
short GRADEI;
char GIVENCNO[6];
EXEC SQL END DECLARE ARE SECTION;
char orderby[150];
strcpy(sqlstring,"SELECT SNO,GRADE FROM SC WHERE CNO = :c");
 /*提示用户输入ORDER BY 子句*/
printf("Enter the ORDER BY clause:");
scanf("%s",orderby);
/*提示用户输入要查询成绩的课程号*/
printf("Enter the course number:");
scanf("%s",GIVENCNO);
/*准备查询格式*/
EXEC SQL PREPARE query FROM: sqlstring;
/*说明游标*/
EXEC SQL DECLARE grade-cursor CURSOR FOR query;
/*打开游标*/
EXEC SQL OPEN grade -cursor USING :GIVENCNO;
/*取数*/
while(TRUE)
{
	EXEC SQL FETCH grade-cursor
	INTO :SNO,:GRADE,:GRADEI;
	if(SQLCA.SQLCODE == 100)
		break;
	if(SQL.CA.SQLCODE < 0)
		break;
	/*以下处理从游标所取的数据,从略*/
	...
}
/*关闭游标*/
EXEC SQL CLOSE grade-cursor;
...

在上例中,由用户动态输入ORDER BY子句和课程号。由于ORDER BY 子句位于SQL语句的末尾,在其后要加分号。如果需要,整个SQL语句也可以动态构成,这相当于在嵌入式SQL中增加了交互功能,这种功能在有些场合是很有好处的。


5.21、PowerBuilder中的嵌入式SQL语句

前面我们说了在C语言里面使用嵌入式SQL语句;而在这一小节,我们要论述如何在PowerBuilder里使用SQL。

实际上,在PowerBuilder使用的SQL和标准SQL差别很小,但是要求每一句都要以分号结尾。


在PowerBuilder中的查询语句

SELECT FieldList
	INTO VarList
	FROM TableName
	WHERE Criteria
	[USING TransactionObject];

【说明:在这里其他的没有什么好叙述的,唯一要说的就是VarList,VarList为接收数据的主变量名列表,当然,这个查询结果一般是一条元组,所以我们称这个SQL语句为不适用游标的SQL语句,其查询的结果必须是单个元组,即单条记录。并且同样的,如果想要写嵌入式SQL语句,那么不属于SQL语句的变量一定要在前面加冒号以示区分。


在PowerBuilder中的插入、更新和删除语句

插入语句、更新语句与删除语句和标准SQL语句没有任何不同,这里就不写出来了。


PowerBuilder中游标的使用

刚才介绍的嵌入式SQL查询语句,其查询结果必须是一条记录,而一般情况下,查找语句查询结果都是多条记录,但高级语言一次又只能处理一条记录,因此,嵌入式SQL在PowerBuilder也提供了游标机制,先将多条记录存放在游标中,然后一次一条地传送给宿主程序处理,从而把对集合的操作转换为对单个记录的处理。


后半部分讲SQLserver

在这一部分,如果有SQL中没讲到的知识,那就说明和MySQL通用,所以后面不过多累述。

*5.1、SQLserver简介

SQL Server 是Microsoft 公司推出的关系型数据库管理系统。具有使用方便可伸缩性好与相关软件集成程度高等优点,可跨越从运行Microsoft Windows 98 的膝上型电脑到运行Microsoft Windows 2012 的大型多处理器的服务器等多种平台使用。

Microsoft SQL Server 是一个全面的数据库平台,使用集成的商业智能 (BI)工具提供了企业级的数据管理。Microsoft SQL Server 数据库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能,使您可以构建和管理用于业务的高可用和高性能的数据应用程序。


*5.2、数据库的建立

在SQLserer里,建立数据库有两种方式,一种是手动创建

image-20211021095955255

当然,这个创建比较简单,下面介绍用命令去创建数据库,这个命令并不是SQL语句,不通用。

create database ChengYuHaoMei /*指定创建的数据库的名称*/
ON
(NAME = ChengYuHaoMei_data,
	FILENAME = 'C:\Database\ChengYuHaoMei_data.mdf',
	SIZE = 10,
	MAXSIZE = 50,
	FILEGROWTH = 5)
LOG ON 
(NAME = 'ChengYuHaoMei_log',
	FILENAME = 'C:\Database\ChengYuHaoMei_log.ldf',
	SIZE = 10,
	MAXSIZE = 50,
	FILEGROWTH = 5)

【说明:这里的参数不是啥考点,需要的去黄德才老师的书找即可。】


*5.3、基本表的定义

这里的建表和MySQL里面的都一样是用SQL语句,所以用那里的格式就对了,下面建几张表,后面要用。

USE ChengYuHaoMei
GO
CREATE TABLE Depts
(Dno CHAR(5) PRIMARY KEY,
Dname CHAR(20) NOT NULL)

CREATE TABLE Students
(Sno CHAR(5)PRIMARY KEY,
Sname CHAR(20) NOT NULL,
Ssex CHAR(2),
Birthday DATE,
Dno CHAR(5),
CONSTRAINT FK_Dno FOREIGN KEY(Dno)REFERENCES Depts)


CREATE TABLE Courses
(Cno CHAR(6)PRIMARY KEY,
Cname CHAR(20),
Pre_Cno CHAR(6),
Credits INT)

CREATE TABLE Reports
(Sno CHAR(5),
Cno CHAR(6),
Grade INT CHECK (Grade >= 0 AND Grade <= 100),
PRIMARY KEY (Sno,Cno),
CONSTRAINT Student_Report FOREIGN KEY(Sno) REFERENCES Students,
CONSTRAINT Student_Courses FOREIGN KEY(Cno)  REFERENCES Courses)

【注:在SQLserver里面和Mysql不一样的是,mysql是输入use databasename才会跳转到名为databasename的数据库,而SQLserver需要在写sql的页面最开头附加,保证sql语句作用于对应的数据库。】


*5.4、插入数据

INSERT
INTO Depts
VALUES ('D01','自动化'),
	('D02','计算机'),
	('D03','数学'),
	('D04','通信'),
	('D05','电子')


INSERT
INTO Students
VALUES('S01','王建平','男','1995-10-12','D01'),
('S02','刘华','女','1997-08-21','D01'),
('S03','范林军','女','1998-02-11','D02'),
('S04','李伟','男','1996-12-22','D03'),
('S05','黄河','男','1999-10-31','D03'),
('S06','长江','男','1994-04-08','D03')

insert into Courses
(Cno,Cname,Pre_Cno,Credits)
values
('C02','数据结构','C05',2),
('C03','数据库','C02',2),
('C04','DB_设计','C03',3),
('C06','网络原理','C07',3),
('C07','操作系统','C05',3)

insert into Courses
(Cno,Cname,Credits)
values
('C01','英语',4),
('C05','C++',3)

insert into Reports
values('S01','C01',92),
('S01','C03',84),
('S02','C01',90),
('S02','C02',94),
('S02','C03',82),
('S03','C01',72),
('S03','C02',90),
('S04','C03',75)

【说明:这里也没什么好说的,详见MySQL】


*5.5、条件查询

【注:这里所有的知识基本在MySQL那一章都讲过,这里的案例可以当成练习来做】

案例1:查询系别编号D03的全体学生的学号(Sno)和姓名(Sname)

select Sno,Sname from Students where Dno = 'D03'

案例2:查询所有年龄在18~22岁的学生姓名及其各自的年龄

select Sname,datename(yyyy,getdate())-year(birthday) age from Students where datename(yyyy,getdate())-year(birthday)>=18 and datename(yyyy,getdate())-year(birthday)<=22

案例3:查询所有年龄在18~22岁的学生姓名及其各自的年龄(使用between and查询)

select Sname,datename(yyyy,getdate())-year(birthday) age from Students where datename(yyyy,getdate())-year(birthday) between 18 and 22

案例4:查询所有年龄不在18~22岁的学生姓名及其各自的年龄

select Sname,datename(yyyy,getdate())-year(birthday) age from Students where datename(yyyy,getdate())-year(birthday) not between 18 and 22

案例5:查询系别编号为D01、D02和D03学生的学号、姓名和性别

select Sno,Sname,Ssex from Students where Dno = 'D01' or Dno = 'D02'or Dno = 'D03'

案例6:查询系别编号既不是D01、D02,也不是D03的学生的姓名和性别

select Sname,Ssex from Students where Dno  not in('D01','D02','D03')

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

select Sname,Sno,Ssex from Students where Sname like '刘%'

案例8:查询姓刘且全名不多于三个汉字的学生的姓名和出生日期

select Sname,Sno,Ssex from Students where Sname like '刘__'

案例9:查询所有不姓刘的学生姓名和出生日期

select Sname,Birthday from Students where Sname not like '刘%'

案例10:查询课程名为“DB_设计”的课程号和学分

select Cno,Credits from Courses where Cname = 'DB_设计'

案例11:查询以“DB_”开头,且倒数第2个汉字字符为“设”的课程的详情

select * from Courses where Cname like 'DB_%' and Cname like '%设_'

案例12:假设某些学生选修课程后没有参加考试,所以有选课记录,但没有考试成绩。试着查询缺少成绩的学生的学号和对应的课程号。

select Sno,Cno from Reports where Grade is null

案例13:查询所有有成绩的学生学号和课程号

select Sno,Cno from Reports where Grade is not null

案例14:查询选修了C03号课程的学生的学号和成绩,并且成绩降序排列

select Sno,Grade from Reports where Cno = 'C03' order by grade desc

案例15:查询全体学生情况,查询结果按所在系的系别编号升序排列,同一系中的学生按年龄降序排列

select * from Students order by Dno,datename(yyyy,getdate())-year(Birthday) desc

案例16:查询学生总人数

select count(*) '总人数' from Students

*5.6、分组函数

案例1:查询选修了课程的学生人数

select count(distinct(Sno)) '选修课程学生人数' from Reports 

案例2:计算选修了C01课程的学生的平均成绩

select avg(grade) from Reports where Cno = 'C01'

案例3:计算选修C01号课程的学生最高分数

select  max(grade) '选修C01学生的最高分' from Reports where Cno = 'C01'

案例4:求各个课程号及相应的选课人数

select Cno,count(Sno) '对应选课人数' from Reports group by Cno

案例5:查询选修了3门或3门以上课程的学生学号

select Sno from Reports group by Sno having count(Cno)>=3

*5.7、连接查询

案例1、查询每个学生及其选修课程的情况

select * from Students s,Reports r where s.Sno = r.Sno

案例2、查询每个学生的学号、姓名、选修的课程名、成绩和系别名称

select S.Sno,S.Sname,C.Cname,R.grade,D.Dname 
from Students S,Reports R,Courses C,Depts D
where S.Sno=R.Sno and R.Cno = C.Cno and S.Dno = D.Dno

案例3、查询每一门课的间接先修课(即先修课的先修课)

select C1.Cno,C1.Cname,C2.Pre_Cno from Courses C1 join Courses C2 on C1.Pre_Cno = C2.Cno

*5.8、嵌套查询

在这里面,我们会见到很多在MySQL一章中没见过的谓词(也就是高中数学说的存在、不存在这些量词等等)

案例1、查询选修了编号为C02的课程的学生姓名和所在系别编号(带谓词IN的嵌套查询)

select Sname,Dno from Students where Sno in (select Sno from Reports where Cno = 'C02')

【注:这里由于子查询返回的是多个值,所以Sno要in于子查询的结果而不是=】

案例2、查询与“李伟”在同一个系学习的学生学号、姓名和系别编号

select Sno,Sname,Dno from Students where Dno = (select Dno from Students where Sname = '李伟')

或者

select Sno,Sname,Dno from Students where Dno in (select Dno from Students where Sname = '李伟')

【注:由案例一的注可知第二种写法最为妥当。】

案例3、查询选修了课程名为“数据结构”的学生学号和姓名

select Sno,Sname from Students where Sno in(select Sno from Reports where Cno in (select Cno from Courses where Cname = '数据结构'))

案例4、查询系别编号不是D01系的学生,且其年龄不超过D01系的所有学生年龄的学生学号、姓名、性别、出生日期和系别编号

select Sno,Sname,Ssex,Birthday,Dno from Students where 
Dno != 'D01' and year(Birthday) <= all(select year(Birthday) from Students where Dno = 'D01')

案例5、查询所有选修了编号C01课程的学生姓名和所在系别编号

select Sname,Dno from Students where exists (select * from Reports where Sno = Students.Sno and Cno = 'C01')

案例6、查询选修了所有课程的学生姓名和所在系别编号

select Sname,Dno from Students where not exists (select * from Courses where not exists (select * from Reports))

*5.9、集合查询

案例1:查询系别编号为D02的学生或年龄不大于20岁的学生信息

select * from Students where Dno = 'D02' union select * from Students where datename(yyyy,getdate())-year(Birthday)<=20

案例2:查询系别编号为D03中年龄不大于20岁的学生

select * from Students where Dno = 'D03' and datename(yyyy,getdate())-year(birthday)<=20

案例3:查询系别编号为D03的学生中年龄大于20岁的学生

select * from Students where Dno = 'D03' and datename(yyyy,getdate())-year(Birthday)>20

*5.10、授权

SQL用GRANT语句向用户授予操作权限,一般格式为:

grant 权限 权限名
on 对象类型 对象名
to 用户

案例1、把对Students表的全部操作权限授予用户User1和User2

grant all privileges on Students to User1,User2

案例2、把对基本表Reports的查询权限授予所有用户

grant select on Reports to public

案例3、把查询Students表和修改学生姓名的权限授予用户User3

grant update(Sname),select on Students to User3

案例4、把对表Reports的insert权限授予User4用户,并允许他将此权限授予其他用户

grant insert on Reports to User4 with grant option

案例5、DBA把在数据库student_Mis中建立表的权限授予用户User6

grant createtab on database student_Mis to User6

【注:收回权限和授权类似,只需把grant改为remove即可】


第六章、数据库管理系统

6.1、DBMS核心的模块结构

数据库管理系统简介

数据库管理系统(DBMS)是数据库系统的核心,它对数据库系统的功能和性能有决定性影响。

目前商品化的DBMS主流是以关系模型或者扩充了对象概念的关系模型为基础的。


数据库操作系统实现过程

这里要对这幅图说明一些东西,从上往下操作:首先这里核心是语义分析和查询处理,操作系统是在在其之上做文件管理等操作,而在这里,接口分为两大类,UFIAPI,其中UFI是提供给用户进行即席访问的。即席访问是什么意思呢?实际上就是有个人在窗口这里不断的给数据库提供指令,提供一条系统执行一条,实际上UFI就是一个交互式接口。

image-20211003013811336

说明:从上到下,从应用开始,我们在应用里面抽出数据库语句,这个过程是交给授权检查去抽离的,然后转换为一种最基本的数据库语言,如SQL,交词法和语法器分析,产生相应的语法树。然后进行授权检查,检查用户是否有权访问语法树中所涉及的数据对象。如果授权检查通过,则继续执行,如果不通过那就驳回消息不执行。

比如他发现应用里面有数据库语句是要求要查那张表,那数据库这边会马上检查用户是否有权限去查这个表,如果没有权限即驳回。

通过授权管理后,将语法树往下继续,会经过语义分析和查询处理,一般都是用基本的SQL语句去处理,在查询处理这个地方,我们通常还存在一些存取路径的选择问题,也就是所谓的查询优化。

经过以上操作后,就形成了SQL语句的执行计划,执行也就是去做从应用传过来那些相应的操作,在操作中还需要注意并发控制DLL还有恢复机制

  • 并发控制:SQL语句在执行的过程中,为了防止多用户一起访问数据库导致数据库数据引起不一致,所以会采取并发控制。
  • DLL:DLL是存放SQL函数可执行代码的动态链接库。
  • 恢复机制:能够是数据库恢复到最近的一致。
  • 访问原语:实际上,我们可以把这一层一层的关系看做是下一层为上一层提供了一个最小的API,这个API函数我们叫做访问原语

实际上,在物理层之外,我们都是对SQL语句动手,而在物理层内,我们是转为对文件系统动手。

从下往上传:

首先磁盘给操作系统的只能是状态信息或者物理块,也就是说把东西交给操作系统与否,给了写TRUE,没给写FALSE,除了传递这些还有传递一些字符流。这时候物理块里面是有信息的,只是没有人去包装他,现在暂时无法解析他。但是经过物理层的加工包装,这些消息和数据可以变成表啊,元组这样的东西了,已经可以形成一些明显的概念了。再往上,物理层再度深入加工,将已知的消息包装成用户能看懂的消息了,比如本来只是一个查询语句往上就会包装成很漂亮的表格。

在这里要说明的是,单纯从SQL的逻辑来讲,并发控制和恢复并不是每个数据库系统都必须的,只有在大型商业的多用户访问数据库才是必须的。


6.2、DBMS进程结构

DBMS以进程为单位,在操作系统上运行的系统软件。

概述

不管我们用什么语言写的软件,当我们要调用数据库时,应用开启作为一个进程,调用数据库数据库也会创建一个核心进程,创建完成后,DBMS就会为这两个进程之间构建一个通讯的管道,在操作系统中,我们有一个pipe它是单向的通讯机制,所以如果要实行双向通讯机制的话,我们就要再创建一个管道。这样的话,一个作为写管道,一个作为读管道。如图:

image-20211118121323800

这一部分的详细知识可以去笔记操作系统2.1.4中寻找。


DBMS分类

专为单个事务服务的DBMS核心进程(也被称为前台进程)

每个事务都有一个与之对应的DBMS的核心进程,它是事务的执行者,并代表事务与用户和系统对话。由于DBMS核心进程可以和用户直接对话,所以我们一般也叫他前台进程。

为公共服务的后台进程

后台进程无法和用户直接对话,而由DBMS调用。一般我们将一些公共操作或事务无法承担的操作由后台进程来执行,如写入数据库后更新后的内容(一般交给事务提交后执行)、预先读取可能用到的物理块、异常结束事务的善后处理等。

前台进程由于是为事务服务,所以寿命和事务一样,而后台进程是常驻进程。一般不影响事务的提交,其执行时间由系统掌控,如在适当时间成批写入数据库的更新内容

进程也是有缺点的

缺点的由来

image-20211003014117017

当你的应用软件创建了进程后,DBMS也会在前台创建一个核心进程,其中他们用管道连接,应用软件利用SQL语句发给DBMS核心进程,而核心进程发结果给应用。

但是这样的话,你会发现,一个应用连接一个DBMS核心进程,那么如果有多用户,就会导致DBMS核心进程急剧膨胀,操作系统性能下降。


缺点

  1. 进程的创建、撤销、切换、通信的开销较大

  2. 由于CPU的处理能力和内存空间的限制,计算机系统可并发运行的进程数是有限制的。

  3. 如并发的进程数过多,则进程切换频繁、系统开销增大,处理效率反而下降。操作系统一般设有并发度的限制,并且做为上限。

  4. 不利于事务共享内存空间。从以上的叙述我们可以知道,应用和进程是单连接的,也就是说,进程是有各自的独立内存空间,受操作系统保护,不能访问彼此的内存空间。

在数据库系统中,所有的事务共享一个DBMS,许多重要资源都是共享的,如数据目录、锁表、各种内存缓冲区。


线程

在进程中,我们还可以创建线程,作为并发运行的调度单位。这样的话,也就是说,线程占的内存比进程小得多。

仔细来说:

  • 线程的主要资源来源于进程,属于线程本身的专用资源很少。

  • 描述线程的状态表要比进程的状态表容易的多。

  • 线程之间可以通过内存通信。

  • 线程切换的开销要比进程切换低的多,甚至不需要通过操作系统。

所以,我们通常叫线程叫做轻量进程,进程叫做重量进程


DBMS进程开启后的状态

DBMS进程刚开启的时候,有一些东西是已经有的:

  1. Daemon线程

    起一个监控的作用,他会监视应用程序访问数据库的一些请求。比如我们和SQL的ip地址和端口号连接的时候,daemon线程就在监视是否连接成功。

  2. catalogue目录

    他是一种原数据,我们存储在数据库的数据提炼的大纲数据就是目录。比如我们写select* from emp;数据库如何查看有无这张表?就是通过目录去查找。

  3. 封锁表

    用来控制对锁的申请,他是一个公共的资源

  4. buffer缓冲区

    内存的缓冲


建立进程的过程

image-20211003014318014

假如现在我们有一个应用程序,然后通过connect请求调用数据库,在调用的时候,Daemon实际上就会捕获到这个请求,从而为这个应用程序的进程创建一个相应的DBMS核心线程,并且同时建立一个通讯的管道;这个管道如果是在机器上就用pipe,网络环境就用socket;建立好后,接下来的操作就和6.1中DBMS核心的模块结构里一样了,经过操作后数据库通过result管道把结果传回应用程序。

同样的,如果第二个应用程序请求调用数据库,也是重复上述的操作;不过好在应用程序这样的话只创建了一个进程,但是创建了多个线程。

【说明:DAEMON实际上只负责监听进程是否建立了,一旦确定建立后他就会消失,等待下一个进程连接。】


6.3、数据目录

数据目录是一组关于数据的数据,也叫元数据。

在高级程序设计语言中,程序中的数据一般容易失效。

而在DBMS的任务是管理大量的、共享的、持久数据。有关数据的定义和描述必须长期保存在系统中,一般把这些元数据组成若干表,称为数据目录,由系统管理和使用。


数据目录怎么生成的?

他是初始化的时候由系统自动生成。数据目录是被频繁访问的数据,同时也是十分重要的数据,数据目录没有建立的时候,任何SQL无法查询,无法生效,所以,数据目录影响全局。一般DBMS不会让你更新数据目录,DBMS会自动帮你更新,他只允许你查询就够了。


6.4、作业

1、如果事务不遵守ACID准则,会对数据库产生什么后果?为什么一般程序中不提ACID准则?


2、何谓线程?试着分析现代DBMS趋向采用多线程进程结构的原因。


3、试着分析当前客户/服务器系统结构和三层客户/服务器系统结构流行的技术背景。


4、试着叙述数据目录的作用、内容和管理的特点。


第七章、数据库的存储结构

7.1、数据库访问管理-文件组织

记录和文件的关系

  1. 传统的数据模型都以记录为基础。记录的集合构成文件。

  2. 文件须按照一定的结构组织和存储记录,访问有关的记录须经由一定的存取路径。对数据库的操作最终落实到对文件的操作。


文件访问的方式

  • 查询文件的全部或相当多的记录
    • 一般把访问15%以上记录的查询都归入这一类。
为什么?

因为根据统计学规律,如果有15%以上的记录在硬盘的盘的话,那么我们我们可以理解为这15%几乎遍布所有的物理块,我们必须找出全部的物理块才能找到这15%的记录。
  • 查询某一特定记录

  • 查询某些记录

    • 一般把访问15%以下记录的查询都归入这一类
  • 范围查询

  • 记录更新

    • 比起查询操作,记录的更新要复杂的多,因为在更新的时候会引起记录位置的调整和相应的修改(比如索引就要改)
拓展:缓冲池和物理块

在市面上,磁盘中的数据划分为大小相等的物理块,每个物理块之间要留有间隙,间隙通常放的是初始化信息和下一个物理块的地址信息。

实际上,我们读取磁盘的时候,并不是像1kb1kb那样的字节去读取的,我们是一个物理块一个物理块去读的。所以,我们通常把磁盘叫做块设备。像linux来说,一般一个物理块是1024个字节。现在假如我们有一个关系想要放入文件当中,此时关系中一个元组占一百个字节,那么我一个物理块能放10个元组左右,那么到时候我们读硬盘的时候,就是一块一块读的。比如我们查询的时候就是一块一块找,看这一块有无我们需要的东西。

数据库对文件的要求

DBMS有自己的一套文件管理系统,之所以不用我们操作系统(比如window)的那一套,主要有以下原因:

  1. DBMS为了实现其功能,须在文件目录、文件描述块、物理块等部分附加一些信息,而传统的文件系统是不提供这些信息的。
  2. 传统的文件系统主要面向批处理,而在数据库系统中,常常要求即席访问、动态修改。这就要求文件结构能够适应数据的动态变化,提供快速访问路径。
  3. 传统的文件基本上是为某一用户或者某类用户服务的,用途比较单一,共享的程度较低;而数据库中文件是供所有数据库用户共享的,甚至有些用途是不可预知的。这就要求数据库的文件结构兼顾多方面的要求,提供多种访问路径。
  4. 如果采用操作系统的文件管理系统作为DBMS的物理层的实现基础,则DBMS对操作系统的依赖不大,不利于DBMS的移植。
  5. 传统的文件一旦建立,数据量是比较稳定的;而数据库中文件的数据量变化较大,数据库中的文件结构应能适应这样的变化。

为了适应文件的访问,DBMS提供了多种文件结构

堆文件

堆文件就类似堆东西一样,不停的往尾巴放东西,不停的在尾巴后插入数据。但是堆文件不一定是顺序结构,可以是链表结构。在查找的时候我们要通过顺序扫描,即从头到尾找,而且最坏的情况是你找到最后才找到。

很明显,堆文件对于查询文件的全部或相当多的记录有相当好的效果


直接文件(哈希文件)

直接文件中,将记录的某一属性用哈希函数(在数据库里面我们以后叫做散列函数)直接映射成记录的地址,被散列的属性称为散列键。

这话可能听着憋屈,我们用一个例子说明

image-20211009211559735

假如这里有个表,里面的记录我们叫元组嘛,然后我们将记录的某一属性(比如说员工编号)映射到另外一个表上,以后我们在那个表想要寻找表里的某个记录,我们就可以根据散列表里面属性对应的地址找到表中相应的元组,在这里的操作里员工编号作为我们的散列键。


索引文件(堆文件配合B+树索引)

如果我们是要查找某一条元组,为了找出这条元组去堆文件里做顺序扫描来查找,显然效率不高,所以我们一般是在主键上建一个B+树索引,当我们要找某个值的时候,根据叶节点(相当于路标)去查找这个值所在的叶节点。

如果我们要做范围查询,利用B+树索引叶节点上面的链表也可以做双向索引。

索引可以建立多个,甚至是建立复合索引都可以。但是索引并不是建立得越多越好,我们是在建立索引的代价和效率两者之间折中的。


7.2、数据库访问管理-索引技术

数据库存储介质的特点

讲这个之前先说一个磁盘的结构

image-20211008110848492

说明:首先,左边有很多盘片,右边是磁头臂,下面有个两个马达,盘片马达驱动磁盘旋转,磁头臂的马达可以驱动磁头臂移动,方便读取磁盘片上的数据。

然而如果你用操作系统中的文件系统存储的话,文件在磁盘上的地址是不确定的(这里指的不是路径,是内存地址),如果是利用FAT(微软发明的文件配置表)和NTFS去寻找文件位置,他会依靠之前存取的时候存储的碎片化位置去寻找,这样就会造成寻找文件的效率变低,如果你想把文件存在同一个磁盘片中,而且物理地址连续,那么操作系统是做不到的。

摩尔定律是英特尔创始人之一戈登·摩尔的经验之谈,其核心内容为:集成电路上可以容纳的晶体管数目在大约每经过18个月便会增加一倍。换言之,处理器的性能每隔两年翻一倍。但是在磁盘中,有个固定操作叫寻道,他的过程就是将磁力壁定在磁盘片上的某条轨道(磁道)切割磁力线,从而寻找文件的地址。在这个过程中,通过移动磁头臂来寻道,这是一个机械移动,对于应用在电子设备的摩尔定律显然不适用,也就是说,无法通过对硬件的升级来解决效率的问题。所以如果想提高效率问题,一定要把文件的内容根据物理地址按顺序排列好,减少磁头的移动。

image-20211003092136162

每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道…磁盘的数据存放就是从最外圈开始的。

image-20211003092215535

根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。

柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。

活动头磁盘的存取实际由三部分构成:寻道时间、旋转延迟实际和传输时间。活动头磁盘通过磁头臂的机械移动来寻道,寻道时间要比其他两部分时间大得多,所以为了有效地访问磁盘,我们应该尽可能地一次I/O连续访问较多的数据,减少I/O次数,从而减少寻道和选择延迟的次数。

在早期的DBMS中,常由操作系统来分配数据库所需的物理块,逻辑上相邻的物理块分散到磁盘的不同区域。在连续访问数据库中的数据时,性能严重下降,所以在现代DBMS中,都改由DBMS在初始化时向操作系统一次性申请所需的磁盘空间,让DBMS去分配。

  1. 连续分配法:有点像顺序表,即物理位置上是连续的。
  2. 链接分配法:有点像链表,也就是逻辑位置上连续。
  3. 簇集分配法:物理连续逻辑也连续。
  4. 索引分配法:每个文件都有一个逻辑块号与其物理块地址对照的索引,通过索引,你可以找到文件中任何一块的地址。

在数据库设计中,如果一直频繁地用某个属性来检索某一条元组的话,那我们就可以根据这个属性把对应的元组用簇集的方式去建立,这样的话查找的时候效率非常快。但是,我们建簇集这个属性一定要很少更新,否则维护簇集的代价会很大。


索引中常用的索引技术

  • B+树索引

  • 簇集索引

  • 倒向文件

  • 动态散列

  • 等等。。


7.3、作业

1、为什么扩大数据库系统的缓冲区可以提高其性能?


2、堆文件、直接文件和索引文件各适用于哪种场合?


3、为什么主键上一般都建有主索引


4、当查询的元组数在总元组数中所占的比重较大时,为什么用非簇集索引有时反而不利?为什么再次情况下用簇集索引有利?


5、在数据库系统中,散列一般用得不如索引多,试分析其原因。


6、如何减少B-树的级数


第八章、查询处理和优化

8.1、概述

查询优化

用户不必关心查询语言的具体执行过程,而由DBMS确定合理的、有效地执行策略。


查询优化实际上是在做什么呢?

实际上当用户写了一条SQL语句后,查询系统会对sql语句做一个重写,然后变成一个执行效率更高的形式。


查询优化的途径

  • 把查询语句进行变换,改变基本操作的次序,然后查询起来更加有效,这个叫做代数优化

  • 根据系统所提供的存取路径,选择合理的存取策略,也就是对存取的方法进行优化,我们叫做物理优化

  • 有限查询优化根据启发式规则和选择执行的策略去先做选择,投影等一元操作,然后做连接操作,这叫规则优化

  • 在很多的优化策略里面,选取代价最小的优化执行策略,叫做代价估算优化


查询优化的注意点

从上面这个例子我们知道,变换后求解结果的效率变快了,但是如果变换的过程很复杂,花费的代价比最开始计算还难,那也是不值得的。


查询树和语法树

数据库查询语言的处理过程和一般高级程序设计语言相仿。都是经历了解释和编译的过程。

image-20211008111059760

还是这个图,应用调用接口传入命令语句,词法和语法分析器会去解释命令语句,在这里他就会用到一个语法树,将命令语句转变为SQL语句,然后授权检查是否有权限;当有权限后,就会执行sql语句,这里就涉及到查询优化的问题了,利用查询树来优化该SQL语句。


8.2、代数优化

代数优化

代数优化是对查询进行等效变换,以减少执行的开销。最常用的变化原则是,尽量缩短查询过程中的中间结果。由于选择、投影等一元操作分别从水平方向或垂直方向减少关系的大小,而连接、并等二元操作本身的开销较大,而且很可能产生大的中间结果。因此在变换查询时,我们让选择和投影先做,再做二元操作。


简单来说,假如现在有两张10000个元组的表,想做查询和连接两个操作,根据笛卡尔乘积现象,简单来说,假如现在有两张10000个元组的表,根据笛卡尔乘积现象,当两张表进行连接查询的时候,没有任何条件进行限制,最终的查询结果条数是两张表记录条数的乘积。也就是要做10000乘以10000次乘积,这无疑是很大的,那如果我们根据选择操作先筛选掉一部分,然后在做连接操作,那么执行效率就会大大提高。


8.3、操作优化

操作优化重点考察连接操作优化。

连接操作的代价估算

估算连接操作花费的代价,也就是估计连接结果的大小,所以我们在这里引入了一个选择因子的概念。


选择因子

英文名join selectivity(简称js),

\[js = |RS|/|RS| = |RS|/|R|·|S|。 \]

如果无连接条件C,则连接笛卡尔乘积,js=1。如果没有元组满足连接条件C。则js=0.一般地,0<=js<=1。


在连接操作优化里去估算代价我们用以下几种方法

  • 嵌套循环法

  • 利用索引或散列寻找匹配元组法

  • 排序归并法

  • 散列连接法


8.4、连接操作实现和优化

连接是开销很大的操作,历来是查询优化考察的重点。


嵌套循环法

大体来说他像我们编程语言里面的循环嵌套一样。

假如现在有两个关系(表):R和S

image-20211008111140906

说明:如果说我们要进行连接操作,本质上连接操作就是加了条件的笛卡尔乘积。那么我们是利用嵌套循环法就是先用R中的一条元组去匹配另外S表中的所有元组(S表中的所有元组相当于内层循环),然后R中的第二条元组再去匹配S中的所有元组,以此类推,直到R的所有元组和S的所有元组比较完为止。

在整个连接过程中,R只要扫描一次,S就要扫描n次(也就是S表中所有的元组),从程序设计的观点来看,R的扫描相当于程序的外循环,而S相当于程序的内循环,因此我们把R叫做外关系,S叫做内关系。当然,两张表的地位可以互换,这取决于两者扫描的IO代价。


在前面的章节中,我们曾经谈论过磁盘。

如果说R中表中一条匹配S表中一条元组就要I/O一次,那么这显然是效率极低的。但是实际上,在操作系统中硬盘是一种块设备,他是以物理块为单位,也就是说,假如一个物理块是1040M,R中一条元组为100M,那么一个物理块就可以容纳R中十条元组去匹配S表


也就是说,我们现在设置两个缓冲区,分为外循环和内循环。

外循环的缓冲区可以存放外循环的物理块,内循环的缓冲区可以存放内循环的物理块,当两张表做连接,我们采用循环嵌套法的时候,我们不再是一条一条元组匹配,而是如上所说一个物理块匹配内循环的一个物理块,如果当你内存足够大,你可以设置多个缓冲区,存放更多的物理块,如果这张表不大,甚至能做到一波全部匹配完,大大减少了匹配效率。

说明:
事实上,关系不是以元组为单位,而是以物理块为单位从磁盘读取到内存。设系统分别为R,S各提供一个缓冲块,p_R 为R的块因子(每块含有的元组数),则R每次IO一次不是取一个元组,而是取p_R 个元组(也就是说S扫描一次可以R中的多个p_R 做比较);这么说的话,假设R的物理块数是b_R,那么我们有b_R=[n/p_R],也就是物理块数等于R表总元组数除去每一个物理块所能容纳的元组数。

由上面的说明我们得知,用物理块去匹配S能提高效率,由此我们受到启发,如果为R增加缓冲区,那么每次多去几块物理块,是不是就可以减少S的扫描次数。


利用B+树索引或哈希索引寻找匹配元组法

细说:这里不禁要说这个算法实在牛逼啊!

具体操作是这么回事:

我们在两个表中还是那样:

image-20211003101331839

R表作为外循环,S表作为内循环,然后给R表一个缓冲区,S表带有B+树索引。


之后R表一个物理块读进来,他要找S表中匹配的元组,就可以通过B+树索引找到物理块中符合连接条件的元组的地址,直接进行匹配,大大节省了寻找匹配元组的效率,不需要像嵌套循环法那样去扫描。


但是这个需要考虑代价:假如在某个属性上面,重复值的数量达到了总元组数的百分之二十以上的话,用索引反而不合算。什么意思呢?如果有过多的重复值,也就意味着你要在建树的时候有许多重复值,这就会导致在辨别重复值的时候花费过多的代价,这样还不如使用循环嵌套法来的方便。


散列连接法

具体来说就是:他把R表和S表中以后做连接有可能产生的结果全部哈希到一个柜子里,柜子有多个抽屉,不同的抽屉代表不同的结果,以后只要需要两张表的连接,只需要在哈希出来的这个柜子(表)里面去找就行了。


但是考虑到代价,也就是说你这个R表和S表不会频繁的去更新,否则的话会打乱哈希出来的表,定期做维护代价也会很大。

image-20211003101632754

8.5、作业

1、试着比较数据库语言的解释和 编译两种实现方式


2、试着单纯讨论代数优化的局限性


3、使用C语言编写一个选择操作的规则优化程序


4、使用C语言编写一个连接操作的规则优化程序


第九章、事务管理

9.1、恢复机制概述

恢复引论:

数据对一个单位是非常重要的。也就是说,数据库的失效常常导致一个单位的瘫痪。


当数据库出现故障我们如何处理?

其实重点就两个字:防、治

数据库出现故障的时候,如果是防,我们就尽可能提高系统的可靠性;如果防不住,我们就治,在数据库系统发生故障后,把数据库恢复到之前的状态。一般来说,我们做第一手的准备不足以保证数据库数据的安全,比如突然停电,这种东西谁都防不住,所以通常在第一手准备的情况下,我们会去做第二手准备,而第二手准备,就是我们讨论的重点:恢复技术。


所以总结来看,我们设计的恢复机制一定有如下的东西:

一、数据冗余

硬件和软件出现故障,就会导致运行出现差错,也就会导致数据库失效。故障是前因,差错是现象,失效是后果。为了恢复失效后果,我们必须留有后备的副本以备不时之需,这样来看的话,备份就会导致数据冗余是必需的。


这里的冗余和数据库设计的冗余不一样,那里的冗余指的是数据库结构为了追求三范式,而有些地方不合理造成的冗余;而我们上面说的冗余是留有数据做备份。


二、要能够检测出故障,不能出现故障了还没有停下来,稀里糊涂的继续操作下去。


恢复技术有哪些?

有三类


单纯以后备副本为基础的恢复技术(周期性转储)

这个是从文件系统继承过来的恢复技术。这里要继续讲下去又得讲一下磁带了。

磁带就相当于小时候的录音带,你甚至可以理解为他是读卡器,他是一种可以脱机的存储设备,但是容易损坏。

所以使用以后备副本为基础的恢复技术时,我们会把磁盘上的数据库周期性转储到磁带上,当然你也可以刻在光盘上。由于磁带可以脱机存放,可以不受系统故障的影响,所以存在磁带的那部分数据库副本我们叫后备副本

那你可以试想,数据库如此庞大,假如真的让你那个类似于U盘一样的东西去拷贝,拷贝一次需要多久?而且在拷贝过程中数据库不能改变,相当于冻结了数据库,暂停其运作;所以在取后备副本的时候由于时间问题,我们不能频繁的取,一般周末夜间数据库空闲的时刻才会去操作。

但是这样一来你就会发现一个问题:假如我取后备副本的周期过长,那就会导致假如在上一次取后备副本到数据库突然故障这段期间我做了大量的更新的话,那么就会导致这些操作全部白干。

所以,我们想到了提高这个恢复技术的办法,也就是增量存储。这实际上是周期性转储方法的变种。一般来说,数据库不会改动整个结构,只会改动其中一小部分,我们可以把那一小部分所对应的物理块拿出来高频(也就是周期很短)地转储,其他的物理块可以很久才转储一次,这样的话,一旦发生故障,马上就能恢复最近更新的那一部分的物理块,损失度大幅下降。

海量转储 增量转储
每次转储全部数据库 只转储上次转储更新过的数据

海量转储与增量转储的比较

1.从恢复角度看,使用海量转储得到的后备副本进行恢复往往更方便

2.如果数据库很大,事务处理又十分频繁,则增量转储更有效

img

由于这种方法实现起来很简单,所以在文件系统使用得很多,但是在数据库系统中只用于小型的和不重要的数据库系统。


以后备副本和运行记录(log或者journal)为基础的恢复技术

这种技术是企业里面用的最多的技术。

在SQLserver中,我们能看到建数据库的时候我们还会建一个日志文件log,这里就派生用场了。日志实际上就是一个流水账,我们也叫他是一个运行记录,记录你所有操作的流水;其中运行记录里面记了两样东西,一种是前像,一种是后像

前像(也就是老的数据,老值)(Before image,简称BI)

如果数据库被事务做了一次更新,那么我们就可以还没更新前数据库的数据涉及的物理块来恢复更新前的状态,也就是撤销更新,这种操作我们在恢复技术里叫做撤销(英文简称undo)。


后像(也就是新的数据,新值)(After image,简称AI)

同样的,我们更新后也有更新后数据涉及的物理块,如果更新的数据突然没了,我们可以根据这个物理块把更新的数据恢复到更新后的状态,也就是更新了然后我再做一次更新,也就是重做(英文简称redo)。

就拿修改表数据的相关操作来说,日志记录以下情况:

image-20211008111231545

事务状态:

记录每个事务的状态,方便在恢复的时候做不同的处理。每个事务在提交的时候面临两种结局:

第一种是提交了才结束,这标志着事务成功地执行了,只有在事务提交后,事务对数据的影响才能被其他事务访问。

第二种是由于事务本身或者外部的原因,事务失败,也就是说这时候可能事务只做了一半,要继续做的时候事务出问题终止了,这时候为了消除那做了一半的影响,我们会对其做出卷回

如果是数据库失效了,可以取出后备副本,然后根据运行的记录,如果没提交就将前像卷回,这叫向后恢复。如果事务已经被提交了,但是提交前和提交后中间添加的数据没了,那我们必要时用后像重做,这叫向前恢复


基于多副本的恢复技术

如果系统中有多个数据库副本,而且这些副本具有独立的失效模式,则可利用这些副本相互备份,用于恢复。所谓的独立失效模式是指各个副本不会因为故障而一起失效。为了尽可能的创建这种环境,各副本的支撑环境要尽可能地独立,比如不要共用电源、磁盘、控制器和CPU等等,反正就是尽可能不要共用东西就对了。这种技术在分布式数据库用的比较多。现在由于硬件价格下降,很多人都能买得起磁盘,所以在一些可靠性要求高的系统中,常常采用镜像磁盘技术;镜像磁盘技术为了使失效模式独立,两个磁盘系统有各自的控制器和cpu,但彼此可以相互切换。他的原理如下

  • DBMS自动把整个数据库或其中的关键数据复制到另一个磁盘上

  • DBMS自动保证镜像数据与主数据库的一致性,每当主数据库更新时,DBMS自动把更新后的数据复制过去,如图

img

出现介质故障时

  1. 可由镜像磁盘继续提供使用
  2. 同时DBMS自动利用镜像磁盘数据进行数据库的恢复
  3. 不需要关闭系统和重装数据库副本

img

没有出现故障时,镜像磁盘可用于并发操作,一个用户对数据加排他锁修改数据,其他用户可以读镜像数据库上的数据,而不必等待该用户释放锁。


9.2、事务和日志

事务英文名:Transaction


什么是事务?

一个事务是一个完整的业务逻辑单元,不可再分。

比如:银行账户转账,从A账户向B账户转账10000,需要执行两条Update语句。

update t_act set balance = balance -10000 where actno = 'act_001';

update t_act set balance = balance + 10000 where actno = 'act_002';

以上这个过程是一起的,如果不一起,就会导致转账这件事失败。也就是说,以上两条DML语句必须同时成功,或者同时失败,不允许出现一条成功,一条失败。

要想保证以上的两条DML语句同时成功或者同时失败,那么就需要使用数据库的“事务机制”。


和事务相关的语句只有:DML语句。(insert delete update)

为什么?因为它们这三个语句都是和数据库表当中的“数据”相关的。事务的存在是为了保证数据的完整性,安全性


假设所有的事务都能使用一条DML语句搞定,还需要事务机制吗?

不需要事务

但实际情况不是这样的,通常一个事务需要多条DML语句共同联合完成。


事务可以保证多个操作原子性,要么全成功,要么全失败。对于数据库来说事务保证批量的DML要么全成功,要么全失败。事务具有四个特征:ACID

  • A原子性:整个事务中的所有操作,必须作为一个单元全部完成(或者全部取消),即:要么不做,要么全做。
  • C一致性:在事务开始之前和结束之后,数据库都保持一致状态
  • I隔离性:一个事务不会影响其他事务的运行

像window如果你太久不关机,就会导致出现一些怪事,比如你搞个死循环,那你的window系统很容易崩溃,但是如果你在linux里面

  • D持久性:持久性说的是最终数据必须持久化到硬盘文件中,事务才算成功的结束。也就是说,一个成功执行的事务对数据库的影响是持久的,即使数据库因故障而受到破坏,DBMS也应该能够恢复。

事务案例

image-20211008111300372

说明:

假如现在做一个银行转账系统,A给B转了s元,如果A余额不足,那么就会提示:insufficient fund(余额不足),然后把A余额-s元的这个操作回滚。

如果余额足够,那么A-s,B+s,然后提示转账成功,然后事务结束,利用Commit提交事务。


日志是什么?

日志一般存储了操作的流水,这里我们前面已经讲过了;而日志他是存储在非挥发存储器(有些书也叫非易失存储器)上。

所谓的非挥发存储器,指的是不会因为断电啊这样的操作而损失数据的存储器,而内存属于挥发性存储器,如果万一遇到停电,内存就没了。


运行记录的结构

活动事务表(Active Transacion list,简称ATL)

活动事务表记录所有正在执行,尚未提交的事务的标识符

正在执行、尚未提交标识符(Transaction Identifier,TID)。


提交事务表(Committed Transaction list,简称CTL)

这个表记录所有已提交的事务的标识符。

实际上这个表是这么运作的,当你把一个事务要提交了,那活动事务表会把TID交给提交事务表,然后在活动事务表中把该TID删除。但是,这两者顺序是不可颠倒的,如果颠倒,万一停电,就会导致TID刚在活动事务表中删除,然后在添加进提交事务表的那一刻由于故障没有任何记录。


日志(log)

日志里面分为三块

image-20211003102941693

TID,前像文件和后像文件。


其中前像文件中的BID记录的是物理块的地址,空白部分记录的是正在更新事务的老值,这一个矩形就相当于一个物理块,前像文件相当于一个堆文件,有很多物理块堆叠而成,如果一个关系需要卷回,就可以从前像文件中找出该事务的所有前像块,然后把它们全部写入由于故障被破坏的关系的对应块中从而消除该事务对数据库所产生的影响。


后像文件也和前像文件的结构一样

在每个日志中都应该记录前像文件和后像文件,以方便供以恢复使用。


提交规则和先记后写规则

一、提交规则:

后像(新值)必须在事务提交前写入非挥发存储器中,不能放在内存中,因为内存会消失。

写入非挥发存储器,也就是说,可以放数据库也可以放日志(前面说过日志也是一个非挥发存储器);万一这时候故障,即使没有放数据库,我们也可以利用日志中的后像文件中的后像重做;如果这时候有其他事务要访问这些数据,由于更新的内容已经保存在后像文件里面了,也可能在缓冲区中(因为后像文件是由多个物理块组成的),所以其他事务仍然可以去缓冲区或者后像文件找哪些更新后的内容。


二、先记后写规则:

如果后像在事务提交前写入了数据库,那必须把前像首先记入运行记录。

这句话可能有点抽象,也就是说假如我现在在做笔记,如果我没有保存以前写的旧笔记,然后直接把旧笔记删了,然后直接写新笔记,一旦突然断电,那就有可能新笔记和老笔记都没了。先记后写规则就是为了防备这一点。


9.3、更新策略以及故障后的恢复

更新操作:

undo和redo


undo(撤销)操作和redo(重做)操作具有幂等性。

举个例子:假如在编程中,我们设x = 10,后面又写了一条x = 20,那么就能把x = 10覆盖掉,其中后像20,前像10,但是后像20覆盖前像10的时候,不管覆盖多少次都是20,这就是幂等性,即做多次操作和一次操作的效果是等价的。


更新策略:

一、后像在事务提交前已经完全写入数据库(没人采用)

1、TID→ATL

2、BI→log

3、AI→DB,log

4、TID→CTL

5、从ATL删除TID

在每个数据库系统里总会有这么个模块——重启动模块,不同数据库系统叫法可能不同;他的作用是在重启动数据库系统后,检查上一次退出是否正常退出。

检查如上所说的更新策略时会出现三种情况

image-20211003103159417

说明:

如果ATL里有而CTL没有,说明在提交阶段出问题了,那么系统会自动去undo收回那些事务。

如果ATL有,CTL也有,说明事务已经提交,但是ATL删除TID的时候出问题了,那么系统就会去删除TID

如果ATL没有,CTL有,说明过程执行没有出现差错,事务提交完毕。


二、后像在事务提交后才写入数据库(目前主流)

1、TID→ATL

2、AI→log

3、TID→CTL

4、AI→DB

5、从ATL删除TID

说明:TID提交ATL说明执行为提交,这时候由于后像没有在事务提交前写入数据库,所以不必记录前像,这时把后像记到日志,然后TID提交给CTL,这时候确定后像提交了,然后再把AI给DB,然后在ATL删除TID。

检查如上所说的更新策略会出现三种情况:

image-20211003103240911

说明:

如果检查时发现ATL有TID而CTL没有,说明提交CTL过程出错,这时候由于还未在其他地方做出任何操作,无须undo,只需在ATL删除TID即可。

如果检查ATL和CTL都有TID,那么说明ATL删除TID出错,甚至AI搬到DB的阶段搬到一般故障了也有可能,所以这时候需要重新开始搬,即redo,然后在ATL里删除TID。由于幂等性,即使你前面搬到一半,现在redo重新搬也不会搬多。

如果ATL没有而CTL有说明过程结束了,无须任何操作。


三、后像在事务提交前后写入数据库(现在过时了)

策略思想:在第二个策略的基础上,人们想了一个办法,同样的不将后像在事务提交前急忙写入数据库,而是创建一个后台进程,当数据库空闲的时候后台进程将其唤醒,开始把后像搬到数据库,一旦数据库忙起来了,就暂停搬运。

1、TID→ATL

2、AI,BI→log

3、AI→DB(部分写入)

4、TID→CTL

5、AI→DB(继续完成)

6、从ATL删除TID

检查如上所说的更新策略会出现三种情况:

image-20211003103315933

说明:

如果ATL有TID而CTL没有,说明BI可能有一部分在搬到数据库了,这时候需要做undo。

如果ATL有,CTL也有,说明AI可能搬到一半,ATL还没删除TID,所以为了继续搬完,我们要根据前像做redo,搬完后在ATL删除TID。

如果ATL和CTL都有,说明过程全部执行完毕,无须操作。


9.4、易地更新恢复技术(研究生)

在前面我们曾经说过,我们如果数据突然故障,要么我们就是redo,要么我们就undo;也就是说,数据要么被覆盖掉,要么就收回;但是如果我们把数据库易地更新,也就是说更新的时候不在原来写一半的地方继续写,而是找个其他地方继续写,就能简化redo和undo操作。

如下图所示是易地更新的示意图,也就是说,每个关系都有一个页表,页表的每一项是一个指针,指向关系中的每一页。这话可能有点抽象,我是这么理解的,有本笔记本,笔记本的前一页写着老值,笔记本后一页写着新值,如果当我们做更新,更新完提交事务了,那么指针由前一页指向后一页,相当于“翻页”,在这里旧页起到一个BI的作用,新页起到一个AI的作用。旧页 我们也叫影页

如果设数据库DB为

\[DB = {F_1,F_2,...,F_j,...,F_{n-1},F_{n}} \]

其中F1,F2,F3是数据库文件,每个关系对应一个文件。M我们称为主记录,主记录相当于我们买的书的目录一样,有每个关系的指针,并且M平常限制在一页。

image-20211007135017830

一般来说我们就是先根据主记录去找事务所需要更新的表,然后把表拿(说是拿,不如说是拷贝)出来做更新,更新完的表我们不会放回原位,而是放到其他的位置,然后把主记录拿出来也做更新,把我们新创建的“那一页”的地址写在“目录”上,最后把M放回去,事务提交。

在事务提交前,其他事务只能访问旧页;而事务提交后,其他事务就能访问新页了。这样的话,执行过程出问题是不用怕的;如果你是在做新页的时候或者是新页做好了还没有提交事务出故障了,那他还是老页,问题不大;如果你做好新页了提交事务了可以看新页了出故障了,问题也不大,新页已经有了。但是这种技术最怕数据库被人搞坏了,如果搞坏了,意味着新页和老页都面临着“被撕掉”的情况,这时候不得已才需要用后备副本和AI重做。


在这个过程中,我们可以看出易地更新有如下缺点

1、同一时间只允许一个事务提交,因为事务提交时要修改M,而M不允许多个事务对它同时修改,类似于排它锁。

2、同一时间一个文件只允许一个事务对它更新,因为页表不允许多个事务对它同时修改

3、提交应是原子动作,所以M一般限制为一页,文件个数收到M大小限制,一旦数据库过大,那么M一页放不下。

4、文件的大小容易收到页表大小的限制,而页表大小受到缓冲区大小的限制。

5、由于易地更新,文件很难连成一片;也就是说,数据散乱分布于磁盘,使读取数据效率变低。

6、DBMS需要解决废块回收问题。

综上所述,易地更新恢复技术不会用于大型数据库系统,只会去小型数据库系统中使用。


9.5、并发控制概述

数据库中的并发概念

如果事务顺序执行,即一个事务完全结束后,另一个事务才开始,则叫这种执行方式为串行访问

如果DBMS可以同时接纳多个事务,事务在时间上重叠执行,则称这种执行方式为并发访问

在单CPU系统中同一时间一个事务只能占用一个CPU,那么多事务只能交叉使用一个CPU,这种并发叫做交叉并发

在多CPU系统中,允许多个事务同时占有CPU,这种并发方式叫做同时并发


并发的好处

1、改善系统的资源利用率和吞吐率(即单位时间内处理的事务数)

2、改善短事务的相应时间。


并发引起的问题

1、丢失更新

啥意思?假如现在有两个同学,A同学对一个变量a赋值10,在写完提交的过程中B同学马上赋值20,结果A提交上去发现变量a的值莫名其妙变成了20。以上所说的简单的案例在数据库原理我们称为写-写冲突

image-20211003103508981

2、读脏数据

首先看图

image-20211003103530393

这幅图反映了一个情况,有两个用户,一个用户T1在改动某条或者全部元组,一个用户T2读了两条元组,这就会导致什么问题呢,假如这个表是工资表,我是T2,我本来要算总工资,结果还没发工资前我在算总工资,算着算着T1那边把工资发下来了,然后我又算总工资,结果x的工资和y的工资是两个不同时期的工资,算出来的总工资肯定是不对的。我们把上述的情况称作读-写冲突

实际上,上述情况还算是好的,假如有多个用户,如下图

image-20211003103715915

T1改了x数据,实际上他还没更新完,只是更新了一次x,T2就急急忙忙拿到了错的x数据,这时候做出来的表达式运算后的错误结果y又被T3急急忙忙拿去用了,造成了一种错误的多米诺骨牌现象。


3、读值不可复现

举个例子,假如现在同学A他去查看银行的余额有100块钱,与此同时同学B把同学A的余额转账到自己的卡号上,结果同学A刚离开银行发现不对劲返回去查看余额发现余额变了。上面说的这个例子实际上也是一种读-写冲突。

这实际上也违背了事务的隔离性。

由此可知,实际上读是不会出问题的,问题出现在写上,如果同一时间段上有人改动数据就会引出一系列问题,并发控制的任务就是要避免访问冲突所引起的数据的不一致。解决这些问题传统方法是加锁,还有其他一些方法。


9.6、并发控制的正确性准则

调度概念:

在数据库系统中常常有多个事务并发运行,而一个事务内又有多种有序的操作,比如说一个事务先修改数据然后选择数据。这些操作由系统去安排其执行的顺序,安排的原则是:你既要交叉执行(你做完然后轮到我做然后你继续做),以充分利用系统资源;又要避免访问冲突。

我们设某一时刻并发执行的事务集合为S = {T1,T2,…,Tn},那么调度S就是对n个事务的所有操作的顺序做一个安排。比如说:

image-20211003103854451

那么写成调度就是

\[S = R2(t[x])W1(t)R2(t[x]) \]

其中下标是事务的事务号。


可串行化

对于同一事务集合可以有多种调度,但是如果不同调度的结果一样,那么我们就说两个调度是等价的,也叫目标等价。如果并发控制后的调度和串行控制的调度是目标等价的,那么我们就称此调度为可串行化的(目标可串行化)。

因此,在一般地DBMS中,都是以可串行化作为并发控制的正确性准则。


冲突等价

我们前面提到了目标等价,实际上还有一种等价叫做冲突等价,简而言之就是,操作会发生冲突,如果发生冲突就会导致不打架,不冲突就等价。

比如我们前面提到的读脏数据,我们前面读了一个新值一个旧值,这就是冲突操作,冲突操作分为:读写冲突和读读冲突,前面都细讲了,现在就不细说了。


当然不冲突操作也有两种情况

第一种是读同一个对象,这种情况下互换顺序也无所谓。

第二种是写不同对象,这种情况下互换顺序也无所谓。

这里我们可以知道,一旦把不冲突操作调换,就会产生一种新的调度,我们称为S的冲突等价调度。这样的话他们两个操作可以互换,不影响结果,所以一定目标等价。但是反之未必正确:即目标等价就一定是冲突等价。


9.7、封锁法以及锁协议

封锁法核心思想:

用加锁的方法实现并发控制,也就是在操作前先对操作现象进行加锁。

比如现在还是那个例子,同学A和同学B同时改变量x,那么我们就对变量x进行加锁,加锁后的x不允许多事务同时操作他,只允许先到事务去操作,比如先抢到改变x值机会的是A同学,那么A同学就会在做完改变x值后又提交会系统后,锁释放掉,B同学才能去操作变量x。


X锁

在这种加锁协议中,只有一种锁是X锁,X锁是一种排他性的锁,一旦有一个A事务在操作某对象时给对象加锁,其他事务就不得在A事务使用期间访问对象。

X锁中的X取自于排他性的英文(exclusive)

其相容矩阵如下:

image-20211003104116176


两段封锁协议

定义一:

在一个事务中,如果加锁动作都在所有释放锁动作之前,那么我们称事务为两段事务(2PL)。上述的加锁限制称为两段封锁协议。

image-20211003104340590

定义二:

一个事务如果遵守“先加锁,后操作”的原则,则此事务称为合式(well formed)事务。


定理一:

如果所有事务都是合式、两段事务,则他们的任何调度都是可串行化的。


总结:

1、合式+两段封锁协议:可串行化

2、合式+两段封锁协议+更新操作的锁在EOT(事务快要提交的时候)时释放:可串行化+可恢复的。

3、合式+两段封锁协议+所有操作的锁都在EOT时释放:严格的两段加锁协议(Strict 2PL protocol)

【说明:严格的2PL协议简单可靠,应用很广,但是所有锁都要保持到EOT,不利于提高系统的并发度,尤其当一个事务的持续时间很长,或一个事务因等待某个资源(数据对象)而被阻塞时,它已获得的锁长期得不到释放。】


(S,X)锁

在这种锁协议中,设有两种锁

S锁用于读,X锁用于写;S锁用于读则导致了多个用户可以并发读,因为我们前面知道,“读-写冲突”和“写-写冲突”根本原因都是写的问题,多个用户并发读不会出现所谓的“读读冲突”,所以S锁可以允许多个用户同时读,即多个事务同时申请S锁;但是,如果申请S锁就无法申请X锁,这是由于X锁是排它锁,不兼容其他锁,假如事务A申请了S锁然后做读操作,事务B申请了X锁做写操作,那就会导致出现“读写冲突”。

其相容矩阵如下:

image-20211003104440094

总结:

从执行效率来看,SUX锁效率最高;但是从系统复杂度和实现难度来看,SUX锁也是最高的。


9.8、死锁和活锁

如果我们采用(S,X)锁,那就会出现一个问题,如果一个数据对象被事务A申请了一个S锁,那么事务B也可以申请S锁,那么这个时候事务A是无法申请X锁的,假设这个时候有n个事务都申请S锁,那么事务A的X锁就会迟迟无法申请,这个锁就叫活锁

活锁:(扼死现象)

若某对象加了S锁,这时候有其他事务申请对他的X锁,则需等待;若某对象加了S锁,这时候若有其他事务申请了对他的S锁,按相容矩阵,无须等待,而X锁的申请迟迟不能获得批准,这种现象叫活锁。示意图如下:

image-20211003104559571

如何避免活锁:

避免活锁的简单方法是采用先来先服务的策略。

X锁假如是第二个来的,那么第三个来的S锁就不能继续申请,得让X锁先来;这样一来,比X锁后申请的S锁就不会先于X锁获准了。


死锁:

一个事务如果申请锁而没有被批准,则需等待其他事务释放锁,这就形成了事务之间的等待关系,如果有两个数据对象D1,D2,T1先申请到D1,T2先申请到D2,然后后面T1又想申请D2,此时D2被T2抢先,所以T1需要等待;而这个时候T2想申请D1,无独有偶,D1被T1抢先,所以T2也在等,这样两个人都在等对方,两个人都等不到,形成了死锁。示意图如下:

image-20211003104626824


9.9、死锁检测和死锁预防

对付死锁就和恢复机制是一个道理,先防,防不住再治。

其中防就是防止死锁,治就是检测死锁,发现了然后处理。

死锁检测

一、超时法

如果一个事务的等待时间过久,我们有理由怀疑他里面死锁了,这个理由虽然简单,但是容易误判,比如事务因为其他原因(系统负荷过大,通信受阻)而使事务超过时限,也可能被判定为死锁。当然死锁的时限也不能设太大,避免发现死锁的滞后时间过长。通常时限须根据系统运行情况通过试验确定。


通常大型数据库系统不会采用这种方法,而采用下面的等待图法。


二、等待图法

等待图是一个有向图G = (W,U)。其中,W是结点的集合,W = {Ti|Ti是数据库中运行的事务,i = 1,2,…,n},U是边的集合。DBMS根据锁申请和加锁的情况,动态地维护一个等待图。当且仅当等待图中出现回路时,死锁才发生

我们把上述的情况也叫死锁定理

①如果资源分配图中没有环路,则系统没有死锁;

②如果资源分配图中出现了环路,则系统可能有死锁。

当运行的事务比较多,我们肯定不可能去看每个边会不会构成环,这样维护等待图和检测回路的开销比较大,影响系统的性能。比较合理的办法是周期性地进行死锁的检测。


解决死锁

死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,环路上的每个进程都在等待下一个进程所占有的资源。

举个例子:小明有键盘,小白有鼠标,小明要用电脑打游戏,小白要用电脑做PPT,小明没有鼠标没法打游戏,小白没有键盘没法做PPT,小明等小白把鼠标给自己,小白也等小明把键盘给自己,但是小明不愿意把键盘给小白,小白也不愿意把鼠标给小明,小明和小白也不能互相抢键盘和鼠标,他俩之间就形成了死锁。


一旦发现死锁,DBMS就要做以下措施;可以在回路中挑选一个事务作为牺牲者,把它后台杀掉,然后给其他事务让路,这样其他事务就能动起来。

牺牲的事务要做两步处理

一是发消息给相关用户,告知他们事务已经因为死锁而被撤销,也就是事务已经不再是三条线造成的死循环关系了,通知系统重新安排改成一条线

二是由DBMS重新启动该事务,不过做这个重启动的操作要慢点,不能说刚杀完后台就马上重新启动,这样可能又出现死锁。


牺牲的事务符合下面三个条件

  • 最迟交付的
  • 获得锁最少的
  • 卷回代价最小的

死锁防止

检测死锁需要一定的开销,所以如果能防止死锁发生,不就不用检测了吗?

操作系统中也曾经提到过死锁防止,比如在事务执行前,直接就把需要的锁全申请了,那么这个事务他要么去等待别人,要么别人来等他。


我们引入几个概念:

时间标记:这个标记记录每个事务从开始执行到“现在”的时间,即ts(time stamp)。

年轻事务和年老事务:执行时间越久我们说这个事务越年老,反之越年轻。


事务重执策略

  1. 等待-死亡策略

    在这种策略中,一个事务Tb持有某对象的锁,一个事务Ta这时候也申请这个对象的锁而发生冲突时,ts(Ta)<ts(Tb),我们让Ta作为年轻事务等待,这时候我们直接把Ta卷回(杀死),然后过一段Ta根据身上的时间标记回到当时执行时的状态,继续执行。但是记得别让Ta“复活”太快,避免年老的Tb还没执行完又出现死锁。

  2. 击伤-死亡策略

    在这种策略中,一个事务Ta持有某对象的锁,一个事务Tb这时候也申请这个对象的锁而发生冲突时,ts(Ta)>ts(Tb),我们让Ta作为年老事务等待,然后把Tb卷回(杀死),然后过一段Tb根据身上的时间标记回到当时执行时的状态,继续执行。

    在上述两种策略中,我们总是以年轻的事务作为牺牲品,如果用年老的事务作为牺牲品,那么年老的事务越变越老,成为永远的牺牲品。

    死锁来自于循环等待,所以破坏等待条件,那么就不会死锁,即直接卷回。但是直接卷回整个事务开销过大,一般不用,我们一般是有选择地去卷回事务冲突的部分,防止死锁,这样就比上面两种策略直接一整个事务卷回好多了。


9.10、多粒度封锁

前面我们将封锁,只是说对一个数据对象加锁,并没有说这个数据对象有多大。

但是实际上,这个数据对象可以是数据库、一个区域、一个文件或者一个关系,甚至是一条元组或者某个属性,封锁单位越大越粗,封锁起来越简单;封锁单位越小越细,往往需要加很多锁,锁起来更麻烦。

好比一个屋子,你怕进小偷,所以你直接把大门锁了,那肯定方便,一个锁就能搞定;但是如果你不锁大门,那你就得把房间门全锁了,这就比较麻烦了。

但是直接锁大门,可能很多细节把握不了,现比如客人来参观大厅,小偷偷房间的贵重;如果你的来访者有些是客人有些是小偷,你大门一关谁都进不来,但是你只关放贵重物品的房间,就能防小偷,不用防客人。

所以我们如果直接大单位加锁锁死,就会导致把一些不需要加锁的数据也封锁了,降低了并发度。

对于上述的情况,我们可以发现,生活中实际上有些表要访问全部,有些访问某些属性即可,所以,我们通常提供多级封锁单位,根据应用的需要来启用,比如我提供了数据库锁和表锁,你需要哪个就用哪个。这种封锁方法我们叫做多粒度封锁

在现代大型数据库系统一般都支持多粒度封锁。但是在微机DBMS中,并发度要求不高,一般以关系作为封锁单位,这称为单粒度封锁


采用多粒度封锁时,一个对象可能以两种方式被锁住了。

举个例子,假如你的数据对象是房间,那房子大门被锁了是不是相当于房间也进不去了,这就是隐式封锁。直接锁房间门就是显式封锁

我们封锁的时候应该尽可能控制封锁范围,以免影响其他事务的运行,降低系统的并发度。


锁的冲突问题:

如果只有显示锁,那我们完全可以像前面学过的那样去检测锁的冲突或者防止死锁等等,维护起来也比较方便;但是如果有了隐式锁,那检查锁的冲突就比较复杂了。

假如你现在在检查房间锁,这时候假如你已经进入了屋子,那么说当你检查完一圈系统还是说你没有检查完所有的锁,这时候回去大门,诶,发现大门被锁上了,是不是很难受。有时候你好不容易检查完房间锁,结果发现还是没有检查完,然后看了一下大门大门没锁,结果看了一下是房间里的保险箱上锁了,是不是更难受?

所以,为了简化这种检查,IBM公司开发了所谓的意向锁(intent locks)。


意向锁:

IS锁

他被叫做意向共享锁。共享,实际上就是读操作;所以当一个数据对象被加了IS锁,表示它的某些子孙加了或者拟加了S锁(拟加,也就是说,如果你不单独设置子对象的锁,那么他直接给你上S锁,如果你单独设置了子对象的锁,那就以你设置的为准)。

image-20211003105053049

如图所示,如果元组加了S锁,现在如果有个其他事务拿不到元组的锁权,那么它就会去想拿元组所在的关系甚至是数据库的锁权,这时候如果其他事务成功上锁,那么对元组加了S锁的事务将无法访问。

所以为了应对这种情况,我们可以在申请元组的S锁权时用IS锁同时锁住数据库和关系,防止其他事务搞事,导致隐式锁和S锁冲突。


IX锁

他称为意向排他锁,同上面说的一样,为了防止其他事务搞事情,在对元组加X锁的时候要给关系和数据库同时加锁IX锁。


SIX锁

SIX锁相当于加了S锁然后再加上IX锁,即SIX = S+IX。在实际应用中,常常需要读整个关系,并且更新其中个别元组。

范例:工资表,看所有人,然后改其中个别员工工资。

有些DBMS没有SIX锁,这时候对于这种想看整体,修改个别的访问,只有以下两种可供选择的加锁方案:

方案一:

在元组的父(关系)上加个X锁,关系的父(数据库)加个IX锁(防止其他事物动手),又由于X锁是排它锁,S锁不能存在。在关系加了X锁就相当于把关系中所有的元组上了X锁,但是这样的话其他事务就不能访问了一大堆东西(比如关系中的数据),降低了并发度。

方案二:

所有需要更新的元组加X锁,其他元组加S锁,关系和关系以上的各级加IX锁。IS锁只能读不能操作,所以我们才用IX锁代替IS锁,这样的话其他事务可以对元组进行访问了。

方案二看起来比方案一好,但是在面临大数据的情况下,假设有一万条元组,那么加的S锁和X锁就有一万个。所以SIX锁这时候就体现出优势了。


意向锁的相容矩阵

image-20211003105146564

其他的就不多说了,在多粒度封锁中,我们知道,如果要对一个数据对象的加锁,就要对这个数据对象的上级加相应的意向锁。申请锁时,我们应该从上级申请到下级去,而不是从下级申请到上级;就好比你去锁门,你要进去一个房间放一笔巨款,不想给任何人进来房间,那么你应该是从最外面的房间锁起,然后锁到里面房间的门,而不是把自己房门锁死了再去锁外面。虽然这个比喻不是很恰当,但也足够说明其原理了,即:申请锁应该最上面申请,然后依次往下,如果相反,在申请上级的锁时发现被其他事务申请了,你还要把自己的锁撤销。


不过,如果你不用涉及到元组方面的操作,那你大可不必申请过于细粒度的锁。当然,如果涉及到了,那你就要遵循逐步加锁原则,即锁递升,比如我房间太多了,房间门锁不过来,那我直接把大门锁了就行了,不过,这是要在锁不过来的情况下,即一个阈值;用粗粒度的锁代替多个细粒度的锁可以节省锁表的空间开销,还可以节省锁冲突的时间开销。


9.11、作业

1、各种数据有不同的恢复要求;有些要求恢复至最近一致状态,有些只要求恢复至先前的某个一致状态;也有些数据不需要恢复措施,只须在失效后重建。试着各举一例。


2、试着论述提交规则和先机后写规则对事务的必要性


3、undo和redo是两种基本的恢复操作,试说明其操作内容及其所需的条件。在什么情况下需要redo,在什么情况下需要undo


4、讨论易地更新恢复技术的优缺点。在易地更新方案中,是否需要运行记录?在什么环境下可以省去运行记录?


5、论述各种失效情况下应该采取的恢复措施


6、试着回答下面的问题

  • 什么叫并发?

  • 为什么要并发运行?

  • 并发会引起什么样的问题?

  • 什么样的并发执行才是正确的?

  • 如何避免并发所引起的问题


7、什么是两段封锁协议?怎么实现两段封锁协议


8、分别讨论写锁和读锁在事务结束前释放可能引起的问题。


9、什么叫活锁,如何防止活锁?


10、什么叫死锁,如何防止死锁?


11、试着区别串行调度和可串行化调度。请各举一例


12、试分析、比较封锁法和时间标记法两种控制并发技术


13、试分析、比较悲观法和乐观法两种并发控制技术。


第十章、安全性和完整性约束

10.1、概述

数据库是共享的资源,所以破坏一般都源自于共享。

破坏分类:

1、系统故障(断电断网等等)

解决:恢复技术

2、并发所引起的数据不一致(死锁活锁等封锁法问题)

解决:并发控制

3、人为的破坏,如数据被不该知道的人访问,甚至被篡改或破坏

数据库安全

4、输入或更新的数据库数据有误,更新事务未遵守保持数据库一致性的原则。

完整性约束


维护数据库安全的常见手段

视图定义和查询修改

假设现在有一个计算机学院的老师,我们不想给他看到所有学生所在基表(最原始的表),我们只想给他看到计算机学院的学生,那怎么办呢,我们可以定义一个视图,视图里面也是一张表格,但是只放了计算机学院的学生。

有些DBMS没有视图这种功能,那么他会提供一种叫查询修改的功能。查询修改,就是你的查询语句比如说还是计算机老师,你只需要给他看计算机学院学生的表,那你可以让DBMS对他写的查询语句做一个改写,比如老师写的语句是select* from student,那么DBMS会自动补上条件:select * from student where college = "计算机"。让这个老师只能查到数据库中计算机学院学生的部分,这就是查询修改。


访问控制

数据库的各种资源都是有一定的操作权利的,DBMS可以通过控制这些权利把用户划分开来,以保证数据库的安全。一般来说,我们会划分成三类:

1、一般数据库用户

在SQL中,这种用户称为“具有CONNECT特权的用户”,可以和数据库连接,并且拥有下列特权:

(1)可以按照授权查询或更新数据库的数据

(2)可以创建视图或定义数据库的别名

2、具有支配部分数据库资源特权的数据库用户

在SQL中,这种用户称为“具有RESOURCE的用户”,除了具有一般数据库用户的所有权利外,还有一下特权:

(1)可以创建表、索引和簇集

(2)可以授予或收回其他数据库用户对其所创建的数据对象所拥有的的访问权。

(3)有权对其所创建的数据对象跟踪审查

3、DBA数据库用户

DBA拥有支配整个数据库的权利,除了上面说的,他还可以:

(1)有权访问数据库中的任何数据。

(2)不但可以授予或收回数据库用户对数据对象的访问权,还可以批准或插销数据库用户

(3)可以为public定义别名,public是所有数据库用户的总称

(4)有权对数据库进行调整、重组或重构

(5)有权使用DBMS中自管理、性能调优等工具。


已经划分好了,那么用户怎么拥有对应的身份去访问数据库呢。一般访问分为以下两个步骤:

1、用户的标识和鉴别

(1)利用只有用户知道的信息鉴别身份

这种技术一般是用口令,也就是所谓的密码,不过密码实际上也有泄密的危险,所以DBA应该督促用户多换口令。

(2)利用只有用户具有的物品鉴别用户

就像钥匙一样,比如磁卡。

(3)利用用户的个人特征鉴别用户

指纹,虹膜等。


2、授权(Authorization)

授权就是给予用户一定的访问权限,这是对用户访问权限的规定和控制。

(1)授予某类数据库用户的特权

假如是皇上选妃,那么这个操作就是给个普通人一个名分,而且这个名分只有皇上(DBA)才能给。

(2)授予某些数据对象进行操作的特权,可以由DBA授予,也可以由该数据对象的创建者授予。

这种操作看着简单,实际上在企业级数据库非常难操作,因为企业级数据库的数据对象十分庞大,不可能一个一个都去设置特权;在这种情况下,我们提出了角色(role的概念。角色,实际上就是某一类数据对象,当我们提出授权的时候,直接对角色授权,可以大幅度提高授权管理的效率。


3、数据加密(Data encryption)

我们的数据库系统是建立在操作系统之上的,也就是说如果要访问数据库中的数据,必须经由DBMS进行访问,而不能绕过DBMS从操作系统进行访问,而这个控制,是由操作系统来负责保证的。而尽管有这么多措施,但是数据存储在介质上(如硬盘、磁盘),还常常通过通信线路进行传输。如果有人窃取磁盘或磁带,或从通信线路窃听,则数据库系统就无法控制了。所以当我们对数据的保密性特别高的时候,我们可以对数据进行加密,这样即使有非法分子绕过DBMS从操作系统拿到了你的数据,他也无法读出。

不过这么做常常要付出代价,由于加了密,所以在数据库中取出文件供自己使用时,常常需要解密,解密的过程有时候很繁琐,会影响一些效率。


4、跟踪审查(Audit trail)

即使你进行数据加密,但还是有牛马人能突破这些限制来窃取你的数据。 这时候既然防不住也治不了,干脆监视,一旦发现有人窃取了,就打开我们在数据对象上安置的审计,然后看下是谁做了什么操作动了什么手脚。这个审计我们一般有术语叫做跟踪审查记录。他一般包含下列的东西:

  1. 操作类型

  2. 操作终端标识和操作者标识

  3. 操作日期和数据

  4. 所涉及的数据

  5. 数据的前像和后像


10.2、统计数据库的安全

一、

在许多地方统计数据往往是公开的,比如说一个公司所有员工的平均工资,一个单位的人均奖金数。但是,每个人的奖金数可能保密(非统计数据,即个别数据)。

人口统计数据库就属于统计数据库,这类数据库以统计型数据为主,但是个别的数据值可以从统计数据推断出来。而如何阻止这种漏洞,到目前为止没有人能完全解决。


那么那些个别的数据如何推断?

		mysql> select* from emp;
		+-------+--------+-----------+------+------------+---------+---------+--------+
		| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
		+-------+--------+-----------+------+------------+---------+---------+--------+
		|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
		|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
		|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
		|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
		|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
		|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
		|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |    NULL |     10 |
		|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
		|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 |    NULL |     10 |
		|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
		|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
		|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
		|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
		|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |    NULL |     10 |
		+-------+--------+-----------+------+------------+---------+---------+--------+
		说明:这是完整的一张表,现在我们要找出表中符合工资为1500到3000的元组。

假如是这么一张表,即使你不允许他单独查询某个员工的工资,他仍能通过条件查询卡工资范围,通过不断的卡多次范围然后推断符合条件的元组,从而推测出单个元组的工资。


实际上教材上有更精彩的例子,但是说来说去就是怎么补漏洞都是补不住的,而且你就算限制他的查询,你也会付出下列的代价:

(1)开销太大,难以检查。

(2)对正常的用户的使用也带来了一定的限制。

(3)为了防止有人采用断续查询的方式来窃密,不但要考虑当前的查询,还要考虑历史上的查询。

(4)为了防止多人合作窃密,不但要考虑某一用户的查询,还要考虑历史上的查询。


如果某个谓词在推断中起到了关键作用,那么我们把这个谓词称为个体追踪器,理论上来说,每个元组都有个体追踪器。


10.3、完整性约束

数据库里关系的一个合法实例必须满足的条件我们叫做完整性约束。

完整性约束分为以下两个:

静态约束

固有约束(static constraints)

指数据模型固有的约束,如关系的属性应该是原子性的,不允许表中套表,也就是我们所说的应该满足一范式的限制。

隐含约束(implicit constraints)

指隐含于数据模式中的约束,一般用DDL语句说明,并且存在于数据目录中。例如,域完整性约束、实体完整性约束以及引用完整性(外键约束等等)约束,他们都用相应的DDL语句说明。

显示约束(explicit constraints)

固有约束、隐含约束是最基本的约束,但概括不了所有的约束。数据完整性约束是多种多样的,并且依赖于数据的语义和应用,这些约束只有显式地说明,所以叫显式约束


动态约束

动态约束不是对数据库状态的约束,而是数据库从一个状态变为另一个状态应该遵守的约束,比如你转账了,接收转账的一方不可能钱越转越少对吧。


完整性约束的说明

用过程说明约束

根据外键,用这个关系1(表)里的某个键,可以引用(寻找)其他关系(表)中的一条元组,即可以引用其他表的主键,那我们叫这个其他表里的主键键叫外键。

现在有两个表r1和r2,k1是r1的主键,k1是α的外键,那么我们要保证下列条件

α在r2表示的投影要包含于k1在r1表上的投影

image-20211003110100193

如果你要在r1做插入操作,那么怎么插入都没事,因为是r2来引用r1,r2只需要在r1里面找得到对应的元组即可,所以r1怎么插入都没事,只需要保证r2的α所在的值是K1的值的子集即可,但是你如果在r2进行操作,那你一定要去检查k1是否有这个新插入的α值,如果没有,则不允许插入,因为在引用的时候他去k1找不到对应的引用值。


如果你在r2做删除操作,那你怎么删除都没事,但是如果你想删r1,那你要注意删r1的时候要保证r1的k1值无α引用,如果有引用,那么用户可以在定义外键的时候选择这两种情况:

  • 系统报错,不让你删
  • 你强制要删,那么先把r2中被k1引用的相关α删除,然后再删k1,我们把这个操作叫做级联删除

如果你在r2做更新操作,那就类似于刚才的插入操作,更新时要注意r2中的α是否是k1的子集,否则引用时就会出现外键当空;如果你在r1做更新操作,那就类似于刚才的删除操作,更新时要注意r1的k1删除后是否会影响α的引用,避免出现外键当空。

在做这些操作时,如果违反约束,那么DBMS会卷回事务。


用断言说明约束

断言指的是数据库状态必须满足的逻辑条件,你可以理解为if语句。

数据库完整性约束可以看成一系列断言的集合。为了表示这个断言约束,自然就有相关的断言说明语句,用这个语言我们可以写出数据库的完整性约束,由系统编译成约束库。DBMS中还有一个完整性控制子系统,对每个更新事务,用约束库中的断言对其进行检查,如果发现更新违反约束,就卷回事务。


范例:assert 余额约束 on 储蓄账:余额>=0;

这种方法有优点也有缺点,优点是免除了程序员在应用程序中分散地考虑完整性约束问题。这既减少了编程的麻烦,又方便了应用程序和约束的维护。

但是,完整性控制子系统实现起来非常复杂,开销也很大,降低了数据库更新操作的性能。


在基表定义中加CHECK子句约束

定义基表的时候添加CHECK,也就是creat表的时候添加CHECK。

范例:

			create table Sailors
			(
				sid integer,
				sname char(10),
				rating integer,
				age real,
				primary key (sid),
				check (rating >=1 and rating <=10)
				//利用check来控制rating属性的域。
			)

单个表用CHECK,多个表用断言。


用触发子表示约束

触发子,也叫触发器(Trigger subsystem)

后面会来论述。


完整性约束的实施

因为完整性约束是伴随数据库更新操作进行的,对数据库的更新操作性能影响很大。

目前域完整性约束在一般的DBMS中都已经实施;

实体完整性约束在大部分关系DBMS中都已经基本实施;

应用完整性约束在部分关系DBMS中已经实施;

显式完整性约束在商品化的DBMS中实施的也逐步增多。

随着计算机性能的提高和约束检验方法的改善,在DBMS商品中,比较全面的完整性约束检验可望成为标准功能。


第十一章、触发子和主动数据库系统

11.1、主动数据库系统引论

一、简介

传统数据库过于被动,需要DBA去操作才能动起来,导致无法应对一些特殊问题,这样的数据库是被动的;而后来由于对主动数据库的要求日益增加,主动数据库系统就称为公认的数据库重要研究方向之一。


二、主动数据库

主动数据库仅仅是一个数据库系统的功能罢了,并不是某一类数据库。也就是说,主动数据库就是具有主动数据库功能的数据库系统。所谓的主动数据库,就是我们希望他有一些主动的能力,当数据库的一些数据达到某个状态的时候,他会发生一些动作,具备这种能力的数据库我们称为主动型数据库。


三、触发子系统

在早期的关系数据库管理系统System R中,就有人建议增加触发子系统。

触发子系统在满足一定的条件时,就会触发一定的动作,主动完成必要的处理,实际上就是现在所称的主动数据库子系统。


11.2、规则的表示

主动数据库之所以做出相应的动作,就是依赖于一些规则;规则中应该说明ECT,E(Event)是事件,他要说明当什么事件发生了数据库才会做出某些动作;C(Condition)是条件,当某些事件发生了,我们不一定真的要去做这个动作,而是要当数据库的数据满足一个条件的时候,那我去做一个动作A(Action)。合起来就是我们常说的ECA规则。

我们常说的Triggers就是由这些规则构成的,或者说,ECA就是Triggers。


触发子的定义格式如下

<触发子>::=CREATE TRIGGER<触发子名>
{BEFORE|AFTER}<触发事件>
ON<表名>
[REFERENCING<引用名>]
FOR EACH {ROW|STATEMENT}
WHEN(<条件>)
(<动作>)
<触发事件>::=INSERT|DELETE|UPDATE[OF<属性表>]
<引用名>::=OLD[ROW][AS]<旧元组名>
NEW [ROW][AS]<新元组名>
OLD TABLE [AS]<旧表名>
NEW TABLE [AS]<新表名>

我们来看一个触发器的例子,这是按SQL99的标准写的。

下面的SQL大概是做这么一件事,对水手表做一个监视,一旦插入了写的元组,触发器就会看这个新插入的水手是不是一个年轻的水手,也就是年龄是否大于十八岁。如果大于十八岁,那么就把水手表中新插入的元组,同时也插入年轻水手表里。删除年轻水手也同理。

CREAT TRIGGER youngSailorUpdate
AFTER INSERT ON SAILORS
REFERENCING NEW TABLE NewSaliors
FOR EACH STATEMENT
INSERT
	INTO YoungSailors(sid,name,age,rating)
	SELECT sid,name,age,rating
	FROM NewSailors N
	WHERE N.age<=18

第十二章、数据库设计

12.1、数据依赖

要设计一个好的数据库,你要充分理解用户的需求。

实际上,我们实际应用中的这些数据都不是孤立存在的,他们都存在各种各样相互的内在的联系,我们把这种联系统称为叫数据依赖关系。

即:Some dependent relations exist between attributes.


那数据依赖关系有哪几类呢?

为了更好的表示一下的数据依赖关系的概念,我们采用一下以下的符号:R为一个关系的模式,U ={A1,A2,A3...An}是R所有属性的集合,F是R中函数依赖的集合,r是R所取的值。


函数依赖(Functional Dependncy,FD)

\[设有一关系模式R(A1,A2,A3...An),X和Y为其属性的子集,\\ 即X\subseteq Y,Y\subseteq U。\\ 设t1,t2是关系R中的任意两个元组,如果t1[X] = t2[X],则t1[Y] = t2[Y]。\\ 这时我们称Y函数依赖于X,或X函数决定Y,X称为决定子。 \]

简而言之就是,函数依赖就是一个或一组属性的值可以决定另外一个属性的值。

比如说学生的学号可以决定一个学生的姓名;一个学生的学号和他所选课程的课程号可以决定他这门课的成绩。正如函数y = f(x)一样,x的值确定了,y值也就唯一地确定了。

而这种关系恰恰是数据库设计里最重要的。

\[特别的,如果Y\subseteq X,显然X→Y成立,\\ 这称为平凡函数依赖,我们平常指的函数依赖多半指的是平凡函数依赖。\\ 如果Y!\subseteq X,则我们称X→Y是非平凡函数依赖 \]

这个定义看着太抽象了,我们来看下面的例子:

非平凡的函数依赖其实就是(学号,课程号)→成绩,即学号和课程号决定成绩。

而平凡函数依赖其实就是(学号,课程号)→学号,即学号和课程号决定学号。

对于任何关系模式,平凡的函数依赖总是成立的,所以对于函数依赖,我们说的实际上都是非平凡的函数依赖。


函数依赖实际上还有两个分类:即完全函数依赖和部分函数依赖,在这里我不给出定义,因为看不懂,直接口头说明即可。

举个例子:

如果学号决定不了成绩,课程号也决定不了成绩,只有(学号,课程号)→成绩,那么我们称为完全函数依赖。

但是如果学号能够决定姓名,那么(学号,课程号)→姓名,这个我们叫做部分函数依赖。


还有一种函数依赖我们叫做传递函数依赖,这里我们也不给出定义。

举个例子:如果学号能够决定系别,并且系别能够决定系主任,那么学号→系主任,所以我们说系主任传递函数依赖于学号。

多值依赖(Multi-valued Dependency,MVD)

在函数依赖X→Y中,如果X的值给定了,Y的值就被唯一确定了,所以叫函数依赖。而在多值依赖(表示为X→→Y,读作X多值决定Y,或Y多值依赖于X)中,对于给定的X值,其对应的是Y的一组数值,而且这种对应关系,对于给定的X值所对应的(U-X-Y)的每个值都能成立。

也就是说,函数依赖是多值依赖的一个特例。

但是实际上现实应用多值依赖是很少的。


上面的概念晦涩难懂,我们找个例子理解一下:

一个教师可以教多门课,也可以去多个学校上课;但是,他去不同的学校上课上的还是一样的那几门课。根据多值依赖我们可以写成T→→C,即只要确定老师就能确定老师教的几门课,我们也可以写出T→→S,确定一个老师可以确定老师取的那几个学校。

image-20211004225133854

连接依赖(Join Dependency,JD)

假如S是供应商编号,P是零件编号,J是工程编号

image-20211004225619371

如果一个表能够通过投影分解成三张表,然后这三张表又能通过连接拼回原来的表,一条元组不落下,那我们就说这个表中的三个属性之间存在连接依赖,我们把这种拆分后拼接回去不落下元组的拼接叫无损拼接

但是实际上现实应用连接依赖是很少的。


12.2、函数依赖的推理规则

12.2.1、概述

在讨论函数依赖时,可能我们会遇到这样的问题:根据给定的一个函数依赖集F,判断另外一些函数是否存在。例如:设关系模式R(U,F),其中属性集U = {A,B,C},函数依赖集F = {A→B,B→C}。问A→C是否成立?这就是所谓函数依赖的逻辑蕴涵和推理规则问题。


12.2.2、函数依赖的逻辑蕴涵

定义:对于满足函数依赖集F的关系模式R(U,F)的任意一个具体关系r,若函数依赖X→Y都成立,则称F逻辑蕴涵X→Y。记为

\[F\implies X→Y \]

函数依赖集的闭包

\[关系模式R<U,F>中为F所逻辑蕴含的函数依赖的全体所构成的集合称作F的闭包, \\记作F^+ \]

例如:关系模式R<U,F>,U = (X,Y),F+{X→Y},易见F+ = {X→α,X→X,X→Y,X→XY,Y→α,Y右Y,XY→α,XY→Y,XY→XY}


注意

△ F包含于F+,如果F=F+,则F为函数依赖的一个完备集。
△ 规定:若X为U的子集,X→Φ 属于F+。

关系模式R<U,F>若有n个属性,则在模式R上可能成立的函数依赖有4n个,其中n个属性中组合成X有2n个,组合成Y有2n个。

例:已知关系模式R(ABC),F={A→C,B→C},求F+

解:∵U={A,B,C},左部不同的属性集组合有23=8种:

Φ、A、B、C、AB、BC、AC、ABC。

(1)∴Φ→Φ

(2)∵(A)F+=AC

∴A→Φ、A→A、A→C、A→AC。

(3)∵(B)F+=BC

∴B→Φ、B→B、B→C、B→BC。

(4)∵(C)F+=C

∴C→Φ、C→C。

(5)∵(AB)F+=ABC

∴AB→Φ、AB→AB 、AB→A、AB→B 、AB→C、AB→BC 、AB→AC、AB→ABC 。

(6)∵(BC)F+=BC

∴BC→Φ、BC→BC、BC→B、BC→C。

(7)∵(AC)F+=BC

∴AC→Φ、AC→BC、AC→B、AC→C。

(8)∵(ABC)F+=ABC

∴ABC→Φ、ABC→ABC 、ABC→A、ABC→B 、ABC→C、ABC→BC 、ABC→AB、ABC→AC。

所以F+共有35个具体如下:

∴Φ→Φ、A→∅、A→A、A→C、A→AC

B→Φ、B→B、B→C、B→BC

C→Φ、C→C、 AB→∅、AB→AB 、AB→A、AB→B 、AB→C、AB→BC 、AB→AC、AB→ABC 、

BC→Φ、BC→BC、BC→B、BC→C、

AC→Φ、AC→BC、AC→B、AC→C、

ABC→Φ、ABC→ABC 、ABC→A、ABC→B 、ABC→C、ABC→BC 、ABC→AB、ABC→AC

【总结:由此可得,要想求完整的函数集闭包,最好挨个求属性集的闭包。】


$$ X→Y∈F^+ 表示关系模式R上X→Y成立 $$

12.2.3、属性集的闭包(重要)

\[设F为属性集U上的一组函数依赖,X \subseteq U,X^+ _F = {A|X→A∈F^+}称为属性集X关于函数依赖集F的闭包。\\ 在F确定的情况下,也可以简写成X^+ \]

简而言之,属性集的闭包就是由一个属性直接或间接推导出的所有属性的集合,例如: f={a->b,b->c,a->d,e->f} 由a可直接得到b和d,间接得到c,则a的闭包就是{a,b,c,d}。


函数集的闭包和属性集的闭包,在我看来主要的区别就是,函数集的闭包是关于U的,U里面所有的属性构成的所有函数依赖都要写出来。而属性集的闭包是从U里面取一部分,你需要写的只要取出来的这部分属性集的所有函数依赖即可。


12.2.4、Armstrong公理系统

设有关系模式R<U,F>,有以下的推理规则:

  1. 自反律:如果Y在X里面,那么X→Y

  2. 增广律:如果X→Y,那么XZ→YZ

  3. 传递律:X→Y,Y→Z可以推出X→Z

【注:自反律和F没有关系】

人们把这三条规则合称为Armstrong公理的正确(有效)性和完备性。其中有效性指的是我们从这个公理系统推出来的每一个闭包都是正确的。完备性就是说闭包里面的每一个函数依赖都能由公理推出。这三条规则的推导过程 这里就不详细说明了。

根据Armstrong公理,我们推导出了另外的推理规则。

  1. 合并律:若X→Y,X→Z,则X→YZ

  2. 分解律:若X→Y,Z包含于Y,那么X属于Z。特别地,若X→YZ,则X→Y,X→Z

  3. 伪传递律:若X→Y,WY→Z,则WX→Z

同样的,这里我们就不证明了。


12.2.5、逻辑蕴涵的判别和属性集闭包的计算(重要)

\[设F为属性集U上的一组函数依赖,X,Y \subseteq U,X→Y能由F根\\据Armstrong公理导出的充分必要条件是Y \subseteq X^+ _F \]

这个引理的证明就不多叙述了。

上面的引理和传递律可以推出下面的定理:

\[若V→W,且X^+_F \supseteq V可以推出X^+_F \supseteq W \]


现在我们给出一道例题:

image-20211016092329252 image-20211215084027976

【注:虽然能得出结果,但是这种由规则去推理方法很容易失效,通常情况下,我们还是得去求闭包,然后通过闭包求解。】


那么属性集X关于U上的函数依赖集F的闭包怎么求解呢?

下面介绍的是比较简单传统的闭包求法

求取属性集闭包的步骤

设要求的闭包属性集是Y,把Y初始化为X.
检查函数依赖集F中的每个函数依赖A->B,如果属性集A中的所有属性都在Y中,而B中有属性不在Y中,则将其加入到Y中。
重复第二步,直到没有属性可以添加到Y中为止。最后得出的Y就是X+。

上面的步骤虽然是别人简化过的,但还是很难看懂,下面我就说个人话把它复述一遍。

找属性集闭包,就是在函数依赖集上扫描,第一次循环,扫描第一个函数依赖,如果该函数依赖左边是我们闭包的子集,那我们就把它右边的属性添加到我们的闭包里,依次扫描过去,第一次循环结束。这时候,如果闭包已经是U,那么则不需要第二次循环,否则,进行第二次,如果第二次扫描和第一次结果还是一样, 那么扫描结束。得出结果为该闭包。

我们来讲几个例题:

image-20211016093318560

image-20211016094602659
image-20211016094255713 image-20211016103546205
image-20211016103713876 image-20211016103651878
image-20211016103839181 image-20211016103855757

12.2.6、函数依赖的代价和覆盖

\[ {引理} 如果G^+ = F^+,就说函数依赖集F覆盖G(F是G的覆盖,或G是F的覆盖),或F与G等价。 \]


$$ F^+ = G^+ \iff F \subseteq G^+,G \subseteq F^+ $$ 所以:我们说F等价于G,则F中的每个函数依赖可以从G推导出来,G中的每个函数依赖可以从F推导出来。

12.2.7、函数依赖集的最小覆盖

最小依赖集的定义

如果函数依赖集F满足下列条件,则称F为一个极小函数依赖集。也叫作最小依赖集或最小覆盖。\(我们把最小函数依赖集写作F_m\)

求解最小依赖集的步骤

(1)F中任一函数依赖的右部仅含单属性(但属性化)

(2)无冗余化

(3)既约化

【注:这里无冗余化和既约化太抽象,我不写出来,具体解法看下面。】


如何求最小函数依赖集

  1. 分解

  2. 消除冗余函数依赖

  3. 消除每个函数依赖左部的多余属性

【注:若进行过第三步则需要重新执行第二步】


例题

image-20211016110420766
image-20211016110515856 image-20211016110534628

12.2.8、正则覆盖

F的一个覆盖Fc,如果满足以下的性质,则称其为F的正则覆盖:

Fc中的任何函数依赖都不含无关属性;

Fc中函数依赖的左半部的唯一地,即不存在两个依赖的左侧完全相同。

【注:实际上我们只需知道一点即可,我们把F求得的最小函数依赖集Fm后,把左侧相同的函数依赖,依据合并律合并成一个,即为Fc】


12.3、关系模式的规范化

第一范式(1NF)

在传统的关系数据模型中,曾经限制关系的属性必须是原子的。这个限制条件被命名为第一范式。用俗话来说就是不允许表中套表。

image-20211003111048609

如上图,第一张表就是突破了一范式的限制,这样的表用Creat的SQL语句是建不出来的,所以必须要把子表给“拍平”;如果你不想拍平,你也可以再做一个张表,把一张表的属性拆成两张表。


第二范式(2NF)

在关系满足一范式的前提下,没有属性对产生部分函数依赖,那我们就说这个关系满足二范式。

image-20211003111133022

我们可以发现,虽然主键是(S#,C#),但是sname不靠主键,仅靠主键的S#即可确定,所以我们说其不满足二范式。

当然,这张表在数据库中可以建立,但是会出现很多问题。


违背第二范式所产生的问题:

  • 插入异常

    假如以上面的例子为例,我们现在有个新生刚来学校,但是他还没选课,导致C#为空,前面我们讲主键约束时曾经讲过主键不能为空,这就导致了包含了这个新生的基本信息的这条元组插不进表。

  • 删除异常

    如果一个学生不小心选错了课,那么如果做退课操作,他的所有信息就会丢失,因为主键不能为空嘛。

  • 更新艰难

    以上面例子为例,最后有个属性是grade,说明由于分数问题,一个人的其余基本信息也要照抄,假如每门课分数不一样,同个人就要重复写五十次,存在大量数据的冗余。这就是你表设计的结构不合理。


如何解决违背二范式所产生的问题?

根据“一事一地”的规则去设计你的表结构,一张表只管一件事。

因为我们可以看到上面的表出问题了就是因为我们一个表管了两件事。


第三范式(3NF)

在第二范式的基础上,不存在对主键的传递依赖即为三范式。

image-20211003111228963

也就是说,工资是由职工所在的工资等级决定的,而工资等级又是由员工编号决定的,这是一个传递依赖。


违背第三范式所产生的问题:

  • 插入异常

    现在拿上面的例子举例,如果有一批新员工进来了,虽然工资等级和工资数有参照,但是老板还没有分配那个员工对应那个工资等级,这时候这个表就没法录入数据。

  • 删除异常

    现在拿上面的例子举例,如果只有一个人拿三级工资,那么当这个人跳槽跑掉了,那么三级工资对应的那个工资等级也会被删掉。但是实际中,我想要的理想状态是即使这个员工跑掉了,但是三级工资等级对应的工资这个对照关系我还是要留着。

  • 修改艰难

    同样的,工资和员工编号有很多不一样的,但是工资等级有很多一样的,这样就会导致每个表都有SAL_LEVEL这个属性,而且每个都要写对,写错了就会导致数据不一致。

解决方法

一事一地。

一般只需要达到3NF的表就很合理了。


第四范式(4NF)

消除了多值依赖,其定义为:\(在关系模式R中,若存在非平凡多值依赖X→→Y,则X必为Y的超键。满足此条件的R属于4NF。\)


第五范式(5NF)

消除了连接依赖,其定义为:\(如果在关系模式R中,除了由超键构成的连接依赖外,别无其他连接依赖,则R属于5NF。\)



12.3.1、重要题型总结

一、范式判断

指出下列关系模式是第几范式?并说明理由。

(1) R(X,Y,Z) F={XY→Z}

(2) R(x,Y,z) F={Y→z,XZ→Y}

(3) R(X,Y,Z) F={Y→Z,Y→X,X→YZ}

(4) R(x,Y,z) F={X→Y,X→Z}

(5) R(x,Y,Z) F={XY→Z}

(6) R(W,X,Y,Z) F={X→Z,WX→Y}

image-20211215090941797

二、判断是否具有无损连接性

image-20211215092553265

三、分解是否保持函数依赖


12.4、数据库设计方法

数据库设计有两大类方法

面向数据的设计方法

面向数据的设计方法以信息需求为主,兼顾处理需求;用这种方法设计的数据库,可以比较好地反映数据的内在联系,不但可以满足当前应用的需要,还能满足潜在应用的需要。


面向过程的设计方法

面向过程的设计方法以处理需求为主,兼顾信息需求;用这种方法设计的数据库,可能在刚开始使用的时候比较好满足应用的需要,获得好的性能;但是在后期,随着应用发展,数据库会发生较大变动。所以这种设计方法一般不能在大型数据库使用,用在饭店管理就很不错。


12.5、数据库设计流程

image-20211004232656526

  1. 需求分析:设计一个数据库,首先必须确认数据库的用户和用途。而且在设计大型数据库时,用人工管理数据库的元数据是很困难的,比如还没有放入数据库的学生的各项属性各项数据项;我们一般用专用软件包或DBMS来管理这些数据,这称为数据字典。在设计数据库的时候还要注意语义问题,比如同名异义。

  2. 概念设计:在需求分析的基础上,通常用概念数据模型,一般是E-R数据模型,表示数据及其相互间的联系。在做E-R图时,我们要注意前面做数据字典时,里面的哪些数据项可以合在一起作为一个实体,实体和实体是什么关系;实体抽象出来后还要去定义实体之间的联系,是一对一还是一对多,在联系的基础上还要注意有无属性;在画E-R图的时候一边和用户讨论,让用户看E-R图是否能满足应用的设计。

    一般画ER图可以用ERWin、Rose、etc去设计。

  3. 逻辑设计:在逻辑设计阶段,要把第二步得到的以概念数据模型表示、与DBMS无关的数据模式,转换成以DBMS的逻辑数据模型表示的逻辑概念模式。在这一步上,由于要考虑到数据模式的规范化,可能会出现我们过度的追求三范式而导致单个表太小,在查询的时候会牵涉到多张表做连接,效率变低;所以有时候我们会逆规范化,虽然没能达到三范式,但是我们心里知道会出现什么问题,而采取对应的措施去避免。

  4. 物理设计:数据库物理设计的任务是:根据逻辑模式、DBMS及计算机系统所提供的手段和施加的限制,设计数据库的内模式,即文件结构、各种存取路径、存储空间的分配、记录的存储格式等。


  1. 注意的问题
    仅仅在结构上达到3NF(BCNF)是不够的。

  2. “一事一地”包括每项信息的唯一,要提取出问题的本质,识别出本质上同一概念的信息项。

  3. 对于表达类似信息,模式相似只是取值不同的表,应尽量合并。如学习经历、进修经历;奖励信息、惩处信息等。

  4. 考虑到效率、用途等因素,该分开的表还是要分开。

  5. 结合DBMS内部实现技术,合理设计索引和文件结构,为查询优化准备好存取路径。

  6. 在结构规范化、减少数据冗余和提高数据库访问性能之间仔细权衡,适当折中。


第十三章、分布式数据库

13.1、概述

什么是DDB?

DDB,英文全称distributed data base,英文全称分布式数据库系统通常使用较小的计算机系统,每台计算机可单独放在一个地方,每台计算机中都可能有DBMS的一份完整拷贝副本,或者部分拷贝副本,并具有自己局部的数据库,位于不同地点的许多计算机通过网络互相连接,共同组成一个完整的、全局的逻辑上集中、物理上分布的大型数据库。


DDB有两种

  • 物理上分布,逻辑上集中(一般式DDB)

  • 物理上分布,逻辑上分布(FDBS,联邦式数据库)


分布式数据库的概念图

image-20211005110558527

如上图,一个大的统一全局模式的数据库可以拆分成若干个小型数据库;实际上,统一全局模式的数据库是我们臆想的,现实不存在,只是在用户看来,分布式数据库的数据看起来就和集中式数据库看起来别无二致。

这时候我们会有疑问,为什么要搞一个这样的技术呢?这是由于在七十年代末八十年代初,局域网技术开始发展,单台计算机的处理能力很弱,而且单台计算机配备的磁盘内存也很小,如果要去处理一个如此庞大的数据库,单台计算机做不到。


分布式数据系统的特点

  • 分布性

    数据在物理层面上是分布的。

  • 相关性

    分布在结点数据库的数据并不是孤立的,他们存在相当紧密的联系。

  • 分布式数据库系统统一管理


分布式数据库系统的优点

  • 局部自治性:尽管数据库被划分成多个局部数据库,但是局部数据库对于存放自己区域的数据具有高度自治能力。

  • 可靠性好、可用性好:由于同一种数据有许多数据副本分布在多个结点数据库,即使部分结点数据库发生故障,也不会影响统一全局模式。发生故障的结点数据库先暂存处理,待处理完成后再弥补丢失的信息。

  • 灵活性好:在集中式数据库下,一旦数据膨胀,原有的数据库装不下了,那么数据库服务器就要更换;而在分布式数据库则不需要,只要以后有钱了,就多加几台机器,多加几个服务器,从管理维护上来讲,这种数据库更为灵活。

  • 经济性能优越:由于重要的工作分出来交给多个机器去工作,所以对于每台机器的要求不是很高。

  • 高效率:分布式数据库在设计初期,我们追求的是日常的查询最好是百分之九十以上在某一个结点的本地数据库就可以找到,而不是在目前结点找一点然后还要跑去其他的结点再找,如果分布式数据库设计成这个样子,那我们就理解为这个数据库设计的比较失败。所以我们尽管分布式数据库以依赖于网络进行数据的传送,但是分布式数据库恰恰追求的是可以在本地查询,而不必跑到多结点去查询。

  • 并行性:在分布式数据库里,每一台机器天生就是一个数据库,是网络上的一个结点,这种架构天生就有并行性。


13.2、需要解决的主要问题

分布式数据库主要的问题

  • 系统开销太大,主要花在通信方面维护。

  • 复杂的存取结构。

  • 数据的安全性和保密性较难处理。

  • 很难让已经存在的数据库融合成一个整体(集成问题)。


分布式数据库和集中式数据库的差异

  1. 查询优化问题(优化的目标不一致)

    在分布式数据库提出来的时候,那时候用的是以太网,带宽才10M,集中式数据库那时候考虑的是如何从磁盘快速提取信息,而分布式数据库考虑的是在网上传送的数据量动手脚。

  2. 并发控制

    在分布式数据库上,我们考虑的不仅仅是单个数据库的事务冲突问题,还要考虑各个结点数据库的冲突问题。

  3. 恢复机制

    恢复机制我们要考虑的问题是,我们联网的多台机器之间,一个故障组合的问题。如果是机器1坏了,恢复后如何和其他机器数据保持一致;如果是机器1和机器2坏了呢,如果是机器1和机器3坏了呢?组合会很多,问题变的很复杂。

  4. 数据分布问题

    一个统一的全局模式的数据,按照什么原则去切割,去分布在各结点的数据库。


13.3、数据分布

一个好的分布式数据库,做出来用户是感觉不到数据是分布的。


数据分布策略

  • 集中式策略:分布式系统,但是数据仍然集中存放。

  • 划分式策略:数据没有多副本,不同的数据元素只存在于一个结点中。

  • 全复制策略:就是所有的结点数据库都是完备数据库的拷贝。但是这种策略不适合更新,只适合以读为主的数据库。

  • 混合式策略:数据的分布按照应用的需要去划分,没有任何限制。


数据分布的单位

以关系为单位:例如现在有三张表,学生表,课程表,班级表,现在教务处系统需要学生表,学习学籍系统也需要学生表,那么每个结点数据库系统都留一张表。但是这也会出现一种情况,就是以关系为单位来分布的时候,粒度就显得太粗了,比如计算机学院的系统需要计算机系学生的全部信息,就需要拿学生表,但是学生表记录的是全校的,一个学校若有三四万人,计算机系只有几千人,那就多了很多结点数据库不需要的元组进来了。

根据裂片为单位:就是把一个关系按照水平(元组)或者垂直(属性)或者混合的分割,把它切割成若干个裂片。把不同的裂片放进不同的结点数据库里。

分割准则

  1. 保证表的完整性

  2. 可重构性

  3. 不相交性


以裂片为单位的数据分布带来的问题

  1. 多副本一致性

    因为在分布式数据库里,对表做分割产生的裂片可以在结点数据库有多个副本,那么更新的时候就要保证所有的副本都要更新到。

  2. 分布一致性

    每一条元组更新后,必须还在他所划分的裂片里面。对于不在的,我们有以下的两种方法去解决。

    (1)重新分布

    在每次更新后然后去检查,一旦检查到该元组不属于这个裂片,就对他进行搬家。

    (2)背回

    创建一个缓冲区(相当于背包),在更新过程中遇到被分布一致性被破坏的元组,就把它放进背包,随着ACK信息回来,更新结束后系统收到背包,打开背包把分布一致性被破坏的元组重新分布。

  3. 翻译

    当用户写进一个查询,用户通过视图是不知道一张表被我们切割成多个部分分到各个结点数据库里了,所以一条查询进来时,分布式数据库系统就会把查询进行翻译,然后通过数据字典去查表的对应内容被分到哪块区域上了,最后去对应的结点数据库查询。

  4. 对表设计分割的方式

    一个表要根据应用程序的需要去做分割,如果一旦这个地方处理的不好,就会造成分布式数据库系统的效率低下。

    以上的1-3点是DDBMS开发的问题,而第4点是分布式数据库的设计问题。


13.4、联邦式数据库系统(了解)

在实践中,由于分布式数据库不能在原有的小型数据库的基础上继续建立分布式数据库,而只能“打乱”,或者说“重新推翻”,在空白的基础上建立分布式数据库,所以分布式数据库在现实中很少使用。

而联邦式数据库则不需要“重新洗牌”,只需要在原有的基础上继续建立多个局部数据库,多个局部数据库之间互相协作共同管理数据库,而不是像分布式数据库一样由分布式数据库系统统一管理。

所以,一般来说我们说联邦式数据库采用了一种松耦合的方法,而分布式数据库采用了一种紧耦合的方法。

联邦数据库系统(FDBS)是一个彼此协作却又相互独立的单元数据库(CDBS)的集合,它将单元数据库系统按不同程度进行集成,对该系统整体提供控制和协同操作的软件叫做联邦数据库管理系统(FDBMS),一个单元数据库可以加入若干个联邦系统,每个单元数据库系统DBMS的可以是集中式的,也可以是分布式的,或者是另外一个FDBMS 。


联邦式数据库实现集成的方式

成员之间互相协商,来决定各自的输入或输出模式。

输入表示可以从其他局部数据库中取来数据供自己使用。

输出表示自己可以分享数据供别人使用。


第十四章、数据库新技术及其应用

14.1、数据仓库概述

以前我们学的一般的数据库系统做的应用,都叫做OLTP应用,OLTP应用实际上就是联机事务处理应用,在他背后支持的数据库,我们叫做操作型数据库,OLTP实际上是一种特殊的事务处理。


数据处理的方式

操作型处理

操作型处理也叫事务型处理,是指对数据库联机的日常操作,通常是对一个或一组记录的查询和修改,主要是为企业的特定数据管理应用。

分析型处理

分析型处理则用于管理人员对的决策分析,其目的是对这些历史数据的分析,从中提取管理决策所需要的重要信息。


数据仓库的提出是由于网络的积累面临着海量的数据,如何有效地利用这些数据,如何在这些数据中寻找有用的信息,就成了人们关心的问题 ,一旦这些海量的数据不去处理,那么他们就会变成垃圾。所以人们逐渐尝试对数据库中的数据进行再加工,形成一个综合的、面向决策分析应用的环境,以更好地支持决策分析。


所以数据仓库是什么?

数据仓库:是一个专门用于支持企业或部门的管理决策分析的、面向主题的、集成的、不可更新的、随时间不断变化的数据集合,他是一种特殊的数据库。

注:数据仓库也有客户不拿来做决策分析,而是拿去做数据集成。

所以对于数据仓库来说,数据是最重要的的来源,最常见的来源有两种:一种是面向OLTP应用的数据库中定期传送过来的数据;另一种是外部购买或采集的数据。


数据仓库系统的组成

建立一个大型的数据仓库是为了保证OLTP不会被支持决策的部分影响到。

image-20211006093851237

我们利用数据抽取工具对数据源作ETL(Extract抽取,Transform转换,Load加载)

的操作,然后形成一个数据仓库;数据仓库又可以支持数据挖掘,多维数据分析等决策分析应用。


那为什么要重新建一个数据仓库?直接在OLTP数据库里面操作不行吗?

1、OLTP数据库的组成是适合日常事务处理的,并不适合决策分析。

2、一次操作牵涉的数据量过大,在OLTP数据库上是跑不动的。


数据仓库的主要问题

语义集成问题:当从不同的数据源获得数据时,有很多语义方面的问题需要去解决。

异构数据源问题:在对操作型数据抽取出来作集成时,往往会遇见来自不同数据库的数据源,那么在数据仓库中如何统一地去访问就成了一个问题,还有格式统一问题,都是需要去解决的。

加载、刷新和清洗:必须加载数据,然后按时更新,还有要把老的数据清洗掉。

元数据的管理:元数据是描述数据仓库内数据的结构和建立方法的数据,是数据仓库的重要组成部分。他的作用相当于数据库系统中的数据字典。主要有两种元数据:

1、为了从操作型环境向数据仓库环境转换而建立的元数据,它包含了所有源数据项目名、属性及其在数据仓库中的转换。

2、元数据在数据仓库中是用来与终端用户的多维商业模型/前端工具之间建立映射,这种元数据称为DSS元数据,常用来支持开发更先进的决策支持工具。


利用数据仓库主要解决的方案或应用

联机分析处理(多维分析处理)OLAP

分析或查询报表

数据挖掘


14.2、OLTP和OLAP

通常我们说的OLTP指的是On-Line Transaction Processing,而OLAP指的是On-Line Analytical Processing。

image-20211006092500855

分析型数据的特点

需要综合的,或提炼的数据

操作型数据要求是细节的,比如别人去超市买了东西,结果发现是坏的要退货,如果操作型数据不细节,那就不知道这位顾客是否在超市买了东西,也就退不了货;同理,如果是要拿来从分析,我们不可能对着某一个商品的卖出记录进行分析,而是要看这个商品某一个时间段的卖出趋势。

需要历史的数据

我们需要的是过去积累的数据,利用过去积累的数据来预测未来的走向。

需要外部的数据

一些商业化的数据库,不仅要针对自家公司的发展作决策,面对市场的走向作决策也同样重要。

数据的组成方式不一样

在前面学的数据库设计时,我们往往考虑三范式等要求,这是因为我们面向应用去设计数据库;而对于数据仓库,我们是面向决策主题去设计数据库,围绕决策主题去设计数据库。

数据不需要实时更新,他们主要用来读

都是统计数据,都是按照以前来分析,不可能说超市多卖了个包子,马上就更新加入决策范围。


OLAP的特性

  • 快速性

  • 可分析性

  • 多维性

  • 信息性


14.3、数据仓库案例

最开始的这个表是操作型数据;这个表记录了某一服装店每次售出的衣服颜色、大小以及数据,相当于每一笔流水,从这张表中我们很难从中分析到什么规律。

image-20211006100612183

这个交叉表(二维表)是经过统计的数据,但是我们知道,在数据库里面是存不了这种二维表的,怎么办呢,我们可以把它拉开。

image-20211006100631015

我们用关系来描述,这实际上就相当于是一个分析型数据库中的一个表了。

image-20211006100943160

一般来说我们的数据仓库不止是二维。

多维数据概念

多维数据中有以下概念

1、变量:变量是对数据实际意义的描述,即,描述数据是什么,例如:“人数”,“销售量”。

2、维:维是人们观察的角度,例如:企业常常关心产品销售数据随着时间推移而产生的变化情况,所以时间就是一个维。

3、维的层次:人们在某个维上观察数据还可以根据粒度继续划分,例如:时间的粒度可以分为年、月、日等;地区的粒度也可以分为国家、省、城市。

4、维成员:维的一个取值就叫该维的一个维成员。

image-20211006103717410

5、数据单元(单元格):多维数组的取值就叫数据单元,当多维数组的各个维都选中一个维成员时,这些维成员的组合就唯一确定了一个变量的值,如(4月份,上海,服装,1000)就是一个数据单元。

当然,如果想要看其他维度的销售额情况,我们可以对这个立方体做翻转、切割操作,看一下其他面的数据。


14.4、星型模式和雪花模式

那前面我们说过可以把数据做成一个立方,那如何在数据仓库中存储一个立方呢?有如下集中方法。

1.ROLAP
ROLAP将分析用的多维数据存储在关系数据库中并根据应用的需要有选择的定义一批实视图作为表也存储在关系数据库中。不必要将每一个SQL查询都作为实视图保存,只定义那些应用频率比较高、计算工作量比较大的查询作为实视图。对每个针对OLAP服务器的查询,优先利用已经计算好的实视图来生成查询结果以提高查询效率。同时用作ROLAP存储器的RDBMS也针对OLAP作相应的优化,比如并行存储、并行查询、并行数据管理、基于成本的查询优化、位图索引、SQL的OLAP扩展(cube,rollup)等等。

2.MOLAP
MOLAP将OLAP分析所用到的多维数据物理上存储为多维数组的形式,形成“立方体”的结构。维的属性值被映射成多维数组的下标值或下标的范围,而总结数据作为多维数组的值存储在数组的单元中。由于MOLAP采用了新的存储结构,从物理层实现起,因此又称为物理OLAP(PhysicalOLAP);而ROLAP主要通过一些软件工具或中间软件实现,物理层仍采用关系数据库的存储结构,因此称为虚拟OLAP(VirtualOLAP)。

3.HOLAP
由于MOLAP和ROLAP有着各自的优点和缺点(如下表所示),且它们的结构迥然不同,这给分析人员设计OLAP结构提出了难题。为此一个新的OLAP结构——混合型OLAP(HOLAP)被提出,它能把MOLAP和ROLAP两种结构的优点结合起来。迄今为止,对HOLAP还没有一个正式的定义。但很明显,HOLAP结构不应该是MOLAP与ROLAP结构的简单组合,而是这两种结构技术优点的有机结合,能满足用户各种复杂的分析请求。


多维数据模型是最流行的数据仓库的数据模型,多维数据模型最典型的数据模式包括星型模式、雪花模式和事实星座模式,本笔记只记录星型模式和雪花模式。

在多维分析的商业智能解决方案中,根据事实表和维度表的关系,又可将常见的模型分为星型模型和雪花型模型。在设计逻辑型数据的模型的时候,就应考虑数据是按照星型模型还是雪花型模型进行组织。

1.星型模式的核心是一个大的中心表(事实表),一组小的附属表(维表)。星型模式示例如下所示:

当所有维表都直接连接到“ 事实表”上时,整个图解就像星星一样,故将该模型称为星型模型,如图 1 。

星型架构是一种非正规化的结构,多维数据集的每一个维度都直接与事实表相连接,不存在渐变维度,所以数据有一定的冗余,如在地域维度表中,存在国家 A 省 B 的城市 C 以及国家 A 省 B 的城市 D 两条记录,那么国家 A 和省 B 的信息分别存储了两次,即存在冗余。

星型模型因为数据的冗余所以很多统计查询不需要做外部的连接,因此一般情况下效率比雪花型模型要高。星型结构不用考虑很多正规化的因素,设计与实现都比较简单。雪花型模型由于去除了冗余,有些统计就需要通过表的联接才能产生,所以效率不一定有星型模型高。正规化也是一种比较复杂的过程,相应的数据库结构设计、数据的 ETL、以及后期的维护都要复杂一些。因此在冗余可以接受的前提下,实际运用中星型模型使用更多,也更有效率。


2.雪花模式是星型模式的扩展,其中某些维表被规范化,进一步分解到附加表(维表)中。雪花模式示例如下图所示:

技术分享

从图中我们可以看到地址表被进一步细分出了城市(city)维。supplier_type表被进一步细分出来supplier维。


二、使用选择

星形模型(Star Schema)和雪花模型(Snowflake Schema)是数据仓库中常用到的两种方式,而它们之间的对比要从四个角度来进行讨论。


模型的使用和选择

  1.数据优化

雪花模型使用的是规范化数据,也就是说数据在数据库内部是组织好的,以便消除冗余,因此它能够有效地减少数据量。通过引用完整性,其业务层级和维度都将存储在数据模型之中。

相比较而言,星形模型实用的是反规范化数据。在星形模型中,维度直接指的是事实表,业务层级不会通过维度之间的参照完整性来部署。


  2.业务模型

主键是一个单独的唯一键(数据属性),为特殊数据所选择。在上面的例子中,Advertiser_ID就将是一个主键。外键(参考属性)仅仅是一个表中的字段,用来匹配其他维度表中的主键。在我们所引用的例子中,Advertiser_ID将是Account_dimension的一个外键。

在雪花模型中,数据模型的业务层级是由一个不同维度表主键-外键的关系来代表的。而在星形模型中,所有必要的维度表在事实表中都只拥有外键。

  3.性能

第三个区别在于性能的不同。雪花模型在维度表、事实表之间的连接很多,因此性能方面会比较低。举个例子,如果你想要知道Advertiser 的详细信息,雪花模型就会请求许多信息,比如Advertiser Name、ID以及那些广告主和客户表的地址需要连接起来,然后再与事实表连接。

而星形模型的连接就少的多,在这个模型中,如果你需要上述信息,你只要将Advertiser的维度表和事实表连接即可。

  4.ETL

雪花模型加载数据集市,因此ETL操作在设计上更加复杂,而且由于附属模型的限制,不能并行化。

星形模型加载维度表,不需要再维度之间添加附属模型,因此ETL就相对简单,而且可以实现高度的并行化。


14.5、实视图

星型模式和雪花模式是数据仓库存储数据的普遍形式,但是在做决策计算的时候我们不能直接在星型模式和雪花模式的基础上计算,因为这样效率会比较低;所以我们通常是把各个维度的统计实现计算好,然后以一个实视图的方法存起来,这样的话在后面的多维统计就能提高效率。

实视图,也有些书叫做物化视图,他相当于一个虚表,是基于基表算出来的映射。

根据不同维度的统计,可以做成一张张的实视图。

当我们把所有的实视图拼在一起的时候,就形成了一个数据立方。


14.6、数据立方和OLAP操作

数据立方上面我们已经提过了,在这里我们只提OLAP操作。

  • 上卷

    往更大的粒度观察数据,例如按月看销售额不够看,要看年的,那就进行上卷操作。

  • 下钻

    往更小的粒度观察数据,例如按月看销售额粒度太粗了,那我们就进行下钻操作,看天的销售额。

  • 翻转

    要看不同的面,那我们就需要翻转

  • 切片

    要查看某个维,但是隐藏在立方里面,那这个时候我们就需要切出来


在我们先前的例子中,可以总结出来,如果有k个维,那么立方体就有2的k次方个视图。为了方便把这些视图做一个立方体,SQL添加了一个语法:CUBE语法。

如:

cube pid,sid,did by sum sales

所以下面我们也可以利用CUBE语法来查询数据立方。

cube(TV,No1,Q1)

说明:表明想要查询TV在一号店的第一季度销售额是多少。

cube(all,No1,Q1)

说明:表明想要查询所有产品在一号店的第一季度销售额是多少。

cube(all,all,Q1)/cube(all,all,all)*100%

说明:cube语法里也同样支持运算,以上语句表明想要查询第一季度的销售额在所有销售额里占的比例是多少。


写在最后及致谢

本笔记有许多不足,包括一些语义上的歧义,但是重点围绕理解和复习进行展开,很多东西不是累赘而是反复强调。王能斌老师的《数据库系统教程》是我学习数据库过程中见过最好的书,虽然里面有一些技术在现在2021年都已经实现了,还有些已经落伍了,但是并不妨碍我们的学习。


课外补充

在研究生课程中,我还漏了几节,

在关系演算一章里我本来是有面向对象数据模型和基于逻辑的数据模型的,但是由于写的太糟心了就删了,这一部分详细可以参考王老师的书。

在并发控制一章中,应该有索引的并发控制、幽灵及其防止、基于时间标记的并发控制技术、乐观并发控制技术等。

由于本科我也不学那么多,所以就不过多叙述了,详细的知识王老师书上也有写,自己去看吧-_-!

在数据库研究新领域中Nomysql没有录入,XML数据库也没有录入,这个由于我跟的是徐立臻老师08年的课,那时候没有这些技术,王老师的课本也没说,所以如果想了解这方面可以去看黄老师写的《数据库原理及其应用教程》。

触发子一章规则的执行和规则的实现我也没有在笔记里写,主要是太抽象并且不是考点,如果需要推荐去一些博客里逛逛。


华南师范大学研究生数据库考纲

一、考查目标

《数据库原理》重点考查学生数据库原理基础理论和应用实践创新能力。要求考生深入理解和掌握数据库原理基础概念、理论体系和实践方法,精通运用数据库原理和方法进行数据管理应用实践、具有综合解决数据库相关实践问题的创新能力。

二、考试形式

  1. 试卷满分及考试时间:满分为50分,考试时间为60分钟。

  2. 答题方式:闭卷、笔试。

三、考试范围

​ 1.数据库基本概念和原理

1.1 层次模型、网状模型、关系模型、面向对象数据模型等数据模型的要素及特点

1.2 数据库、数据库系统、数据库管理系统

  1. 关系数据库

2.1 关系数据结构及形式化定义、关系代数的运算、关系的完整性约束

2.2 函数依赖和关系模式规范化

  1. 关系数据库标准语言

3.1 SQL数据定义、数据查询、数据更新语言

3.2 SQL语言和关系代数相互转换

3.3 SQL查询优化

  1. 数据库的设计和开发

4.1 数据库设计的过程和主要内容

4.2 E-R模型和关系的转化方法

  1. 数据库运行与维护

5.1 事务的基本概念、特点和事务运行模型

5.2 数据库安全的含义和安全控制方法

5.3 数据库并发控制和可串行化调度

5.4 数据库完整性控制及约束条件

5.5 数据库备份与恢复机制

  1. 高级数据管理相关技术

6.1 主动数据库技术、时态数据库技术、分布式数据库等

6.2 大数据处理相关技术,包括存储模式、计算模型、资源调度模式

四、考试题型

考试题型包括有填空题、选择题、简答题、判断题、综合分析题等


引用

百度百科

《数据库系统教程》—东南大学王能斌

《数据库原理及其应用教程》—黄德才

《数据库系统概念》—杨冬青、李红燕、唐世谓


https://www.cnblogs.com/wzhao-cn/p/11557698.html

https://www.cnblogs.com/rocky-AGE-24/p/7629396.html

https://blog.csdn.net/weixin_44538395/article/details/86772034

https://blog.csdn.net/jgm20475/article/details/81297819

https://blog.51cto.com/u_12009752/1843733

https://www.cnblogs.com/ssihc/archive/2006/07/31/464258.html

https://lgwx.qq.com/web201809/pc/trigger-help/1_basic_rule.html

https://blog.csdn.net/xr_acmer/article/details/22893987

https://blog.csdn.net/m0_46670811/article/details/109526906?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163435021116780366574780%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163435021116780366574780&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-109526906.first_rank_v2_pc_rank_v29&utm_term=%E5%B1%9E%E6%80%A7%E9%9B%86%E9%97%AD%E5%8C%85&spm=1018.2226.3001.4187

https://www.bilibili.com/video/BV1NZ4y1c7uZ?spm_id_from=333.999.0.0

posted on 2021-12-30 00:48  尘鱼好美  阅读(2581)  评论(0编辑  收藏  举报