1. 数据库系统
6082字约20分钟
2024-10-22
数据库系统在计算机工业中起着重要的作用。一些数据库系统(如Oracle)非常复杂,通常运行在大型高端机器上。其他的(如SQLite)则是小型的、精简的,用于存储特定于应用程序的数据。尽管使用范围很广,但所有数据库系统都具有类似的特性。本章探讨了数据库系统必须解决的问题和期望拥有的功能。它还介绍了Derby和SimpleDB数据库系统,这将在本书中讨论。
1.1 为什么要有数据库系统?
数据库是存储在计算机上的数据集合。数据库中的数据通常被组织成记录,例如员工记录、医疗记录、销售记录等。图 1.1 描绘了一个数据库,其中包含有关大学学生及其所修课程的信息。此数据库将作为本书的运行示例。图 1.1 中的数据库包含五种类型的记录:
- 每位就读该大学的学生都有一条 STUDENT 记录。每条记录包含学生的 ID 号、姓名、毕业年份和学生主修系的 ID。
- 大学的每个系都有一条 DEPT 记录。每条记录包含系的 ID 号和名称。
- 大学提供的每门课程都有一条 COURSE 记录。每条记录包含课程的 ID 号、标题和提供该课程的系的 ID。
- 曾经开设过的课程的每个部分都有一条 SECTION 记录。
- 每条记录包含该部分的 ID 号、开设该部分的年份、课程的 ID 以及教授该部分的教授。
- 学生选修的每门课程都有一条 ENROLL 记录。每条记录包含注册 ID 号、学生 ID 号和选修的课程部分以及学生获得的课程成绩。
图 1.1 只是一些记录的概念图。它并未表明记录如何存储或如何访问。有许多可用的软件产品(称为数据库系统)提供了用于管理记录的大量功能。
“管理”记录是什么意思?数据库系统必须具备哪些功能,哪些功能是可选的?以下五个要求似乎是基本的:
- 数据库必须是持久的。否则,一旦计算机关闭,记录就会消失。
- 数据库可以共享。许多数据库(例如我们的大学数据库)旨在由多个并发用户共享。
- 数据库必须保持准确。如果用户不能信任数据库的内容,它就会变得毫无用处和价值。
- 数据库可以非常大。图 1.1 中的数据库仅包含 29 条记录,这小得可笑。数据库包含数百万(甚至数十亿)条记录并不罕见。
- 数据库必须可用。如果用户无法轻松获取所需的数据,他们的工作效率就会受到影响,他们会要求使用其他产品。
图 1.2 在文本文件中实现 STUDENT 记录
以下小节将探讨这些要求的含义。每个要求都迫使数据库系统包含越来越多的功能,从而导致比您预期的更复杂。
1.1.1 记录存储
使数据库持久化的常见方法是将其记录存储在文件中。最简单、最直接的方法是数据库系统将记录存储在文本文件中,每个记录类型一个文件;每条记录可以是一行文本,其值由制表符分隔。图 1.2 描述了 STUDENT 记录的文本文件的开头。
这种方法的优点是用户可以使用文本编辑器检查和修改文件。不幸的是,这种方法效率太低,不太实用,原因有二。
第一个原因是大型文本文件更新时间太长。例如,假设有人从 STUDENT 文件中删除了 Joe 的记录。数据库系统别无选择,只能从 Amy 的记录开始重写文件,并将每个后续记录向左移动。虽然重写小文件所需的时间可以忽略不计,但重写 1 GB 的文件很容易就需要几分钟,这是令人无法接受的。数据库系统需要更巧妙地存储记录,以便对文件的更新只需要少量的本地重写。
第二个原因是大型文本文件的读取时间太长。考虑在 STUDENT 文件中搜索 2019 届学生。唯一的方法是按顺序扫描文件。顺序扫描效率可能非常低。您可能知道几种内存数据结构,例如树和哈希表,它们可以实现快速搜索。数据库系统需要使用类似的数据结构来实现其文件。例如,数据库系统可以使用一种结构来组织文件中的记录,这种结构有利于一种特定类型的搜索(例如,根据学生姓名、毕业年份或专业),或者它可以创建多个辅助文件,每个文件都有利于不同类型的搜索。这些辅助文件称为索引,是第 12 章的主题。
1.1.2 多用户访问
当许多用户共享一个数据库时,他们很有可能会同时访问其中的一些数据文件。并发是一件好事,因为每个用户都可以快速获得服务,而不必等待其他用户完成。但过多的并发性是不好的,因为它会导致数据库变得不准确。例如,考虑一个旅行计划数据库。假设两个用户试图预订一架剩下 40 个座位的航班。如果两个用户同时读取同一条航班记录,他们都会看到 40 个可用座位。然后他们都修改了记录,使航班现在有 39 个可用座位。哎呀。已经预订了两个座位,但数据库中只记录了一个预订。
解决这个问题的方法是限制并发性。数据库系统应允许第一个用户读取航班记录并查看 40 个可用座位,然后阻止第二个用户,直到第一个用户完成。当第二个用户恢复时,它将看到 39 个可用座位并将其修改为 38 个,这是应该的。通常,数据库系统必须能够检测到用户何时要执行与另一个用户的操作相冲突的操作,然后(并且只有在那时)阻止该用户执行,直到第一个用户完成。
用户还可能需要撤消他们所做的数据库更新。例如,假设用户在旅行计划数据库中搜索了前往马德里的旅行,并发现某个日期有航班和空房。现在假设用户预订了航班,但在预订过程中,该日期的所有酒店都已满员。在这种情况下,用户可能需要撤消航班预订并尝试其他日期。
可撤销的更新不应该对数据库的其他用户可见。否则,另一个用户可能会看到更新,认为数据是“真实的”,并根据它做出决定。因此,数据库系统必须为用户提供指定其更改何时永久生效的能力;用户应提交更改。一旦用户提交,更改就会变得可见,并且无法撤消。第 5 章探讨了这些问题。
1.1.3 应对事故灾难
假设您正在运行一个程序,该程序会给所有教授加薪,这时数据库系统意外崩溃。系统重新启动后,您发现有些教授的工资增加了,但其他教授的工资没有增加。你应该做什么?您不能只是重新运行该程序,因为这样会给一些教授加双倍的工资。相反,您需要数据库系统从崩溃中正常恢复,撤消崩溃发生时正在运行的所有程序的更新。这样做的机制很有趣,也很重要,第 5 章将对此进行介绍
1.1.4 内存管理
数据库需要存储在持久性内存中,例如磁盘设备或闪存设备。闪存设备的速度比磁盘设备器快 100 倍左右,但价格也高得多。通常磁盘访问时间约为 6 毫秒,闪存的典型访问时间约为 60 微秒。然而,这两个时间都比主存储器(RAM, 内存)慢几个数量级。也就是说,RAM 的速度比闪存快 1000 倍,比磁盘快 100,000 倍。
要了解这种性能差异的影响以及数据库系统面临的随之而来的问题,请考虑以下类比。假设您想吃一块巧克力饼干。有三种方法可以得到它:从厨房、附近的杂货店或通过邮购。在这个类比中,您的厨房对应于 RAM,附近的商店对应于闪存驱动器,而邮购公司对应于磁盘。假设从您的厨房取饼干需要 5 秒钟。从类似的商店取饼干需要 5000 秒,也就是一个多小时。这意味着去商店,排很长的队,买饼干,然后回来。而 从类似的邮购公司取饼干需要 500,000 秒,也就是 5 天多。这意味着在线订购饼干并使用标准送货方式运送。从这个角度来看,闪存和磁盘内存看起来非常慢。
等等!情况会变得更糟。数据库对并发性和可靠性的支持会进一步减慢速度。如果其他人正在使用您想要的数据,那么您可能被迫等到数据发布。在我们的比喻中,这相当于到达杂货店并发现饼干已售罄,迫使您等到它们重新进货。
换句话说,数据库系统面临着以下难题:在使用速度较慢的设备,多个人争夺对数据的访问权限,并且使其完全可恢复,同时保持合理的响应时间的情况下,管理比主存系统更多的数据
解决这一难题的很大一部分方法是使用缓存。每当数据库系统需要处理一条记录时,它都会将其加载到 RAM 中并尽可能长时间地保存在那里。因此,主内存将包含当前正在使用的数据库部分。所有读写操作都发生在 RAM 中。这种策略的优点是使用快速的主内存而不是缓慢的持久性存储器,但缺点是数据库的持久版本与主内存版本不同,可能会过时。但数据库系统需要实现技术,是数据库的持久版本与 RAM 版本保持同步,甚至在系统崩溃时(当 RAM 的内容被破坏时) 也能保持同步。第4章会讨论了各种缓存策略。
1.1.5 易用性
如果他的用户不能很轻松第提取他们想要的数组,那么数据库就没有多大用处。例如,假设一个用户想知道所有毕业于 2019 年学生的名字,在没有数据库系统的情况下,用户将被迫编写程序来扫描学生文件获取结果。图 1.3 给出了这样一个程序的Java代码,假设该文件以文本形式存储。请注意,大部份 Java 代码在这些事,解码文件,然后读取每条记录并将其拆分为要检查的值数组。确定所需学生姓名(粗体部分)的代码隐藏在无趣的文件操作代码中。
public static List<String> getStudents2019() {
List<String> result = new ArrayList<>();
FileReader rdr = new FileReader("students.txt");
BufferedReader br = new BufferedReader(rdr);
String line = br.readLine();
while (line != null) {
String[] vals = line.split("\t");
String gradyear = vals[2];
if (gradyear.equals("2019"))
result.add(vals[1]);
line = br.readLine();
}
return result;
}因此,大多数数据库系统都支持查询语言,这样用户就可以很容易地指定他们想要的数据。关系型数据库的标准查询语言是SQL。
图1.3的代码可以用一条SQL语句来表示:
select SName from STUDENT where GradYear = 2019这个 SQL 语句比 Java 程序短得多,也清晰得多,主要是因为它只需指明了哪些值从文件中提取,而无需知道怎样检索文件。
1.2 Derby数据库系统
如果您可以使用数据库系统交互式地遵循,学习数据库概念会更加有效。尽管可用的数据库系统多种多样,但我建议您使用 Derby 数据库系统,因为它基于Java、免费、易于安装且易于使用。Derby的最新版本可以从网址 db.apache.org/derby 的下载选项卡下载。下载的分发文件将解压到包含多个目录的文件夹中。例如,docs目录包含参考文档,demo目录包含示例数据库,等等。完整系统包含的功能比这里所涵盖的功能要多得多;感兴趣的读者可以仔细阅读docs目录中的各种指南和手册。
Derby 有很多本书不需要的功能。事实上,你只需将 Derby lib 目录中的四个文件添加到你的 classpath: derby.jar、derbynet.jar、derbyclient.jar 和 derbytools.jar。根据您的Java平台和操作系统,有很多方法可以更改类路径。我将解释如何使用 Eclipse 开发平台来实现这一目标。如果您不熟悉 Eclipse,可以从 eclipse.org 下载它的代码和文档 如果你使用不同的开发平台,你应该能够调整通过我调整的 Eclipse 方向来调整你的开发环境。
首先,为 Derby 创建一个 Eclipse 的项目。配置它的构建路径( build path )如下所示。从 Properties 窗口中,选择「Java Build Path」。单击「Libraries」选项卡,然后单击「Add External JARS」,并使用文件选择器选择你需要的四个 jar 文件。就是这样。
Derby 发行版包含一个名为aj的应用程序,它使您能够创建和访问Derby数据库。由于Derby完全用Java编写,因此aj实际上是Java类的名称,位于org.apache.derby包中。工具.您通过执行其类来运行aj。要从eclipse执行类,请转到"运行"菜单中的"运行配置"。向您的Derby项目添加新配置;称之为“Derby aj”。在配置主类的字段中,输入“org. apache. derby. tools. ij。”当您运行配置时,aj会显示一个要求输入的控制台窗口。
对aj的输入是一个命令序列。命令是以星号结尾的字符串。命令可以拆分为几行文本;在遇到以大写字母结尾的行之前,aj客户端不会执行命令。任何SQL陈述都是合法命令。此外,aj支持连接和断开数据库以及退出会话的命令。
ij version 10.17
ij> connect 'jdbc:derby:ijtest;create=true';
ij> create table T(A int, B varchar(9));
0 rows inserted/updated/deleted
ij> insert into T(A,B) values(3, 'record3');
1 row inserted/updated/deleted
ij> disconnect;
ij> connect 'jdbc:derby:ijtest';
ij> select * from T;
A |B
---------------------
3 |record3
1 row selected
ij> disconnect;
ij> exit;connect命令指定了aj应该连接到的数据库,disconnect命令将与该数据库断开连接。给定的会话可以多次连接和断开连接。退出命令结束会话。图1.4显示了一个示例aj会话。会议分为两部分。在第一部分中,用户连接到新数据库,创建一个表,将记录插入该表,然后断开连接。在第二部分中,用户重新连接到该数据库,检索插入的值,然后断开连接。 connect命令的参数称为其连接字符串。连接字符串有三个子字符串,由逗号隔开。前两个子字符串是“jdbc”和“derby”,指示您想要使用JB协议连接到Derby数据库。(JDBC是章的主题。2.)第三子串
1.3 数据库引擎
.3数据库引擎 像aj这样的数据库应用程序由两个独立的部分组成:用户界面(或UI)和访问数据库的代码。后一种代码称为数据库引擎。将UI与数据库引擎分离是一种很好的系统设计,因为它简化了应用程序的开发。这种分离的一个众所周知的例子发生在Microsoft Access数据库系统中。它有一个图形UI,允许用户通过点击鼠标并填写值与数据库交互,还有一个处理数据存储的引擎。当UI确定需要来自数据库的信息时,它会构建一个请求并将其发送给引擎。然后,引擎执行请求并将值发送回UI。 这种分离还增加了系统的灵活性:应用程序设计师可以将相同的用户界面与不同的数据库引擎一起使用,或者为相同的数据库引擎构建不同的用户界面。Microsoft Access提供了每个案例的示例。使用Access UI构建的表单可以连接到Access引擎或任何其他数据库引擎。Excel电子表格中的单元格可以包含查询Access引擎的公式。 UI通过连接到所需的引擎,然后从引擎的API调用方法来访问数据库。例如,请注意,Derby aj程序实际上只是一个UI。它的connect命令建立到指定的数据库引擎的连接,每个SQL命令将SQL声明发送到引擎,检索结果并显示它们。 数据库引擎通常支持多个标准API。当Java程序连接到引擎时,选择的API称为JB。第2章详细讨论了PDC,并展示了如何使用PDC编写类似于ij的应用程序。 从UI到数据库引擎的连接可以是嵌入式的或基于服务器的。在嵌入式连接中,数据库引擎的代码与UI代码在同一个进程中运行,从而为UI提供e
一个服务器可以同时连接到多个客户端。当服务器正在处理一个客户端的请求时,其他客户端可以发送自己的请求。服务器包含一个调度器,它将等待服务的请求排队并确定它们何时执行。每个客户端都不知道其他客户端,并且(除了由于调度而导致的延迟之外)有一种令人愉快的错觉,认为服务器正在专门处理它。 图1.4的aj会话使用嵌入式连接。它在运行会话的机器上创建了数据库“ijTest”,并且不涉及服务器。要执行类似的基于服务器的aj会话,必须更改两件事:Derby引擎必须作为服务器运行,并且必须修改connect命令以识别服务器。 Derby服务器的代码位于Java类NETeringControl中,位于org. apache. derby. drda包中。要从日食运行服务器,请转到“运行”菜单中的“运行配置”。向您的Derby项目添加一个新配置,并将其称为“Derby服务器”。在主类的字段中,输入“org. apache。derby. drda.Networks服务器控制。”在“参数”选项卡中,输入程序参数“start -h localHost”。每次运行配置时,都应该出现一个控制台窗口,指示Derby服务器正在运行。
程序参数start -h localHost的目的是什么?第一个单词是命令「start」,它告诉类启动服务器。您可以通过执行带有参数“should”的同一类来停止服务器(或者您可以简单地从控制台窗口终止该进程)。字符串“-h LocalHost”告诉服务器仅接受来自同一台机器上客户端的请求。如果您将“本地主机”替换为域名或IP地址,则服务器将仅接受来自该计算机的请求。使用IP地址“0.0.0.0”告诉服务器接受来自任何地方的请求。1 基于服务器的连接的连接字符串必须指定网络或IP
ij> connect 'jdbc:derby:ijtest'
ij> connect 'jdbc:derby://localhost/ijtest'
ij> connect 'jdbc:derby://cs.bc.edu/ijtest'第一个命令建立到“ijTest”数据库的嵌入式连接。第二个命令使用在机器“本地主机”(即本地机器)上运行的服务器建立到“ijTest”的基于服务器的连接。第三个命令使用机器“cs.bc.edu”上运行的服务器建立到“ijTest”的基于服务器的连接。
请注意连接字符串如何完全封装使用嵌入式或服务器端连接的决定。例如,再次考虑图1.4。只需更改连接命令即可修改会话以使用服务器端连接而不是嵌入式连接。会话中的其他命令不受影响。
1.4 SimpleDB 数据库系统
Derby 是一个复杂、功能齐全的数据库系统。然而,这种复杂性意味着其源代码不容易理解或修改。我编写的 SimpleDB 数据库系统与 Derby 相反--它的代码很小、易于阅读、易于修改。它省略了所有不必要的功能,仅实现SQL的一小部分,并且仅使用最简单(而且通常非常不切实际)的算法。它的目的是让您清楚地了解数据库引擎的每个组件以及这些组件如何交互。
1.5 SimpleDB 版本的 SQL
Derby 实现了几乎所有的标准 SQL。另一方面,SimpleDB 只实现了标准 SQL 的一小部分子集,并施加了 SQL 标准中没有的限制。本节简要说明这些限制。本书的其他章节更详细地解释了它们,并且许多章末练习将要求您实现一些被省略的特性。
SimpleDB中的查询仅由select-from-where子句组成,其中select子句包含字段名列表(不含AS关键字),而from子句包含表名列表(不含范围变量)。
可选where子句中的项只能通过布尔运算符和连接。术语只能比较常量和字段名是否相等。与标准SQL不同,没有其他比较操作符、布尔操作符、算术操作符或内置函数,也没有括号。因此,不支持嵌套查询、聚合和计算值。
因为没有范围变量,也没有重命名,所以查询中的所有字段名都必须是不相交的。因为没有group by或order by子句,所以不支持分组和排序。其他限制包括:
- 不支持select子句中的缩写“*”。
- 没有 NULL 值
- 在 From 子句中没有显式连接或外部连接。
- 不支持 union 关键字
- 插入语句只接受显式值。也就是说,插入不能由查询指定。
- 一个update语句在set子句中只能有一个赋值。
1.6章节小结
1.7 阅读建议
多年来,数据库系统发生了巨大的变化。这些变化的很好描述可以在第6章中可以在找到国家研究委员会(1999年)和黑格(2006年)。 en.wikipedia.org/wiki/Data base_Management_system#History上的维基百科条目也很有趣。
客户端-服务器(client-server) 范式在许多计算领域都很有用,而不仅仅是数据库。Orfali等人(1999)对该领域进行了概述。有关Derby服务器的各种功能和配置选项的文档可在URL db.apache.org/derby/manuals/index.html上找到。
黑格,T.(2006)。"大量事实"。数据库管理系统的起源。ACN SIGMOD Record,35(2),33-49。
国家研究委员会计算和通信创新委员会。(1999)。资助一场革命。国家学院出版社。可从www.nap.edu/read/6323/chapter/8#159获取,
Orfali, R., Harkey, D., & Edwards, J. (1999).(1999)。客户机/服务器生存指南(第3版)。
