UCB CS186 数据库导论笔记(一)

P1:第一讲 介绍 + SQL I - main - BV1cL411t7Fz

好的,各位,这就是《数据库系统导论》第一讲,CS 186。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_1.png

不幸的是,我们的 Zoom 录音崩溃了,所以这是另一种尝试录制第一讲。我是 Aditya Parmeshran,我和 Alvin 一起共同教授这门课。那么,考虑到你可能想在数据库中做的事情,让我们来谈谈你可能想要为这门课解答的查询。所以我们将讨论为什么我要上这门课。

这门课的内容是什么?谁在主讲?这门课将如何进行?为什么你应该考虑参加这门课?从最高层次来看,这门课将涵盖如何开发系统,基本上是管理、维护、处理、与数据交互、进行交易并理解数据。

所以,如何在非常大的数据集上以高效的方式完成所有这些任务。这一切都与系统开发以及在管理、维护和与数据进行交易的背景下的系统开发原理相关。那么,为什么这是有用的呢?嗯,第一个理由是它无处不在。

所以当你进行预定、预定酒店、机票或 Airbnb,或者在社交媒体上点赞,或者在寻找餐厅,或者在 Piazza 或 Slack 上发布求助信息,或者进行金融交易、购物时,实际上你可能正在使用这样的系统。事实上。

如果你考虑建立一个创业公司或加入一家大公司,很可能你正在构建一个将由这种系统支持的应用程序。这些系统也是现代科学的支柱,对吧?所以如果你想想基因组学、天文学、神经科学、医学、气象学,这些都有相关的。

他们生成或收集的大规模数据集合。需要对这些数据进行理解,对吧?这些系统,即我们将要学习的系统,将成为解决未来一些最紧迫社会性重大挑战的关键。例如,气候变化和公共卫生问题。

除了通用应用程序和科学之外,我们将在这门课中讨论的原则将在任何有大量数据的环境中发挥作用,这几乎是未来大多数环境的特点。所以,除了简单的实用性,第二个理由是数据的核心性。数据是现代社会的核心,而这一点蕴藏着改变人类的巨大潜力。

但也有很多潜在的关注点,对吧?所以这是关于数据被不当使用的情况。而且围绕隐私问题、安全性、伦理、公平性和偏见等问题,最近有许多有时效性的辩论。这些问题非常非常重要。在某种意义上,我们要学习的内容,即数据基础设施,正是与这些问题紧密相关的。

它将决定你能做什么或者在处理数据时什么是可行的。因此,从某种意义上说,我们是数据的守门人,对吧?随着数据变得至关重要,管理数据的基础设施也将变得同样重要。而且,意识到数据在现代社会中的核心地位,当然,很多人都知道。

关于这个新的数据科学专业,其中一个重点是人类背景和伦理,对吧?在我看来,这个框架太小了,应该更大一些。但实际上,你如何思考数据、如何使用数据和你从中获得的结果一样重要。所以第三个原因是数据正在成为计算的核心。

数据增长将在未来继续超越计算,而未来计算的关键瓶颈将在数据处理上。因此,面向大规模数据的系统将是现代计算的核心。相信你们已经见过很多类似的幻灯片,讨论了这个话题。

关于数据生成量的例子,虽然很有帮助,但我们来看另一个例子。所以在这里,每分钟你可以看到,大约有50万条推文被发送,大约2000万次天气频道的天气预报请求,以及大约400万次谷歌搜索请求,对吧?这是一种惊人的数据生成量。

这些数据的生成速度非常快,并且持续快速增长。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_3.png

气候数据是另一个大难题,对吧?它是我们在商业领域完全没有经验的另一个尺度。例如,大型强子对撞机每年生成19泽字节的数据,相当于100万拍字节。所以这是一个疯狂的数据量,而这些数据庞大到需要进行降采样,降到大约每年1000拍字节,很多时候甚至进一步降采样。

每年50拍字节,对吧?理想情况下,你希望能够存储和分析的是由其设立的,而不是拍字节。但这就是当前系统能够舒适处理的数据量。事实上,你希望构建能够逐渐扩展这个限制的系统。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_5.png

除了数据将成为计算核心的事实外,你将在课堂上学到的技术也构成了计算中许多不同主题的基础,诸如抽象的概念、数据建模和表示的概念、复用、缓存和物化、数据的快速访问。

我们在本课程中学习的第四个原因是这里有许多机会和学术研究。在数据管理领域,已经获得了四个图灵奖,其中包括查尔斯·巴克曼、泰德·科德(提出关系模型的概念,我们将在课程中深入探讨)、吉姆·格雷(提出事务处理概念,正是我们要讲的内容),以及迈克尔·斯通布雷克,他开发了Ingress和Postgres,最成功的数据库系统之一。开发可扩展的数据系统是并将继续是计算机科学研究中最激动人心的领域之一。所以这就是你应该选修这门课的原因。

我们将花费大量时间讨论的内容,迈克尔·斯通布雷克提出了Ingress和Postgres,它们是最成功的数据库系统之一。而且,开发可扩展的数据系统仍然是计算机科学研究中最激动人心的领域之一。所以这就是你应该选这门课的原因。

数据系统引起了极大的关注。那么这门课到底是讲什么的呢?

那么让我们来谈谈数据库是什么。假设你正在从零开始构建一个银行数据管理系统,没有使用数据库。假设你的目标是管理客户、账户、联名账户、转账、交易和利率。也就是所有与银行相关的信息。假设我使用你最喜欢的编程语言,如C++或Java来实现这个系统。

如果不使用数据库系统,而是使用Python或其他工具。假设你是一个软件开发人员或设计师。你认为我们可能需要担心哪些方面?

所以在课堂上我进行了一次讨论,讨论了你可能需要担心的方面。现在我给你几秒钟的时间思考一下。然后我将展示学生们给出的答案以及我在幻灯片上写的内容。好的,下面是你可能需要担心的事项。

所以你肯定需要能够处理大量数据。因为会有很多交易。你需要快速处理。所以你不希望因为有数百万个其他客户而使你查询银行账户信息时耗费数分钟。你希望信息在系统故障(如电力故障等)发生时不会丢失。你希望能够支持多个用户同时访问他们的信息。

同时进行更新。你希望信息保持一致,不会随着时间推移而退化。你希望系统易于使用。你希望能够支持方便的方式来访问数据。好了,让我们来谈谈数据库系统的方法。数据库系统的方法基本上是将所有这些数据管理功能集成到一个系统中。

我们在上一张幻灯片中谈到的内容并将其抽象为一个独立的层次,以便许多应用程序能够访问它。所以这可能是一个ATM应用程序,一个网页应用程序,或者在这个银行场景中的一个手机应用程序。DBMS代表数据库管理系统。我们稍后会讨论这个缩写。这个抽象层次就是指这样的一种方式。

这个独立的层次在许多场景中都会出现。因此,将其抽象出来并保持独立是有意义的。这里有一个可能较为笨拙的数据库系统定义:数据库系统是一个提供高效、便捷和安全的多用户系统。

存储对海量持久数据的访问。接下来我们将通过几张幻灯片来拆解和解释这个问题。好的,接下来我们从下往上谈起。首先是数据,对吧?你有大量的数据。你有信息账户、客户、余额等等。数据量非常庞大,对于大银行来说,最少有几千 TB。

如果你保存所有交易的历史记录,那就更多了。如果你还保存所有支票的图像(例如JPEG格式),那就更复杂了。

数据量可以达到PB级别,对吧?这肯定是巨大的。第二个形容词是持久性,对吧?你希望你的数据能够永久保存,而不仅仅是依赖于操作它的程序。因此,即使系统关闭,某个服务器宕机,也不意味着数据会消失。所以,你当然不能把数据存储在内存中,因为内存是临时的。

你需要将数据存储在稳定的存储介质中,比如硬盘或闪存。下一个形容词是多用户。你有多个用户想要访问同一个数据库,甚至是相同的数据。例如,多个用户需要同时访问同一个银行账户信息,你需要能够协调他们的访问,确保其正确性。比如……

假设你有Alice和Bob,他们的联合银行账户里有200美元。Alice在办公室里订购了一本《自私的团队》。而在差不多同一时间,Bob在家决定订购一本《枪跳与钢铁》。其中一本《自私的团队》定价80美元,而《枪跳与钢铁》定价100美元。

所以这两笔独立的交易是允许的,因为他们的银行账户里有200美元。但如果第二笔订单,《枪跳与钢铁》的订单金额是130美元,这笔交易就应该被阻止,因为他们的银行账户中只有200美元。因此,数据库系统需要协调这些访问并确保它能做到这一点。

需要遵守某些约束。例如,这里账户中只有200美元,你不能超支。因此你需要遵守这些约束。你希望系统是安全的,这与之前的观点也有关。你希望系统在发生故障时仍能保持安全。例如,由于停电,账户中的钱不应消失或突然增加。

比如说,假设Bob现在去自动取款机(ATM)并试图从他的账户中取款。那么ATM有内部软件,其伪代码大致如下:从数据库中获取余额,如果余额大于50,则将余额减去50,向Bob发放现金,然后更新数据库中的余额。如果例如……

假设在现金发放后但余额更新之前发生了断电,那么Bob已经收到了现金,但该笔款项的扣除未在账户中反映。如果断电只是因为Bob拔掉了ATM机的插头,那么Bob可以通过在现金发放后拔掉ATM机来实现这一目的,从而利用这一点。

生成任意数量的资金从银行账户中提取。这并不是理想的做法。因此,从某种意义上讲,你希望这个程序要么整体执行,要么完全不执行。所以如果钱已经发放给Bob,那么显然这需要在账户中得到反映。你还希望系统能防止恶意用户的侵害。

你希望系统能够保护你的数据不被不应有权限的人篡改。系统应该方便使用,你应该有简单的命令来进行账户扣款、查询余额等操作。因此,这一切都应该以相当直观的方式发生。

你不应该写大量的代码来实现这一点。实际上,即使是无法预见的查询,也应该很容易处理。另一个目标,最后一个目标是它应该高效。这是显而易见的,你不应该需要搜索所有的文件或记录。

你所有账户的信息,包括查询某一账户的余额,应该不会因为账户的总数而增加访问的复杂度。所以即使有一百万个账户而不是一百万个账户,查询你的银行余额也不应该花费十亿单位的时间。

所以你需要能够高效地快速检索这些信息。同样地,如果你要执行某些其他类型的请求,例如,你想查找所有余额较低的账户,或者你想制作一个汇总电子表格,列出所有的大额交易,你应该能够高效地完成这些操作。

你应该能够以尽可能交互的方式来完成这一切。那么为什么直接使用我们之前提到的那种原生编程语言来实现会有困难呢?为什么那种方式行不通?

结果发现,早期的数据库系统是从文件系统演变而来的。文件系统当然提供了存储大量数据的功能,并且数据是持久化的。所以它通常存储在磁盘上。然而,这种方式有许多不符合要求的地方。例如,如果系统在操作过程中崩溃,可能会导致数据操作中断。

在Bob尝试提取资金的情况下,程序的行为是无法保证的。在那个实例中,尽管Bob不应该获得资金,但他还是拿到了。在其他情况下,可能会导致数据丢失。这并不理想,也不是高效的。它本身并不支持快速访问你关心的数据,尤其是数据的位置。

文件是未知的。因此,你不需要编写自定义代码或维护自定义数据结构来支持这种快速的数据访问。所有这些都可以在数据库系统中原生实现。所以这就是为什么数据库系统被发明出来的原因。数据库系统描述了现实世界中的实体,比如我们讨论过的银行实体。它们存储非常大的数据集,这些数据是持久管理的,超越程序的生命周期。

它支持高效的查询和更新。它支持结构的高效更改。例如,如果你想添加额外的方面或属性,你可以这样做。它处理并发更新或同时发生的更新。你可以以无缝且正确的方式处理这些情况。它还处理崩溃。

所以,如果数据库系统崩溃,它可以从中恢复。它还遵循安全性和完整性属性。它防止不安全的访问,并保持数据的一致性。所以我们所称之为数据库系统的其实是它的完整名称,即数据库管理系统(DBMS)。这就是数据库系统的全名。

DBMS是一种存储、管理和促进数据访问的软件。而数据库则是一个大型的、组织良好的数据集合。这就是数据库系统或DBMS管理的内容。但有时数据库也用来指代软件,即数据库系统。

或者数据库管理系统(DBMS)本身,使用时应根据上下文明确。数据库或DBMS的符号是一个圆柱体。这里是三种不同版本的圆柱体。为什么是圆柱体?嗯,数据库系统最重要的一个方面就是数据是。

持久地存储在磁盘上的。圆柱体看起来像磁盘槽上的插片。因此,强调数据的持久存储是数据库通常用圆柱体表示的原因。让我们来谈谈数据库系统的实现。这里是一些示例数据库系统。传统上,数据库系统被称为传统的数据库系统。

本地关系数据库系统或RDBMS,简称关系数据库。这里是一些关系数据库的例子。Oracle、Teradata、SQL Server是商业产品。Postgres和SQLite是开源的。还有一些非关系数据库系统,如图数据库、文档存储、键值存储等等,所有这些都不是关系数据库。

它们不遵循关系行为。我们将在本课程中讨论“关系”是什么意思。这里是一个数据库系统演变的图表。这个家谱的大部分根源都在伯克利。所以像Ingress和Postgres这样的系统,正如我提到的,Mike Stonebaker因Ingress和Postgres获得了图灵奖,这一切发生在伯克利,正是Mike Stonebaker。

曾是这里的教职工。Sidebase和Informat是伯克利开发的项目的另一些衍生物。大约在同一时期,伯克利是关系数据库系统的先驱,Oracle最初是一个小型创业公司,最终发展成了一个庞大的企业,它是另一类关系数据库产品。

IBM曾是UC Berkeley的强大竞争者,至少在研究领域,并最终也推出了他们自己的数据库产品。所以我们将主要关注关系数据库系统。你可能会有一个问题,那就是这些东西是不是过时了,当然至少你可能会这样想。

可能会让人误以为这些内容过时了,因为我们的主要教材相对陈旧。但这门课的重点将放在超越不同类型数据库系统的基础系统原则上,不论是否是关系型数据库。而这些系统原则从某种意义上讲是超越时代的,具有永恒性。

所以这些基本上是经得起时间考验的内容。它们今天依然有价值。如果你在未来构建类似的系统,这些内容是你需要记住的。所以,如果你考虑构建可扩展的数据库系统,我们将讨论你在此过程中需要思考的原则。

我们将讨论的原则涉及可重用的思想和组件,以及通过组合这些思想和组件来连接它们的方法。所以最终的目标不仅仅是能够使用现有的数据库系统技术,而是能够构建新的数据库系统,希望能为你提供实现这些目标的工具和技术。

那么,你在这门课中会学到什么呢?你会学到更多数据导向的编程,使用SQL,这是一种数据编程语言,是一种根本不同的编程思维方式。你将了解数据系统设计,包括存储和索引,这些是基础内容。

数据系统设计的下层部分。接着我们会讨论查询交叉和优化,这属于稍微高一级的内容,主要是讲解如何使数据导向的查询在数据上高效运行。接着我们将谈论事务以及并发、一致性和恢复的概念,以及如何对数据进行建模和推理。所以,再次强调。

如果这些术语对你来说并不十分熟悉,不用担心。这正是这门课的目的。我们将更详细地讨论所有这些术语。所以,在我们的讨论中,会出现一些更高层次的原则,包括以下内容:数据独立性,声明性思维的概念。

它包括连接的概念,基本上是时间和空间中的会合。它还包括程序隔离和程序间一致性的概念。同时也包括数据表示的概念。你如何思考你的数据?

如何建模以及其他相关内容。这些原则是永恒的,它们将塑造你的大脑,使你以不同的方式思考可扩展的数据系统。我们将讨论数据库系统的各个层面,从磁盘空间管理到缓冲区管理、索引和文件管理,再到关系操作或算子。

作为查询解析和优化的一部分。我们有并发控制和恢复机制。这些仅仅是数据库系统的不同层次,我们将讨论这些层次如何相互作用。因此,从某种意义上说,我们将讨论的内容是原理。所以,我们将覆盖数据库的原理和算法。

管理系统,支持可扩展的数据管理系统。这些系统设计是将各个组件组合在一起,形成一个可扩展的端到端数据管理解决方案。算法和原理与系统的紧密结合实际上非常重要。

是什么让计算机科学成为一门科学?这是理论与实践结合的一个极好例子。那么现在让我们进入这门课的具体操作,看看它将如何进行。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_7.png

那么,我们是谁呢?Alvin,大家希望能在未来的讲座中看到他,是伯克利大学计算机科学的教授。他在麻省理工学院获得了博士学位。他是数据库和编程语言方面的专家。我是计算机科学系和信息学院的助理教授。

我从事数据库和可用性方面的工作。我在斯坦福大学获得了博士学位。所以我们两个人都在一年前开始在伯克利工作。所以这是我们第一次教授这门课程。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_9.png

所以请保持宽容。这是你们的优秀首席助教名单。更多关于他们的信息可以在课程网站上找到,我几分钟后会提供链接。所以这四位首席助教是Itin、Jerry 和 Chris。然后你们还有一群优秀的助教,分别是 Justin、Sami、Gabe 和 Amy,Kaylee、Suman 和 Shreya。

Sam Noah,Caitlin,Montej,Aditya,Anadha Aditya 和 Alan 和 Dylan,Jennifer 和 Sabrina。这是你们的优秀助教团队,他们会帮助你们确保这个学期非常成功。除了助教和讲师之外,这门课的另一个重要组成部分就是你们自己。一切都可以通过稳定的努力来完成,只要你们愿意投入时间,按稳定的节奏来学习材料。

这是你们手中的课程,这是一个特殊的学期。所以我们希望你能充分利用 Piazza 这个资源,与其他学生进行联系。我们的目标是尽量不给你们施加压力。我们的目标是帮助你们真正掌握课程内容。如果有任何方法可以让压力消失,请告诉我们。

所以请在方便的时候告知我们,如何才能让这对你来说更容易。我们这边会尽力帮助你调整进度。我们会提供每周的课后练习和补充资料,帮助你保持进度。我们还将设置小组和辅导员,以及编程项目,帮助你在实际环境中运用这些原则和技能。那么,这门课将如何进行呢?

首先,这对你来说应该不是什么惊讶的事情,一切都转到线上了。所有讲座和辅导都会在线进行。对于学生来说,不再受时区限制。所以请告知我们你的时区,以便我们确定你的位置。如果有需要调整辅导安排或讲座内容的地方,我们会尽量协调。

如果能对此更宽容一些,那我们就可以尝试。 当然我们资源有限,人员有限,但我们会尽最大努力,尽管你身处地球的任何角落,仍会尽力让你能够访问到。我们也会随着疫情的发展,实时调整我们的策略。

请随时在Piazza上提出问题。我们会尽力帮你解决。那今年的CHAS 186有什么不同呢?另一个变化是,过去几个学期,CHAS 186完全是MOOC风格的课程,视频录制于2018年,由Joe Hellestine提供。

我们之所以选择同步授课,是因为之前的视频并非同步授课,因为同步视频出现了问题,但我们正在尽力同步授课。我们会讲授一些相似但不完全相同的概念。所以请优先参考我们的视频,而不是Joe Hellestine的视频。

但Joe Hellestine的教学视频非常棒,依然可以观看,如果你想从不同的角度了解内容的话。由于这是我第一次教这门课,还是第一次带这么大的班,750人真的是个非常庞大的规模。请耐心等候,我们正在努力调整。肯定会遇到一些小问题,请多包涵。

请告诉我们你喜欢和不喜欢的部分,我们会尽力改进。还有一个需要记住的重要点是课程网站。现在就把它收藏起来,这是你获取课程所有信息的地方。你可以在这里找到教学大纲、日历等资料。

讲座幻灯片和作业可以通过课程网站链接访问,另外还有PRS讨论小组,所有内容都在网站上。至于课程负担,我们每周有两次同步讲座,讲座也会录制。如果你不能参加,那也没关系,你可以离线观看。但如果能参加,还是请尽量参加。

这在某种意义上是保持大家责任感的方式,课堂上会有很多互动。如果不能与我们实时互动,也可以通过聊天进行互动。所以课堂上有很多内容发生,请尽量参加。如果你参加课堂,我们强烈建议你开启视频,以便…

让大家的体验不那么枯燥,同时也能保持大家的责任感。如果你这么做,请不要做一些你在普通课堂上不会做的事。所以不要接电话,不要做饭,不要洗澡。请不要做任何奇怪的事,就像你在普通课堂上一样。还有,如果你不在说话时,请保持音频开启,这样我们就能减少…

防止交流干扰。如果你需要提问,一个很好的方法是举手,我们会关注那些举手的同学并回答你,因为Alvin和我会共同出席每一堂课。所以我们会有一位负责查看聊天和举手功能。

请和我们互动。我们喜欢问题,我们喜欢答案。我们讨厌像这样只录制静态视频。我们喜欢同步授课,所以请和我们互动,向我们提问。这就是我们以同步方式授课的原因。那么,工作量是什么样的?

除了讲座,还有办公时间,从这周开始,但下周第一天之前会先开始。每周会有一些简单的在线小测验,你可以放弃其中两个小测验,但你需要完成这些练习才能提交。此外,还有编程项目,我会在下一张幻灯片中详细介绍。

两个心理考试加上一场期末考试,我们仍在确定考试的格式。是否能通过Zoom进行监考等问题还在讨论中。一旦确定下来,我们会在Piazza上发布通知。还有一个要提到的是,我们的考试时间已经调整到了不同的小组,希望能够减少时间冲突。编程项目和作业的暂定时间表、截止日期等将会公布。

这些信息可以在课程网站上找到。关于编程项目,这些是你将课堂上学到的概念付诸实践的方式。我们将有一个相对简单的SQL查询项目,之后是更多的后续项目。

将会更具挑战性。所以首先是关于索引的内容,然后是连接操作、查询优化和事务处理。因此,第一个项目将在下周发布。请关注并尽快开始。同时,第一次我们也在考虑做一些稍微复杂的后期项目,可能会采取两人小组形式。更多细节会尽快发布。

为了帮助你处理这份工作量,你最多有五天的缓冲时间。所以比起上学期的三天有所增加,目的是让你更轻松一些。这可以用于项目,并且也是按天数计算的。这是因为一些特殊的情况和范围的问题,导致我们无法以其他方式进行计算。

计数将在最终粒度层级进行。所以它将按照天数来计算。这个缓冲时间是一个安全网,而不是一个便利。你理想情况下不应该计划使用它们。你应该尽量跟上课程进度。如果你最终用了所有五天,理想情况下,你是做错了。所以理想情况下,你不应该需要使用它们。如果你想使用它们。

当然可以随时提问。但我建议你在学期末的时候使用这些天数,当你真的需要它们时,而不是在学期开始时就把所有天数用完。关于学术诚信的问题也很重要。这是我们有零容忍政策的领域。

所以我们相信你会独立完成作业。不要作弊。我们会发现的。我们有相关技术手段来检测。我们发现大部分作弊现象都是由于压力引起的。所以,解决这个问题的一个简单方法就是提前规划,保持进度,以减少压力。而你也有一些内置的缓解压力的机制。

你可以选择退课。你有五天的缓冲时间,所以留到真正需要的时候使用。我们很可能会把期中考试的比重加大。我们会随后确定具体的政策,但这是计划。请留意课程退课的截止日期。如果你修的课程太多,

随时可以加入这门课,这完全没问题。确保自己不要在已经很紧张的学期中被过度工作压垮。除此之外,如果你感到有压力,请及时联系。课程工作人员会为你提供帮助。我们乐意提供我们能做的任何帮助。当然,校园也有相关资源。如果在任何时候你觉得自己完全不堪重负,课程也无法继续进行下去,你。

完全可以选择不及格,并在未来的学期重新修这门课。所以,任何健康问题都完全可以适用这种方式。关于学术诚信的看法是,我们确实希望帮助你学习并取得成功。我们不希望你作弊。我们希望一切公平,因此我们需要遵守规则。

但如果有任何方法可以缓解一些压力,请告诉我们。最重要的是,请不要作弊。那么你该如何与我们沟通呢?

所有课程沟通都通过Piazza进行。Piazza,Piazza。Piazza。所以,Piazza。Piazza的链接也在课程网站上。我们已经开通了,已经有一些问题在解答中。讨论也非常活跃。所有的公告和讨论都将在Piazza上进行。

所以请仔细阅读,然后再提出问题。但是,如果你要提问,确保之前没有人问过这个问题。并且互相回答问题。学习的最佳方式之一就是回答别人提出的问题,这样你就能在思考自己问题的同时,澄清自己脑海中的概念。

给教授或助教发送事件邮件通常不是一个好主意。我们都被邮件压得喘不过气,很可能会被埋没。所以,如果你希望你的问题能引起注意,在Piazza上私下给讲师发帖要比发邮件好得多,这样你就能保证有人会回应你。

如果你发送邮件,除非问题非常敏感,否则可能不会得到回答。好的,以上就是我的部分内容,再次欢迎加入这门课,希望你度过一个愉快的学期,享受课程内容,希望下次我们进行同步视频时,视频能够保存下来,并且你可以和我们进行实时互动。

我期待在下节课亲自见到你们。大家好,欢迎来到186。在TI9,期待在这个学期通过Zoom与大家在线见面。那么今天我们要讲解的这门课的第一个技术内容,就是我们将在整个学期中使用的关系模型,以及我们将用来操作它的语言。

被称为SQL的关系。所以在此之前,我们先回顾一下DTS在介绍部分提到的内容。我们意识到这是一个非常特殊的学期,的确是一个非常特殊的时期。也正因为如此,我们无法在课堂上亲自见到你们。如果你是在线加入我们的,请随时打开视频,如果你觉得舒服的话。

当然,这完全取决于你,我们并不要求你感到有压力。如果你不觉得舒服,也没关系。正如Aditya所说,如果你有问题,可以尝试解除静音或举手,或者你也可以随时在聊天窗口输入,我们会在整个讲座过程中监控这些问题并尽量回答。当然,我们也鼓励你在Piazza上发帖。

如果你觉得私下交流更为舒适,随时可以给我们发邮件,或者在Piazza上给任何一位工作人员发送私信,我们会尽快联系你。好的。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_11.png

所以我们先来简单谈谈关系。正如我之前所说,我们希望将其作为这门课的第一个技术主题。那么,什么是关系,关系又是如何与数据库相关的呢?好吧,正如Aditya所说,数据库本质上是这些命名的关系的集合。

所以你在这里看到的是我们尝试的一些关系,用这种有趣的图表表示。这里的每个圆圈基本上表示一个关系。然后这里我特别标出的是显示实际数据的那个关系。那么到底是什么定义了一个关系呢?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_13.png

好的,正如Aditya所说,然后再重复一下,数据库是一个由命名关系组成的集合。每一个这样的关系,也称为表,包含多个不同的元素。首先,有一个模式,也就是通常所说的元数据。这个基本上描述了表格或关系的内容。

我们很快会看到我所说的模式是什么样的。然后还有一个概念叫做实例,它基本上是存储在这个关系中的一组数据。比如这里就是我在前一张幻灯片中用到的例子。这是我们的第一个关系,你可以看到这里有三列。

这基本上构成了模式。所以你可以看到这里的模式包含了三个列。每一列都有名字,并且它们都有类型,对吧?比如在这个例子中,姓氏这一列的类型是文本类型。这是一个关系的例子。所有这些数据共同存储在这里,被称为该关系的实例。

与这个特定关系相关的实例。所以更多的术语。我们在这节课上讨论的每一个关系都会有一个属性,也称为列,或者有时我们会称之为字段。所以从之前的例子中摘取一部分,这就是由第一个字段组成的列。

名字列的类型是文本。所以你可以看到这里有三个名字。就从垂直层面来看,这是表中的一个属性或列。我们还有一个叫做元组的概念,它被称为记录,或者更通俗地叫做行。比如这是我之前提到的关系中的一行。

所以这些行中的每一行也被称为一个元组。最后,还有一个叫做基数的概念,基本上就是某个特定关系中元组的数量。所以为了总结我刚才跟你们提到的例子。在这个例子中,我们有一个由三个不同列或属性组成的模式。

对于数据实例来说,我们也有三行或三元组,你可以看到这些数据。然后在这种情况下,由于表中有三行,所以这个关系的基数是三。那么你可能会问,为什么我们要使用这些比较深奥的名字来描述属性和元组等等,为什么不直接称之为行和列呢?

所以它们实际上出现在我们最初开始使用电子表格时,关系被定义时,实际上是作为一个数学概念来定义的。所以它们使用这些术语,现在这些术语已经有些过时了。但本质上,它们只是在谈论你我都很熟悉的行和列。

如果你以前使用过电子表格,好的,到目前为止希望一切顺利。那么这些是你需要了解的与关系相关的术语技术。还有一些关系的属性我们也应该了解。例如,第一个是模式本身是固定的。这是什么意思呢?嗯。

它基本上意味着,像我之前谈到的,模式由一组属性名称组成,对吧?然后它们也有类型。所以在这种情况下,我们强调所有这些类型必须是原始的或原子的。所以你可能在像 61A 或 B 的课程中已经接触过这个概念。

这里指的是像 Python 或 Java 这样的语言。所以它们有一些被称为原始类型的东西。你可以把这些看作基本上是非对象的东西。它们也像非结构体,对吧?如果你是 C 语言程序员的话。所以需要注意的一点是,每种语言定义了它所说的原始类型的含义。

SQL 也有自己的定义。事实上,每个数据库管理系统的实现也有自己对原始类型和非原始类型的定义。所以当你开始使用数据库时,一定要检查这一点。例如。

在 Postgres 和 MySQL 中,日期实际上也是一种原始类型。所以你可能不太习惯这一点,对吧?如果你来自 Java 世界的话。

因为例如,在 Java 中,日期实际上是类中的一个对象。但在 SQL 中,或者至少在 MySQL 和 Postgres 的实现中,它实际上被认为是一种原始类型。所以确保你意识到这一点。还有什么?

还有一点是,关系或标签实际上是没有顺序的。所以基本上这意味着行的顺序或两个目标的顺序实际上并不重要。我可以按任何顺序对它们进行排序,或者根本不排序,这在构成一个良好形式的关系时都是完全可以的。

同时,行本身不必是唯一的,它们也可以是两种不同类型的关系,其中一种关系要求每一行只能出现一次。所以这也叫做集合,对吧?正如你从方法论概念中知道的那样。还有一种关系则允许同一行出现多次。

所以如果是这样的话,它们被称为反向集合(backs)或冗余集合(mouthy sets)。这就是关系的另一个特性。表格或关系本身实际上是平面的。我的意思是平面是什么意思?基本上意味着我们不能有任何属性是彼此嵌套的。我们将在下一张幻灯片中看到一个例子来说明我的意思。最后,

表格本身实际上并没有规定行或实例,或者数据应该如何在磁盘上存储。这实际上是一个非常有趣的特性,叫做物理数据独立性。那么我的意思是什么?这是关系的一个例子。所以我们可以看到这里有四个属性,然后我们也有四行,或者说两行。

现在假设我想将这个表格相关的实例或数据存储到磁盘上。假设作为一个文本文件。那么,我到底该如何存储呢?

存储这些数据的一种可能方式实际上是将其存储为对象数组。因此,我可以采用行主序列。我遍历屏幕上看到的每一个记录。然后我基本上按照不同属性的顺序将它们写出来。例如,你可以看到我在这里写的是gizmo works,然后基本上就是这样。

假设我们将与gizmo相关的整个两个记录存储在文本文件的第一行,然后再继续存储表格中的下一行,换句话说。但这并不是存储关系到磁盘文件的唯一方式,对吧?

例如,如果我们可以采用行主序列(row major),我们也可以采用列主序列(column major)。所以实际上,它们是一样的,对吧?只是我们先沿着列的每个属性走,然后再进入下一列。为什么这很重要?我们将在稍后的课程中讨论磁盘时讲到这个问题。但这是被称为物理数据独立性的特性,正如我之前提到的那样。

这基本上是这种逻辑定义的方面,我们向堆栈上层暴露对吧?比如说,某些应用程序使用关系,它们实际上不需要知道数据是如何在磁盘上存储的。我们可以使用行主序列、列主序列、拼接模式,或者你想要的任何方式。

只要我们向应用程序暴露这个关系接口,那么就没问题了。所以这实际上是一个非常有趣的特性,我们将在课程的后面再次讨论。好,现在,为了总结一下关于关系的讨论,意思是现在你知道了构成有效关系所需了解的所有术语。

但我还想强调的是,关系实际上并不是存储数据的唯一方式。例如,假设我们想存储你所有的Facebook朋友。这里有一种做法。你可以将你的朋友们存储为图(graph),对吧?

例如,这里我们有人的名字,每一行都有一条连接两个节点的边。如果这两个人是朋友的话,我的意思是,这是一种有效的存储方式。还有另一种方式。那么你也可以将它作为表格存储。所以现在你可以看到我们有一个包含三种属性的表格,并且我们列出了人们的名字。

前两个,然后最后一个基本上是一个二元变量,表示这两个人是否是朋友。哪个更好?两者取决于应用场景。事实上,像你们知道的,学期后期,我们实际上会学习使用这些不同模型的权衡,然后它们的优点和缺点是什么。好的。

所以让我们快速检查一下,确保你们理解这些概念。那么这里是。我称之为关系,对吧?但结果发现它不是,为什么呢?看这里,对吧?现在我们有这个额外的东西挂在右边。那是什么,对吧?没有对应的属性,对吧?我的意思是,根本没有。

没有任何属性真正描述了这个数字是什么?这是邮政编码吗?我的意思是,这是电话号码吗?它是什么,对吧?还有注意到像你知道的,在这里。这实际上是一段文本,即使关系的属性类型在这里被声明为整数。所以这不匹配,对吧?

因为在关系中,所有的行必须符合相同的模式。在这种情况下,模式作为一行,它应该是一行包含三种不同类型的属性:整数、文本和另一个整数。所以因此,这不是一个关系。另一个例子,为什么这不是一个关系?好吧,看看。我们有两个属性,它们的名称完全相同。

所以这也是不允许的。因此,它们关系中的每个属性必须是唯一的。想象一下,如果你想定义标题,对吧,然后再删除它。那么你可能不想定义多个列,但它们的名称是一样的,对吧?

那只是让人困惑。好的,那么最后一个,为什么这不是一个关系?好吧,看看这里的第三个属性,对吧?你注意到它实际上写着地址。所以地址在这里实际上不是一个原始类型,因为它实际上,你可以看到,这是一个程序员类型,基本上被表示为结构和对象。

所以因此,这不是一个原始类型。而且,由于关系模式必须只包含原始类型,因此,这就不是一个有效的关系,很遗憾。很酷。那么现在我们来谈谈这个叫做事实(fact)的属性,对吧?就像我之前告诉你们的那样,我们在这门课上讨论的所有关系都必须是事实。

另一种说法是,它们必须符合所谓的第一范式。首先,它基本上是说所有关系必须是扁平的同义词。让我们来看一个例子。在这个例子中,我们有两行,每一行都包含公司名称和它们的产品。

属性。假设我们想要能够添加由这些公司制造的产品。那么,这就是一种做法。请注意,我在右侧添加了一个额外的属性,叫做“products”。然后我基本上只是在尝试连接,对吧?

所有这些公司生产的产品都包含在最后一个属性中。这是一个有效的关系吗?不是,对吧?因为我们基本上是在一个表格中尝试存储多个属性。所以请注意,这实际上是一个半表格,拥有自己的属性和行,对吧?所以根据我们对关系的定义,这是不被允许的。

因此,这不是一个有效的关系。这正是我所说的关系必须是扁平的原因。所以,正如我所说的,这个屏幕底部的关系不符合第一范式。那么我们该如何修复它呢?

所以必须有一种方法,能够存储与这些公司相关的产品,对吧?

解决这个问题的一种方法是基本上创建一个单独的表格。所以请注意,我在这里实际上创建了一个名为“products”的独立表格。还要注意,在这种情况下,我通过在产品表中添加第四个属性,尝试将其与制造这些产品的公司连接起来。

所以现在这是第一范式,对吧?因为不再需要其他任何东西。你可以基本上回去检查所有关系必须满足的要求。你可以看到它们都符合。所以这是一个有效的关系。太好了。这就是我关于关系想要说的所有内容。

现在你们应该都是专家,能够识别什么是关系,什么不是。那么现在我们来谈谈如何实际处理关系。我们这门课涵盖的内容是SQL,它基本上是一种早期设计的编程语言。

IBM研究始于1970年代。它最初是作为系统研究项目启动的。同时,正如Aditya所说,有趣的是,UC Berkeley也是关系数据库的先驱之一。在那个时候,一位名叫微石(Micro Stone)的研究人员也在研发一个原型关系系统,名为Ingress。

在Ingress中,他们用来查询关系的语言叫做Quail或QUEL。事实上,这与SQL最终的形式不同,这两种语言基本上并行了一段时间,直到Ingress的下一个版本,后来是Postgres决定最终采纳SQL。因此,SQL语言及其实现实际上是在80年代首次商业化的。

由IBM推出。他们基本上启动了一个名为DB2的产品线。不幸的是,IBM未能迅速把事情做得更好。那时有一家小公司在Redwood Shores成立,后来被称为Oracle。它也试图通过SQL作为使用的语言来商业化关系型技术。

查询和操作关系。正如他们所说,到那时其余的就成了历史。如果你看到我展示的时间线,或者是兴趣线上的细节,你会注意到SQL和关系数据库中的关系实际上已经超过了40年。因此,显然,SQL并不是唯一可以用来查询关系的语言。

事实上,在过去40年里,使用SQL查询关系这一概念反复被质疑。例如,在90年代,出现了一个被称为面向对象数据库系统(Object-Oriented Databases)的运动。这正是与限制关系的反向论点。

不仅仅是存储原始类型。在面向对象的数据库管理系统(DBMS)中,它们基本上试图让数据库管理系统本地存储结构或对象,而不是需要将所有内容膨胀。因此,这实际上是90年代的一部分,当时人们定义了新的查询语言,例如OFL。

代表对象查询语言(Object-Query Language)。然后在2000年代,人们开始关注XML和文档交换以及数据交换等内容。于是,像X-query和X-path这样的东西也随之而来。如今,当然,还有No-SQL运动,以及Hadoop和MapReduce等技术也相继登场。

这些基本上都是人们在操作数据方面工作的不同语言。而不是所有的语言实际上都在操作关系,正如你可以看出的。然而,SQL不知怎么的,随着时间的推移,重新作为标准不断出现。很难说为什么,也许只是因为人们习惯了处理关系。

他们确实喜欢用SQL作为操作语言。事实上,现如今,像Hadoop和Spark这样的系统也有SQL接口,正如你可能已经试过的那样。这基本上告诉你它有多受欢迎。然而,就这样吧,对吗?所以我们并不是在这里传教。我是说,我们确实注意到,像是,你知道的。

SQL并不是完美的语言,就像你能找到的任何其他编程语言一样。它是有用的,你知道,它也有许多语言特性,我们可以学习。希望它能教给我们一些教训,以便当我们真的走出这个世界时,也许你可能会想定义下一个奇怪的语言,你就可以借鉴。

你知道的,SQL开发者这40年来所学到的经验教训。好了,那么我们来谈谈这个语言的一些优缺点。你会注意到,当你开始做作业时。首先,SQL语言被称为“声明性语言”。这是什么意思?

这基本上意味着我们只需要说我们想要检索什么,或者我们想要处理什么,但实际上并不关心如何完成。所以,如果你还记得61AMV,你会记得,每次我们谈到定义一个函数时,我们必须提供实现,而实现基本上就是。

它谈论的是你想要实现的目标。而在SQL中,你实际上并不需要告诉我们你想如何实现它。你只需要说你想达成什么目标。听起来像是魔法,对吧?我的意思是,这样就好了,因为我们就不需要做61课程的所有作业了,对吧?但是,你知道的。

当然,这也有权衡。我们将在讨论实现时看到这到底意味着什么。你知道的,正如我所说,它在许多不同的实现中非常流行,广泛应用于各大商品数据库管理系统中。所以这绝对是一个优点。而且,你知道,这些实现中的每个其实在某些方面会有一些不同。

语言支持,语言提供了哪些特性等等。他们都在效率上进行竞争。因此,你肯定希望使用一个,例如,当你想运行查询或插入数据时,能在很短时间内完成的数据库系统。我能看到这一点,对吧?所以一些优点,首先。

它实际上是有限制的。如果你深入了解这种语言,你会发现它其实并不是完全的。因此,单一的语言本身并没有实现完整性。它实际上需要一些称为用户定义函数(UDF)的东西,才能让它变得完整。

语言。尽管如此,像你知道的,它实际上只是用于数据处理的通用语言。因此,尽管已经定义了所有特性,然而人们一直在尝试扩展它,使其能够用于超出数据处理之外的其他用例。但到目前为止,这种语言确实非常适合进行数据计算。基本上就是这样。

你会注意到,当我们进一步讨论这些实际特性时。不过,就像我说的,对吧?

它实际上是可扩展的。你知道,你可以为其添加功能,许多供应商都这么做,很多通用数据库也如此。而且你还可以使用 SQL 创建许多不同类型的数据源。事实上,人们已经做出了超越关系的数据扩展。因此,你甚至可以使用 SQL 查询图形或其他类型的数据模型。

所以 SQL 语言本身有两个不同的部分。我们在这堂课上将要讲解的第一个部分叫做数据定义语言(DDL)。这就是我们用来定义关系和模式的语言。然后,语言的第二部分叫做数据操作语言(DML)。

这就是我们用来编写查询、实际检索存储数据的例子。传统的关系数据库管理系统应该有非常高效的实现来处理这些语言的两个部分。例如,正如我之前所说的,你大概不希望使用一个需要等待数天的数据库。

对吧?同时向其中插入一行数据。正如我们将在本学期稍后深入讨论的那样,选择正确的算法来实现这些语言特性,实际上是关系数据库中最有趣的研究和实现问题之一。为了激发讨论,让我用这个关于水手、工作人员和研究的例子来说明。

在这里我们定义了三个不同的关系,你可以查看这些关系中有多少个属性和元组,对吧?这就是你的第一个 SQL 程序。在这里,我们创建了一个名为 sailors(水手)的新表,并且我们基本上创建了一个包含四个不同属性的表。所以请注意,在 SQL 中。

约定是首先定义属性的名称,接着是该属性的类型。因此你会看到,例如,SID。在长颈鹿一词中,SID 是在前面。而顺便提一下,在本节课中,你可以基本上认为我们不区分大小写。所以大写和小写在本质上是相同的,尽管你需要检查你实际使用的特定实现。

你会注意到这里有一个下划线。此时,SID 实际上是下划线标记的。这就是所谓的键。所以我在这里用粗体标出了它。

对吧?那么这意味着什么呢?键基本上是每一行的唯一标识符。例如,在这个例子中,你可以看到这些行中的每一行都有一个唯一的 SID 属性或水手 ID 属性标识符。那么,作为键意味着什么呢?这就是唯一的标识符。

它基本上提供了一种查找该关系的方法。所以在这种情况下,给定SID,你基本上可以找到整行数据,对吧?举个例子,如果我们提供SID为2,那么我们基本上可以用它来查找整条记录,因为我们基本上知道没有其他海员有这个SID。

这是一个唯一的查找键,在表中标识“2”,对吧?举个例子,如果我们要存储一个学生表格,你也可以想象,学生ID在这种情况下将是键。所以因为它们必须是唯一的,显然我们不能在整个表中有重复的值。举个例子。

我们不能在这里有另一行,其中SID等于2,对吧?

所以这是不允许的,对吧?但是还要注意,键本身不一定只能由一列组成,对吧?例如,在这个例子中,它实际上只是由这一列组成,即海员ID,但我也可以定义另一个表,其中包含多列,组合起来形成唯一标识符,可能是名字和姓氏。好。

这里是我们使用数据定义语言定义的更多表格。请注意,像这样,我为BOCs关系定义了另一个主键。在这种情况下,它是BOC ID。所以你注意到我们称其为主键,对吧?

我们之所以称其为主键,是因为基本上每种类型的关系只能有一个主键,即使这个键已经设置,并且可以由多列或属性组成。我们稍后会讨论如何实际存储这些主键,或者数据库在定义某个内容为主键时会做些什么。但现在。

只要记住,我们讨论的每个关系只能有一个主键。好,然后这里是另一个。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_15.png

所以现在我们定义了另一个表格,叫做研究表格。请注意,这是我们第一个由多个属性组成的键的例子。所以在这种情况下,我们说海员ID、BOC ID和日期组合在一起形成唯一标识符。这是有道理的,对吧?

因为我们在谈论的是这种情况下的预订表,对吧?

这是我们为人们服务的表格。所以显然,仅凭海员ID是不够的,它不能作为唯一标识符,因为一个人可以预定多个前台的座位。类似的,对吧?

如果你考虑BOC ID或日期,它们本身也不是唯一标识符。然而,你可以认为它们组合在一起,三者结合形成唯一标识符。那就是我们将在这种情况下用作主键的内容。所以你还会注意到,像是。

它通过图示方式,我们描绘一个关键字的方法是通过下划线。所以你可以看到,在这个例子中,我们下划线了所有三个属性,因为它们组合在一起构成了关键字;而对于BOC表,我们只下划线了BOC ID,对于sailor表,我们只下划线了sailor ID作为关键字。很好。

所以让我在这里暂停,我们将在周二继续讨论如何实际编写查询,以从这三个表中检索数据。[ Silence ]。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a941846d6b83ce0262220ddf81b60192_17.png

P10:第10讲 迭代器与连接 I - main - BV1cL411t7Fz

好的,看到你们所有人都在这里真是太棒了。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_1.png

好的,太好了。给我一秒钟确认一下我的聊天窗口是否可见。好了,太棒了。我们现在正在直播,讨论关于连接算法和 IT 行业的内容。我想 Alvin 和我基于核心团队的反馈决定,我们会尝试在课程中间或者课程结束时做一些公告。

可能有些人会因为之前有课而迟到。因此,我们尽量在课堂的中间做公告。通常当我把掌控权交给 Alvin 时,或者当他把掌控权交给我时,我们会在那个时候做公告。如果我们没有交接掌控权,我们会想办法安排。

我们仍会尽力确保公告在课程结束前完成。所以我们在谈论迭代器。我们之前谈到过关系运算符都是这个迭代器类的子类。在我们继续讨论之前,让我们复习一下上次讨论的内容,那就是查询计划。一个 SQL 查询会被编译成查询计划,查询计划是这样的。

就像这样的树。这个树编码了元组的流动。基本上,元组从底部流动。这些是关系,它们向上流动。因此,树中的这些边缘编码了元组的流动。树中的顶点包括运算符和底部的源顶点。

以及编写表访问运算符。这可能是扫描或索引查找等。因此,这就是你如何读取排序树。这被称为查询计划。在某些情况下,它也被称为数据流图,原因显而易见,因为你在图中编码了元组的流动,对吧?

所以它是一个图,编码了元组的流动以及对元组执行的操作。而这个数据流图不仅仅是数据库系统特有的,它在其他大数据、深度学习和机器学习系统中也可以找到。因此,数据库系统中的这个查询执行器组件负责实例化。

这些运算符负责创建这些运算符的实例。查询优化器则负责选择哪些运算符需要首先执行。因此,这个优化器负责选择查询计划。查询执行器将创建这些运算符的实例,运算符随后执行查询。

查询的执行,好吗?所以执行查询计划的是查询执行器。查询执行器通过创建这些运算符的实例来运行这些运算符。因此,对于每个运算符,每个内部节点,每个叶节点,查询执行器都会实例化一个运算符。所以你会有,每个这些都叫做迭代器。原因很快就会明白。

比如你有一个索引嵌套循环连接迭代器,或者一个投影交易器,或者一个选择交易器。这些是一些即时交易器,比如选择和投影。我们上次讨论过即时交易器,我们也会快速复习一下。底部我们通过索引扫描迭代器来访问这些关系。

所以这些基本上是通过索引查找该关系的元组。稍后我们会看到为什么这可能会很有用的例子。所以每个操作符实例基本上实现了这些迭代器接口。这是每个操作符都需要支持的一组标准方法,这样就有了一个统一的。

操作符之间具有统一的可组合性。所以一个操作符可以调用其他操作符,而这些操作符可以组合在一起,形成这个完整的计划。因此,迭代器接口如此重要且强大。每个操作符实例负责执行操作符逻辑,并将元组转发给下一个操作符。这个操作流从底部流向顶部。

所以元组将从底部到顶部流动,结果将在根节点生成。到目前为止有什么问题吗?好的,那么,什么是这个迭代器接口?

那么,这是什么,所有这些操作符都应该支持的这组公共方法是什么?迭代器接口基本上是一个类,支持一组固定的方法。它是一个抽象类,具有一组固定的方法。一个是设置方法,它基本上为每个操作符设置输入集合。

给那个操作符。所以这些是该操作符的子操作符。然后是,通常这是标准的,我们将避免讨论它。这对任何特定的操作符实例来说并不是非常独特,它基本上是整个操作符空间的一种标准。

所以我们不会过多关注这个,我们会关注剩下的三个。剩下的三个更有趣,而且对于不同的操作符它们是不同的。因此,init基本上是进行设置,对吧?它基本上是在开始处理元组之前进行一些处理。

所以这是在调用next之前的调用。next是负责单个元组处理的操作符。因此,这个init操作在调用next之前被调用。它设置一些状态,通常初始化一些变量,做一些背景设置,之后你就可以开始处理那个操作符了。下一个操作符是返回类型为元组的。

所以它基本上负责生成另一个元组。这个操作符是由父操作符用来生成另一个元组的,比如说来自子操作符的元组。而且它负责返回另一个元组。因此,通过反复调用next,你可以遍历产生的元组。

由一个给定的操作符来执行。因此,术语“迭代器”就来自这里。好的,这就是“迭代器”一词的来源。关闭函数是标准的。再说一遍,这通常在操作符没有更多元组可生成时调用。所以你基本上是在某种意义上“关门打烊”。所以你基本上清理状态,关闭文件句柄等等。所以这个迭代器接口是基于拉取的计算模型,而不是基于推送的。

计算模型。根节点开始,根节点从子节点请求更多的元组,然后子节点再从各自的子节点请求元组,以此类推,直到元组从叶节点被拉取,叶节点是所有的关系,一直到根节点。所以根节点在某些情况下可能是,例如,控制台,控制台可能。

调用查询计划的根操作符中的“初始化”。根操作符然后随之请求更多的元组。如果这个元组尚未准备好,下一个请求会传递到查询计划中的后续节点。

每个操作符都作为一个操作符,然后如果它们尚未实例化,您可以通过调用“初始化”来设置它们。然后如果元组已准备好,它会立即提供。如果没有准备好,它们将再次调用它们的子操作符,依此类推。大致就是这种基于拉取的计算模型的工作方式。

它从根节点开始,然后逐步向下传递到叶节点。这个“初始化下一个”可以是以流式处理、实时方式,或者是阻塞、批处理方式支持的。流式处理基本上涉及较少的工作量,而阻塞通常用于那些必须消耗大部分子节点输出的操作符。

在它生成任何输出之前,它不会产生输出,直到它消耗完所有输入。例如,排序操作是一个阻塞操作,因为你需要查看所有的元组,才能按排序方式生成结果。所以,这种迭代接口的一个好处是,所有的关系操作符。

被实现为该类的子类的操作符可以进行组合。因此,任何迭代器都可以作为输入传递给另一个迭代器,因为它们都实现了相同的接口。这个迭代接口的另一个有趣之处在于,给定操作符的所有逻辑都被封装在其中。因此,迭代器可能会维持大量的私有内部状态,这些状态仅对该操作符可见。

操作符对其他操作符不可见。因此,它们可以维护哈希表、运行计数、指针、游标,以及排序文件等。因此,这些都是各个操作符可以作为状态维持的内容。好的,我们现在来处理一个简单的实时操作符,即选择操作符。再说一遍。

这是关系代数上下文中的sigma运算符。这个是一个流式或即时运算符,意味着它为每一个生成的元组做一点工作。好的,那么InIt做了什么?好吧,选择操作符的InIt是以谓词作为参数调用的,它是。

参数是作为选择操作符的下标应用的条件。所以它做三件事。首先,它对其子节点调用InIt。因为选择操作符不能单独存在,它必须有一个子节点。通常这个子节点其实可能有所不同。所以它可以是一个关系。

它也可以是一些操作符的结果。所以这个选择操作符对其子节点调用InIt。它使用这个输入参数谓词设置一个名为pred的内部变量。所以这是一个存储状态的局部变量。然后它有一个名为current的局部游标,初始值为null。好的,我们接下来会看到为什么current很有价值。

所以下一个函数有一个while语句。下一个函数是说,只要current不在文件结束,并且谓词对当前元组的评估结果为假,继续从子节点获取另一个元组。好的,基本上这个while循环指示这个选择操作符的子节点生成。

越来越多的元组,直到你找到一个匹配谓词的元组,或者你达到了文件结束标志。好的,eof是文件结束。要么你找到一个匹配谓词的元组,要么你看到文件结束。无论哪种情况,你都会返回那个,对吧?一旦你找到一个匹配谓词的元组。

你返回那个。否则,你基本上已经处理完了,达到了文件的结尾。基本上,这意味着子节点没有更多内容可以提供。所以你返回文件结束标志给父节点。好的,这就是你如何生成另一个符合条件theta的元组。

或者谓词(pred),在这种情况下,它作为该操作符的一部分进行指定。关闭操作符不做什么惊人的事情。它基本上告诉这个操作符的子节点关闭。好的,我们来看看一个稍微复杂点的操作符。这是一个堆扫描操作符。所以这个通常作为查询计划的叶节点出现。

因为它基本上是在读取堆文件。所以这个heapscan的初始化函数接受一个关系作为参数,然后基本上为该关系设置一个堆扫描。所以它会为这个关系打开堆文件。然后它会尝试查找堆文件中与该关系相关的第一页。在那一页中,它识别出第一个槽位,引用该关系中的第一个元组。

页面,它可能会在后续需要读取。好的,这就是 init 做的事情。所以一旦你设置了这个状态,基本上你通过 init 做的就是告诉系统,嘿,这是我的当前页面。所以,这是我正在读取的页面。在这个页面中,这是我将要读取的插槽。然后,接下来会有一个运算符请求元组,随后再调用。

好的,那么接下来该怎么做呢?如果当前页面是 null,那么你就返回文件结束。基本上,这个是预留给你已经读取完所有元组,没什么东西可以再读的情形。好了,那么,如果当前页面不为 null,那么你最终会做的是初始化一个叫做 current 的变量,它指向当前页面和当前页面内的当前插槽。

那个页面。好吧,这基本上是一个记录 ID 指针。而这个记录 ID 将作为我们的返回值。也就是说,我们还没完全结束,因为我们不仅仅要返回记录 ID,我们还需要弄清楚如何准备好跳转到下一个记录 ID,以便进行下一步操作。

下一个调用。对。所以,单纯返回当前页面和插槽 ID 后继续执行是不够的。我们需要弄清楚如何推进指针到下一个我们将在后续请求中读取的记录。因此,这个逻辑基本上就是告诉你怎么做。当前插槽会被推进。所以你调用 advance,这个函数将从一个插槽推进到下一个适用的插槽。

在该页面上。如果当前插槽是 null,那么这个函数会返回 null,这意味着该页面内没有更多的插槽可以读取,在这种情况下,你就推进到下一个页面。好的,那么你就会转到堆文件中该关系的下一个相关页面。如果当前页面不为 null,说明你还没有读完所有页面。

如果它等于 null,意味着没有更多的内容可以读取。如果它不为 null,意味着你仍在处理一个你还没有读取完元组的页面。所以,当前插槽会被初始化为该页面内的第一个插槽。好了,所有这些处理就是为了为下一个调用(next)做准备。

所以,一旦你完成了这部分,你就返回当前记录 ID,它是当前页面在当前插槽对中的一个记录,返回给父节点。好吧,有什么问题吗?好,明白了。对,所以,关闭操作也不算令人惊讶。它基本上是关闭文件句柄。对,不是。好了,Sean,继续吧。是的,我在想,像是。

我们在嵌套的 if 语句中再次检查当前页面是否为 null。如果它是 null,我们是不是也应该返回文件结束?抱歉,不好。不好。你想在这里加上文件结束吗?所以如果当前插槽是 null,我们就会推进当前页面。对吗?没错。如果当前页面推进后是 null。

我们应该在那儿返回文件结尾吗,因为我们在这种情况下仍然返回当前记录?不,所以。你可以这样理解,对吧?当前引用的是将要生成的当前元组,或者是当前调用 next 时的记录 ID。然后当前槽位和当前页面基本上表示即将生成的下一个元组。所以如果当前页面最终是文件结尾。

这意味着你已经超出了适用记录的范围。你没有更多的记录可以生成,但你将在下一次调用 next 时检测到这一点。现在不用担心这个问题。你现在已经有了要生成的记录。所以你可以直接生成它。好了,大家,谢谢。好的,好的。是的,是的。

那么,为什么我们传递的是记录 ID 而不是记录元组本身呢?

所以你也可以这样做。这只是一个指针,这和前面提到的是等效的,对吧?我的意思是。你也可以直接减少记录,元组本身也可以,这样做是等效的。我猜这取决于实现方式。所以没有特别的原因需要使用记录 ID。你也可以提取元组并让它作为输出。那样也可以。谢谢。

所以这也取决于你使用的编程语言。如果你使用的是 C++,你可能需要复制一些东西,但如果你只是记录,返回记录 ID,那就只是在返回一个数字。还有其他问题吗?好的。好吧,那么让我们来谈谈排序。好的,我们将讨论的变种是两次传递排序。

所以这只是两次传递。如果我没记错的话,基本上是四倍的处理量。好的。让我们通过这个例子来讲解,然后如果需要的话我们可以再讨论复杂度。好的。所以排序的初始化函数基本上接受一个参数,就是你想要用来对输入数据进行排序的键。好的,这就是你将要排序的子项。

子项的结果。所以这是这个初始化函数接受的一个参数。你首先要做的就是完成零次传递。好的,零次传递。负责在磁盘上生成排序运行数据的过程已经在初始化步骤中完成。这个过程是一个阻塞调用,因为它没有生成任何输出,而且消耗了很多资源。

输入的部分。所以基本上你最终会像往常一样执行 child dot init,你初始化子项,然后。你会重复调用 child dot next,然后在磁盘上生成排序后的运行数据。好的。一直生成排序后的运行数据,直到子项提供文件结尾。此时你知道已经生成了这些排序后的运行数据,你可以继续进行下一步。

好的,接下来是为第一次合并设置准备,对吧?因为我们假设这是一个两遍的算法,我们将假设它有足够的缓冲区来正确地进行合并,这意味着需要有足够的空间来容纳每个排序运行中的一个页面。好的。

所以你正在打开每个排序运行文件,并将每个运行的一个页面加载到输入缓冲区。你基本上是在为第一次合并做准备,将磁盘上每个排序运行中的一个页面加载到输入缓冲区。这就是你的初始化。初始化已经做了很多工作,对吧?

所以初始化阶段已经做了很多工作,基本上是在为排序合并的下一阶段做准备。好的,按照排序运行的方式进行排序已经完成,我们已经为合并准备好了状态。好的,接下来这个函数基本上是执行第一次合并,并且假设有足够的缓冲区来进行合并。这样你就拥有了每个排序运行的一个代表性页面。

所以产生的输出元组基本上是所有缓冲区中的最小元组。它查看所有包含每个排序运行的一个页面的缓冲区,找出所有缓冲区中的最小元组,然后将其作为输出。如果这个最小元组是其缓冲区中的最后一个元组,那么你基本上就需要重新填充这个缓冲区。

对吧?所以你从那个运行中获取下一个页面到缓冲区。当你生成一个最小元组时,你识别出最小元组,并且已经重新填充了每个排序运行对应的页面。所以你现在又从每个排序运行中获取了一个代表性页面。你可以返回输出,然后等待下一个调用,以便你可以继续进行。

再次读取一个最小元组。Carson,你有问题吗?

既然我们要在多个不同的值中选取最小值,那么有什么好的方法来实现呢?

你只需基于所有这些值构建一个最小堆?

所以你可以做得更复杂一些。你可以存储一个最小堆。也许更直接的方式是循环遍历所有元组,找出最小值,对吧?另一种选择是,嗯,基本上这两种方法可以作为选择。

一种智能数据结构能够在添加新的元组或移除页面时,始终保持最小值。所以基本上,当你耗尽一个页面中的所有元组时,你就引入一个新页面,这对应于对该数据结构的一个新插入。另一种选择则是直接遍历。

通常你会选择一些轻量级的方式,所以优先队列可能并不是必需的。Carson,这样理解吗?是的,这样理解。还有其他问题吗?好的。那么排序操作的关闭函数,也就不算什么令人惊讶的部分,对吧?

基本上,你会释放运行文件,然后关闭子进程。也就是说,销毁你剩余的状态。我们继续讲解 group by,我们将特别讨论一种情况,特别是基于已排序输入的 group by。所以我们假设输入已经排序。好的。

所以这可以是一个明确的排序操作之后的例子,group by 正在消费排序操作的结果。结果证明,我们也可以在不显式排序的情况下进行分组排序。所以输入不一定需要排序。但在这个例子中,我们假设输入已经排序。

我们假设输入已经明确排序。因此,group by 与我们正在计算结果的聚合无关。对合并组中元组的操作适用于多种聚合函数。所以典型的处理方式是这样的:你为每个组初始化某些状态,针对每个你要使用的聚合函数。

你需要为其生成一个结果。你一次处理一个元组,与你当前的状态合并,然后返回结果,当你完成一个组时。让我们举一个具体的例子。如果你的聚合类型是 count,那么你维护的状态就是这个变量。

这里有一个叫做 count 的变量,它在每个组开始时初始化为零。当你看到更多的元组时,你会增加 count 的值。这就是当你合并一个新值时所做的操作。然后,在看到该组的所有元组后,你返回 count。好的。

所以这就是当你完成一个组时的操作。总和类似。好的,你有一个叫做 sum 的变量。你将它初始化为零。如果你看到一个新值 X,你就把它加到当前的总和中,然后当你完成时,你就以此作为这个分配的输出。基本上,对于这些函数中的每一个,操作都是如此。

你一次处理一个值,并且可以增加它。你可以将它与现有值合并。现在,让我们谈谈平均值和最小值。那么,对于平均值,状态应该是什么样的呢?我们想要维护什么样的状态?

那么我们如何将新元组的结果与现有状态合并呢?

总和与计数,正确吗?

所以基本上,你会同时维护总和和计数。你会将它们都初始化为零。当你获得新值时,你会增加计数,然后将新值加到总和中。然后,当你需要生成结果时,你就拿总和并进行除法运算。

它按 count 来进行处理。那么最小值呢?最小值也不太令人惊讶。你基本上会保持到目前为止看到的最小值。在初始化时,你会将其设置为一个非常非常大的值。好的,比如你可能会将其初始化为正无穷大。然后,当你得到新值时,你会检查当前最小值是否小于新值。

你正在检查的值是否存在。如果新值更小,你会用新值替换当前的最小值。然后,最终你会生成你一直跟踪的最小值。好的,这就是它的样子。所以平均值基本上保持一个注释对。

总和和计数。对于最小值,你将保持当前最小值,这个值是你迄今为止看到的。你将其初始化为一个非常大的值。如果当前的最小值大于你看到的值,你会用它来替换当前的值。好的,有问题吗?所以这是我们认为的,是的。是的,既然平均值使用了计数和总和,这是否意味着,比如说,如果…

那么我们是否真的需要做所有这些工作,比如创建和设置状态?如果只是计算平均值,或者如果只是简单地计算两个已经知道的值,是否有必要做所有这些设置?是的,如果你只需要平均值,那就想想,看是否能找到更好的方法,对吧?

我不知道有没有更好的方法去做,而不需要同时维护总和和计数。哦,好吧。是的,我在想,就像我之前说的,如果只是依赖于计数和总和,是否有必要维护平均值本身?我们是否需要维护?好的,我明白了。是的,所以如果是这样,你肯定可以,如果你知道你会返回计数、总和和平均值。

比如,你可以保持总和和计数,然后就完成了,对吧?

你可以对所有三个都使用这个方法,如果那是你的意思。好的,是的,那是我想问的。是的。还有其他问题吗?好的。那么,使用这个原语,让我们试着找出如何处理未排序的输入进行分组。这个分组操作符的初始化函数将接收一堆分组键。

这是你的分组键和你想要生成的一堆聚合。好的。下一个函数负责繁重的工作。下一个函数基本上会继续组合新的值。所以下一个函数就是下一个函数。

所以下一个函数就是下一个函数。基本上,下一个函数会持续地组合新的值。对于一个给定的组,当它确认不会再看到该组的任何元组时,它会产生一个结果。好的,这就是直觉。

所以你的输入已经排好序。记住,由于你的输入是有序的,你基本上是在按顺序消耗结果,对应于一个组。一旦你进入下一个组,就可以为该组生成一个结果,然后开始一个新组。好的。所以,你有这个do while循环。再次。

现在暂时不关心结果。结果被设置为 null,稍后我们会解释为什么结果设置为空是有道理的。当前组最初设置为 null,实际上指向你正在处理的当前组。所以这个元组是一个变量,指向来自子任务的下一个元组。好吧。所以你向子任务请求另一个元组,得到它,这就是你的新元组。

然后这个新的元组,你需要检查该元组所属的组是否是当前组。好吧。它属于当前组吗?如果不属于当前组,那就意味着你需要开始一个新的组。好吧。当我们开始处理时,每个组的值是空的。所以你最终读取的任何元组,所属的组都不会是空的。

所以在这种情况下,你最终也会开始一个新组。所以如果当前组不等于空,意味着你会处理一些组,然后你会继续为该组生成结果,因为我们知道,已经移动到下一个组了。好吧。所以结果基本上就是当前组。

也就是说,当前组,并且完成所有需要生成的聚合结果。然后我们将重新更新当前组,当前你正在操作的组,将变成元组所属的组,并且我们会对每个组调用所有必要的聚合操作。好吧。所以如果你确定没有更多的元组可以生成,基本上就会发生这种情况。

对于给定的组,你还会对所有的聚合操作调用 merge。所以基本上是合并与当前元组相关的所有聚合值。所以不管你是生成一个新组还是继续处理一个旧组,都会执行这个操作。一旦这个结果变量不为空,

所以记住,结果变量最初是被初始化为 null的,如果该结果变量变得不为空,那就意味着你已经准备好为一个组生成结果了。所以一旦发生这种情况,你就会退出这个两重 while 循环,并将其作为结果返回。好吧。所以一旦你有了一个新元组可以贡献,这基本上就是一个组。

它的所有聚合结果,你就会开始生成结果,并在下一次调用时返回。所以,子任务这一部分其实并不令人惊讶。你基本上是在对子任务调用 close。所以这个迭代器的有趣之处在于,它实际上在任何给定的时刻,只在内存中维护一个部分结果的元组。这一点非常有意思,对吧?

所以它基本上维持着某些状态,这个状态是恒定的,不受每个组处理中元组数量的影响。这个设计非常巧妙,对吧?

所以它是非常轻量级的。好的,接下来我们讨论的这些操作符有堆扫描、选择、排序和 group by,我们现在可以开始对整体查询计划有一些感知了。因此,查询计划,正如我们现在将讨论的,是单线程的。好的,我们稍后会看看是否需要将其改为多线程。目前,我们将考虑查询计划是单线程的。

所以你可以想象,查询计划基本上是从根部开始调用 next,在初始化之后,根部上下跳动,沿着查询计划上下调用 next。每次调用 next 会导致对其他操作符的 next 调用。

在查询计划中,这些调用将产生结果,并将这些结果传递回根部。所以作为一个练习,我鼓励你尝试一下这个特定的查询计划,它并不复杂,是一个单表查询计划。看看比如,如果你在 group by 上调用 init,它是如何递归地向下传递链条并返回一个值的?

然后,尝试理解如何调用根部的 next,看看 next 如何在查询计划中向下工作,然后返回两个结果。好的,正如我们所看到的,一些操作符是阻塞操作符。例如,排序操作符即使在初始化时,也会进行一个阻塞步骤,生成排序的结果。

由于 group by 操作符依赖于排序结果必须已经排序,因此它本质上像是一个流式操作符,就像 select 操作符一样。所以这两个操作符本质上都是流式的。因此,我鼓励你尝试一下。这是一个很好的练习,可以真正帮助你理解操作符的语义。

以及迭代器接口,以及这些调用如何从查询计划的根部一路传递到叶子节点。好的。所以,这里还有一个有趣的地方是,我们不一定需要将每个操作符的输出存储在磁盘上,对吧?元组实际上是流经查询计划的。

它们通过这个调用栈进行流式传输。当然,一些操作符可能需要使用这个。所以例如,排序操作符确实会将排序结果写入磁盘。但这不会暴露给操作符之外,至少从迭代器接口的角度来看,这些元组是被传递的,它们正在流经这些操作符。

所以迭代器框架本身是非常轻量级的,即使个别操作符可能更为重量级。好的,有关于这个的任何问题吗?好的,那我们继续讨论二进制迭代器。具体来说,我们将讨论连接(joins)。之前这是一个单表查询计划。现在我们将讨论连接。所以在谈论连接之前,我们需要设置一些符号。

所以,带有方括号的R将表示存储关系R的页面数。PR将表示每个页面存储的R记录数。带有竖线的R将表示R的基数,也就是R中记录的数量。所以,这就是PR乘以方括号。以我们的例子为例。

我们将使用这两个关系:我们的预订(reserves)和销售(sealers)关系。因此,预订关系分布在1000个页面上,每个页面有100条记录,总共有100,000条记录。同样,销售关系也如此。所以你有500个页面,每个页面有80条记录,总共40,000条记录。这就是该关系的基数。

是的,我做了。你认为现在是处理公告的好时机吗?

好主意。好的。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_3.png

所以,我,Alwyn,没能找到你的公告幻灯片。所以我会直接根据我的Slack笔记做公告。好的。这里有几个公告。第一个,我会按随机顺序说,最重要的是期中考试快到了。

所以,我鼓励你测试一下你的设置,并在遇到问题时来找我们。对吧?所以Alwyn和我的办公时间相对宽松。如果你有任何问题,欢迎来找我们。如果有具体的技术问题,也可以在Piazza上发布帖子。如果有什么问题,我会。

也与期中考试有关,我鼓励大家参加这个周五的复习课。这个周的讨论也很有价值,非常有助于项目三的进行。项目三要求比较高,所以我鼓励大家参加本周的讨论。

如果你不能参加讨论,我鼓励你通过其他方式弥补,比如观看视频。例如,你提到维他命的发布时间和截止日期对你来说不太合适,部分原因是你想先参加讨论,再去做维他命作业。因此,我们实际上调整了时间表,将维他命作业的发布时间定在周六,截止日期是。

下一周的周一。这样你总共有九天时间,这样你就可以在参加完讨论之后再做维他命作业。这些就是公告。关于公告有任何问题吗?

正如我所说的,如果你有任何问题,可以随时来找我们。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_5.png

让我们检查一下你的视频设置。我想Ethan已经在Piazza上发布了一个示例,告诉大家我们期望你在考试期间做什么。如果你有任何问题,或者只是想检查一下设置,可以在办公时间来找我们。所以,我们要求大家提交一些东西,如果你想获得额外的分数。基本上就是检查一下设置。不过正如Piazza上发布的内容所说。

我们实际上不会逐个点击你们提交的每个视频,对吧?由于隐私原因,而且我们也没有足够的工作人员去检查所有600个视频。所以如果你对自己提交的视频是否符合要求有疑虑,可以来办公时间找我们看看。说到这里。

录制的意图是在考试期间,我们可以检查是否符合要求。我们希望如此,并且它还试图最小化隐私问题。因此,鼓励良好行为始终是感觉安全的基本方式。我们确实希望考试公平,并尽可能与真实考试相似。

它应该是这样的。所以它会模拟真实的考试体验。很好。还有其他问题、想法或疑虑吗?好的,我将开始介绍我们联合算法的第一个变种。这被称为简单的嵌套循环算法,我们将为一般的θ连接进行描述。在我开始之前有两点需要说明。

所以我不会为这些算法展示迭代实现。而且,这只是为了简化,事实上,这些算法本身已经足够复杂了,展示代码并不会比我提供的直觉理解帮助更多。但我鼓励你们尝试自己推导出这些算法的迭代实现。

这些实现会是什么样子,对吧?在每个算法的nextclose调用中。我们惯例是忽略写出输出的成本。好的,原因是它在所有方法中是常数。因此,它不是我们用来在不同方法间比较的依据。

连接的结果将具有相同的大小,无论你使用哪种算法。在许多情况下,连接的输出将通过next流式传输,对吧?

所以它可能不会写入到这个地方。它可能会被下一个操作符消费掉。因此,出于这两个原因,我们不会计算写出的成本。我们将专注于读取成本和处理成本,而不是写出的成本。好的,简单的嵌套循环连接非常直接。

基本上就是做两个嵌套循环。一个遍历R,一个遍历S。对于R中的每个记录R和S中的每个记录S,如果满足条件,θ谓词为真,那么你就将R和S添加到结果中,你基本上就是做连接,并将结果添加到结果缓冲区。好的,这就是你的简单算法。

那它看起来是什么样子呢?好吧,对于每个元组,你可以用X-Y平面来表示这个过程。R的元组沿Y轴,S的元组沿X轴。所以对于R中的每个元组r,你将遍历S中的所有元组。好的。然后你会对R中的第二个元组这样做,再对第三个元组,对第四个元组。

R 的元组等等。好的。所以你对每个大写的 R 的元组、小写的 r 的元组都做这个操作。好的。然后你遍历 S 的所有元组。好的。那么根据这些大小,这个算法的成本大概会是什么样子?有什么想法吗?

像是 N 平方吗?用这些数字我们该怎么表达呢?R 乘以 S。这是其中的一部分,但不是全部。好的。R 乘以 S 是其中的一部分,但还不是全部。让我来解释一下为什么。基本的成本是扫描 R 一次,而对于每个 R 的元组你要扫描 S 一次。

你将会遍历整个 S。好的。所以扫描 R 一次的成本基本上是 R 的页面数量,而你对每个 R 的元组扫描一次 S,所以你基本上是将 R 的基数乘以 S 的页面数量。好的。因此,总成本是 5000 万加 1000。好的。所以这是总成本。你就是扫描了一次 R,然后每个 R 的元组都扫描了 S 一次。

你扫描了所有的 S。好的。所以这是 R 的基数乘以 S 的页面数量。好的。这是第一个变体。对,所以一个自然的选择是反转这两个的顺序。对,你遍历所有的 S,然后对于每个 S 的元组遍历所有的 R 的元组。好的。所以你改变了这两个的顺序。那么在这种情况下,成本会是什么样子呢?

好的,你扫描一次 S,然后对于每个 S 的元组扫描一次 R。所以这是 S 的页面数量加上 S 的基数乘以 R 的页面数量。所以这个大概是 4000 万,而这个比之前的算法有显著差异,之前是 5000 万。好的。好的。所以我开始注意到聊天中有一些消息,问为什么要遍历所有的 R。

是基于 S 的元组而不是页面。这也是下一个算法背后的直觉。好的。好的。所以这是一个非常简单的算法,但它做了很多不必要的 I/O 操作。所以下一个算法会在思考时更加小心,考虑到从磁盘检索的基本单位是页面。

但在我讲到这一点之前,我确实想提到一下,做 R 作为外循环还是做 S 作为外循环之间有一个显著的差异。所以连接的顺序确实很重要,我们稍后会进一步探讨如何确定最佳的连接顺序。好的。那么我们该怎么改进这个呢?

聊天中有一些消息,这是这个思路。对,之前的算法在 I/O 方面效率比较低,那为什么不在页面的基数上操作呢?所以基本的思路是,现在有四个部分。第一个 for 循环是遍历 R 的所有页面。第二个是遍历 S 的所有页面。

然后,对于R页中的每个元组和S页中的每个元组,你就做连接并生成输出。好的。所以基本上你现在是在页级别而不是单个元组级别进行操作。你将引入这一页和那一页,然后你会对这两页中的所有元组对做连接。

所以整体执行大概是这样的:与逐个元组扫描S不同,你现在是在页级别扫描S。因此,在这个简单的例子中,你总共扫描了S四次,因为R有四页。那么,在这种情况下,成本是什么样的呢?对吧?那么成本在这里。

基本上是扫描R一次和每页扫描S的成本。对吧?所以扫描R一次,成本是方括号R加上方括号R乘以方括号S。而且这是50万,相对于前面的简单嵌套循环算法中的4000万来说要小得多。所以这就是我们所谓的页嵌套循环算法的基本思想,因为粒度。

你引入的内容是以页面为粒度的。好的。所以一个自然的问题是,我们能改进这个吗?对吧?是否可以进一步改进?有任何想法吗?使用更大的页面或缓冲区。Yin的想法是对的。对吧?基本的概念是,你想要最小化扫描S的次数。对吧?每次你。

在扫描S时,你增加了S的额外成本,包括S的页面数以及方括号页面的成本。整体成本因此上升。那么我们能否减少扫描S的次数呢?答案是肯定的,对吧?因此,与你逐页扫描S不同,你可以一次性扫描多页,对吧?如果你有B个块在。

如果你的缓冲区有B个槽位,你最好填充尽可能多的R页,然后扫描所有的S页。这正是下一个算法的思路。因此,传统上,这个算法被称为块嵌套循环连接。但因为我们之前在其他上下文中使用过块这个词,所以我们将它称为块。

好的。所以这将被称为块嵌套循环连接。这个块嵌套循环连接基本上是一次引入一定数量的R页。应该是R。然后循环遍历所有S的页面。所以,对于每个B减去2页的R块和每页S,你基本上是在找到所有的匹配元组。

这页S和R的区块中。你好?好的,我想是有人不小心打开了麦克风。好的。所以,你基本上是将R中B减去2页和这一页S做连接,并将所有输出生成,添加到结果缓冲区中。好的,这就是块嵌套循环连接的基本思想。在这个简单的例子中,可能。

你可以带入两个R的块,然后遍历所有S的块。所以你一次带入一个S的块。然后你会生成所有连接的结果。所以再次在图示的X,Y平面表示中,你基本上只是对S的所有页面进行两次扫描,因为你正在按粒度带入R的页面。

这些块的大小为Y B减去两页。那么,我们的约定是我们使用总体B块。我们为S有一块B B框架,总共有B块在你的缓冲区中。你有一页用于S,一页用于输出。然后,剩下的B减去2是你为R使用的。好的。那么,这样的成本是多少?

因此,成本基本上是扫描R一次的成本加上你需要扫描S的次数,取决于块数。那么我们如何计算有多少块呢?嗯,基本上是R除以B减2。所以B减2基本上是你每次可以带入的R的页面数,假设你有一页S是专门的,并且你有。

一个输出缓冲区。对。所以这就是“2”来自哪里。在这里你使用上取整,因为你想四舍五入。因此,总体成本是R的页面数加上R除以B减2的上取整值。这个是你带入B减2页时的块数,乘以S的页面数。

在这种特殊情况下,如果我们假设例如B为102,则总体会导致6000次IO操作。好的。所以这比页面循环提高了100倍。我会称之为稍微好一点。好的。所以总的来说,这是一个非常常用的连接算法,尤其适用于非等式谓词。如果你有一个θ连接,它是一个任意的。

如果条件不是等式,块嵌套循环连接通常作为一种默认的回退方式。好的。因此,其他算法如页面嵌套循环或简单嵌套循环并不是替代选择。好的。所以我想讨论的另一个替代连接算法是索引嵌套循环算法。好的。假设我们有一个。

在带有条件的等值连接中,ri。因此,R的第i个属性等于S的第j个属性。因此,索引嵌套循环连接的过程如下。就像你在排序时所做的那样,基本的简单嵌套循环连接,你会遍历R中的所有元组,并且对于R中的每个元组,你基本上会遍历S中的所有元组。

在条件匹配的地方,ri等于SJ,你将r和S添加到结果缓冲区,或者你生成一个连接的元组并将其添加到结果缓冲区。好的。那么我们如何找到ri等于SJ的元组呢?这就是索引的作用,对吗?因此,为了找到所有满足该谓词的S元组,我们用这个键进行查找,对吧?Ri。

然后,你在S上查找这个索引,这个索引可以是一个B+树,你找到所有匹配的S页,对吧?以及这些页面中的所有元组。对于每个你生成的元组,你将它与R连接并添加到输出中。那么,索引嵌套循环连接的成本是多少?嗯,成本是你要读取所有的。

所以,你仍然有方括号中的R的成本加上R的元组数量乘以找到匹配S元组的成本。而找到匹配S元组的成本变化较大,既取决于匹配S元组的数量,也取决于你用来编码B+树的替代方案。所以如果你记得,我们有替代方案一、二和三。

替代方案之一是将记录存储在B+树本身,而第二种和第三种方案则是B+树仅编码指向磁盘上记录的指针,对吧?例如,在堆文件中。好的,如果我们使用替代方案一,那么每次查找的成本是多少,对吧?

对于每个元组,基本上是从根到叶子遍历树的成本。这可能在两到四次IO之间。好的,通常我们讨论的树并不是非常深,因此你需要的IO次数并不多,才能到达叶子节点。如果一些页面已经在缓冲区中,这个成本也可能更小,对吧?例如。

你的B+树的根页面通常总是在缓冲区中,因为它访问频繁。对于替代方案二或三,再次,通过B+树的访问大约需要两到四次IO操作,然后就是检索记录的成本,对吧?使用记录ID。再次强调,记录ID基本上是页面ID和槽ID的组合。这取决于你使用的索引类型。

所以,如果你使用的是聚簇索引,你基本上为每个匹配的元组页面提供一个IO的成本,而对于非聚簇索引,如果你记得那些指针在非聚簇索引中是交叉的,在这种情况下,你不能避免为每个匹配的元组支付一次IO操作。所以这是有利有弊的。

对于这个索引嵌套循环连接,对吧?如果匹配的元组数量非常非常大,并且你使用的是非聚簇索引,那么有时索引嵌套循环连接的成本会非常高,以至于它不如其他连接方法可行,对吧?

有关于这些内容的问题吗?好的,那么我们来谈谈排序合并连接。顾名思义,排序合并连接使用排序作为进行连接的基本组件。所以它利用排序来进行连接。所以当你有一个相等谓词时,就使用排序合并连接。它适用于等值连接或自然连接。再次强调。

你可以从中得到一些提示,为什么是这样。你在做等值连接或自然连接时,使用的属性就是你排序的那些属性。因此,排序连接的两个阶段。第一阶段是按连接键对R和S进行排序。好吧?所以你在连接中使用的属性,实际上就是你开始排序的属性。

通过对R和S进行排序。这就能保证所有具有相同键的元组都是按连续顺序排列的。这让你能够按这个顺序处理这些元组。在某些情况下,你甚至不需要显式按连接键对R和S进行排序。输入中的这些元组实际上可能已经是排序过的。例如,如果你已经进行过了排序。

在此之前进行排序合并的过程,你的输入可能已经排序,对吧?因此,R和S可能因为这个原因已经排序了。或者,如果你使用索引扫描,可能元组会以排序的顺序产生。因此在某些情况下,你甚至不需要显式排序。它们可能已经以排序顺序给出。连接过程基本上会。

执行合并。它扫描排序后的分区,并在匹配时输出元组。现在,排序合并连接的挑战在于,R中的每个元组可能会与S中的多个元组匹配,反之亦然。因此,相比普通的归并排序算法,这就变得有些复杂了。你需要保持更多的状态,以确保。

所有可以连接的元组对实际上都会被连接。那么我们是如何做到这一点的呢?我们通过标记跟踪每个S元组块的开始位置。然后,我们利用这个标记来指定一旦处理完R中的一个给定元组后,应该回退多少位置。这样一来,我们就能更好地返回下一个R元组。因此,可以这么理解。

这个过程就像R是外循环一样。如果你想做一个嵌套循环算法的类比,R就是外循环。它只是向前推进。S是内循环,它会向前推进,然后回退。因此,标记用于指定元组块的开始位置。这个排序合并算法实际上非常。

这个过程可能涉及的内容,你第一次可能不太理解,我会一步步带你理清直觉。所以我鼓励你在事后花一些时间仔细琢磨这一点。它可能不会马上显现出来,但事后看来,我向你保证,只要给足够的时间,它就会变得有意义。因此,在我进入排序机制之前。

我想给出两个不同的直觉,然后带你走过代码。所以如果我有这两个元组,我想做一个排序标记连接,这些元组已经排序了。所以这些元组是按SID顺序排列的,而我在SID上进行连接。左边的元组,这个元组将与这两个元组连接。然后左边的这个元组。

这是R,而这是S,S将与这两个元组连接,然后是后续的元组。接下来,lover two将与这两个连接,最后rusty将与底部的107连接。因此,和归并排序不同,在归并排序中你可以简单地按照顺序遍历这两个分区,但在S的情况下,你需要先向下走,然后再回到上面。

所以这就是事情变得有点棘手的地方。这里是排序归并算法的第二个示例。抱歉,是排序归并连接。所以我从指向这两个关系的第一个记录的指针开始。再次强调,这些关系是按顺序排列的,我将像在归并排序中那样推动这些指针,从顶部向下流动。

到底部。我将从排序元组的第一个开始,向下走。好的,所以在这个特定情况下,我将从左侧的第一个元组向下走到第二个元组,因为右侧的元组比左侧的元组大。现在我找到了两个匹配的元组。这两个元组已经准备好连接了。所以我要做的是标记它们。我将标记这个。

这是与SID 28对应的元组的开始。然后我可以开始产生结果。所以我要产生一个与YupE 103对应的结果。接着我要将YupE与104连接。然后现在完成了。所以没有更多的元组会在左侧产生YupE。现在标记派上了用场。我可以现在回溯,带回这个。

右侧指针回到标记处,开始为下一个元组产生结果,位于左侧。好的,在R上。所以我将把左侧指针推进到31。我意识到,31和28无法连接。因此,我需要将右侧指针推进。我将其推进到31。好的。再次地,我添加了一个标记,以指示一个块的开始。

与给定值31对应的元组。现在我可以开始产生输出了。好的。LABAR将与101连接。我可以产生一个输出。LABAR将再次与102连接。我将添加另一个元组到输出中。现在我走得太远了,我已经到达42。42无法与31连接。所以我需要回到31。然后我将推进左侧指针。

指针指向LABAR 2。所以LABAR 2现在将与101连接。对的。我将产生一个输出。LABAR 2将与102连接。我将产生一个输出。现在,再次地,因为这里有一个标记,这个黑色箭头,我可以回溯。我知道该回到哪里,因为我已经完成了为LABAR 2产生的结果。现在我可以继续前进。

LABAR 2与GUPY。在这个特定情况下,没有将产生连接的元组。因此,我将右侧指针推进到58。将左侧指针推进到58。现在再次,我有机会产生一些输出。所以我产生一些输出,然后推进指针。这就是这里的直觉。好的,现在有了这个直觉。

我们来看看代码。好的。这是第二个例子,我想给你们讲解一下。我们来看看代码。好的。正如我所说,这需要几次尝试才能理解直觉。所以我鼓励你们在课后也设置这段代码。好的。好的。所以这。

标记变量基本上是在编码是否有蓝色箭头。现在没有蓝色箭头。R 编码的是左侧的指针,S 编码的是右侧的指针。好的。所以现在没有标记。所以我基本上在这个 if 语句中。如果 R 小于 S,我将前进 R。好的。所以现在 R 小于 S。

比 S 更大,因为它指向 22,而 S 是 28。所以我要前进 R。我不需要前进 S,因为 R 不大于 S。所以现在我找到了两个匹配的元组,基本上是这样的。对的。现在我要开始添加一个标记,表示这是需要做连接的元组块的开始。所以这基本上是开始。

一块元组。它还会记住我处理完之后需要返回的位置。左侧是一个元组。所以现在我进入了 R 等于 S 的阶段。好的。那么,28 等于 28。我现在可以开始生成输出了。所以我将 R 和 S 加入结果中。好的。所以结果是生成一个临时变量,它将指向结果。

将会生成。我将前进 S,因为我想移动到下一个潜在的元组,它将被用来生成结果以供后续调用,并且我将生成结果以供后续调用。对的,之前的结果调用是 28 和 103。所以这是。这个,现在我的指针指向下一个需要与其连接的元组。

upy,再次,这个标记记录了我在处理完 upy 后需要返回的位置。那么,是否有标记?是的,有标记。所以我不在第一个 if 语句中。R 是否等于 S?是的,R 等于 S。所以我要将另一个元组添加到输出中。那个元组将作为这个结果变量实例化。然后我将继续前进。

S,因为我知道我已经处理完那个元组,并且我将产生那个结果。所以那个结果将作为下一次调用此算法或运算符的一部分产生。好的。所以这个运算符的下一轮基本上我再次有了一个标记。所以标记还在。所以我还是不符合这个 if 语句。

我进入下一个 if 语句,即 R 是否等于 S。这里我已经把 S 推得太远了。所以 S 不再等于 R。对的。所以我们不在这个条件中。所以我要进入 else 语句。else 语句说,嘿,你已经把 S 推得太远了,你不会再找到任何匹配的东西了。所以你要将 S 重置为标记。好的。

你将S回滚到标记,并推进R。所以你将R移动到下一个位置,因为你已经完成了处理所有在左侧有yuppy的联合元组。因此你将转到lover。对吧。记住,你正在按顺序处理R中的所有元组,R在这个情况下是外部循环。好的。

所以你已经推进了R,并将标记设置为null。好的。所以你基本上是清除了这个指针,因为你还没有开始处理S的元组块。好的。你将在下一阶段完成这一操作。好的。所以现在我们处于没有标记的阶段。好的。所以我们会进入这个if语句。然后我们会推进R和S,直到我们。

有可能匹配。所以R是否小于S?不是。因此你不需要推进R,R是否大于S?那么推进S。所以你将推进S,直到找到31。好的。现在。你将进行一次标记,表示你现在开始处理右侧的元组块,R等于S。好的。所以这个31等于31。

这意味着你也将产生一个输出。所以结果将指向那个输出。然后你将推进S,同时记住标记总是在那里,当你处理完LABBA并转到LABBA2时可以返回,并且你将生成一个结果。因此,结果将是LABBA和101。现在你有标记了,对吧?

你跳过了这个if语句,再次进入这个if语句,R是否等于S?是的,R再次等于S,因此结果将会实例化为这个对:LABBA,102。你将推进S。所以你将S移动到42。再次记住,标记总是存在,当你需要它时可以返回,并且你将返回这个结果。好的。所以这个。

结果将是对应于102的LABBA。现在是这个循环的另一次迭代。是否有标记?是的,有标记,对吧?所以你还没有完全处理完这个块,或者至少你没有检测到已经处理完这个块。R是否等于S?

R不等于S,因为31不等于42。所以你会进入else语句。现在这是你意识到,你不再处理这个元组块,你需要将S回滚。所以你将S重置为标记,然后推进R,因为你已经完成了LABBA的处理。从这一点开始,LABBA不再是任何联合条件的左侧。

你也会设置标记为现在。好的。接下来是下一次迭代。现在LABBA 2将在此次及随后的迭代中与101和102合并。你还没有标记,因为你还没有开始处理一块元组。由于R和S相等,你不会进入这两者中的任何一个。

无线语句,然后你将标记设置为等于这个31-101记录。在这种情况下,R等于S。好的。所以你将生成一个对应于LABBA和101的结果。LABBA 2和101。然后你可以推进S,同时确保如果需要,标记始终在那里,LABBA 2将与101一起被添加到结果中。

回到顶部准备下一次迭代,是否有标记?有标记。所以你将跳过这个if语句,转到下一个if语句,R是否等于S?R等于S,所以你准备生成另一个输出,这次对应于LABBA 2和102。所以你将把它添加到这个临时结果中。完毕。

你将推进S。S现在将指向42,你现在准备生成结果。好的。所以结果将是LABBA 2和102。所以你将把它添加到结果中。然后你回到循环的开始。你有标记吗?有。你确实有标记。R是否等于S?R不再等于S。你在S上走得太远了。现在你需要基本上。

停止处理这个块。所以你基本上进入L语句。你将S重置为标记,这样你就回到块的起点。然后推进R,基本上你已经表明没有更多的两个极点会与左侧的LABBA 2一起生成。

你将标记设置为null,因为你不是开始一个新的,实际上你是在完成这个块,所以标记需要被设置为null。好的。好,接下来,你到了另一个迭代的循环开始位置。这里没有标记。所以你现在要确定从哪里开始下一个由两个极点组成的块。

在S的右侧。你进入这个if语句,R是否小于S?R不小于S。所以你不会推进R,R是否大于S?R大于S。所以你将推进S,跳过42,因为42不会与44连接。你一直推进到48。现在你将用标记标记58。好的。

这将表示一个新记录块的开始。现在你在检查R是否等于S。在这种情况下,R不等于S。所以你将进入这个else语句。现在你将S重置为标记。在这种情况下,没有任何操作,它不会改变。接下来你将推进R。基本上,你已经。

确定没有两个极点与复制相对应。然后你想将标记设置为null,接着回到开始。所以没有标记。于是你再次进入这个if语句。因为它们相等,所以不需要推进任何一个。标记将被设置为S中最后一个元组,R等于S吗?在这种情况下,R等于S。太好了,我们有。

无法继续产生结果。所以你将这个结果添加到对应的这个“生锈的”和107。你将S推进到S中的下一个元组,这基本上就是文件的末尾,你返回结果。好了,然后你回到开始。所以这里有一个标记。于是你进入了这个if语句,然后你意识到你在S中走得太远了,因此没有更多的元组可以。

从这个联合中产生的。所以这就是你处理的结束。所以基本上,这是一个相当复杂的算法。但希望经过几次的理解,它会变得更加清晰。所以实际上,所有的这部分记录工作是为了确保你按照顺序逐步进行,并且你在S上是断断续续的进行。所以你开始一块元组。

然后你向下走,然后记住你需要回到哪里。所以你回到上面。接着开始下一个,你移动S,你将R向下移动,然后你再向下走。然后你弄清楚该往哪里回去。所以标记就在那里,作为一种记住在哪里重置X的方式,在你执行时。这是一个很大的部分。好的。所以我很高兴Alvin正在进行投票,因为这是一个相当复杂的算法。

事实上,我在描述给Alvin时,想添加另一个动画,目的是让这个问题更容易理解。但它仍然是一个相当复杂的内容。所以当你。什么时候你发现无法继续推进S?当R或S都不能继续推进时,那就是你到达文件结尾的标志,你知道你。

你不需要再做了。你知道你不会再产生任何更多的联合表了。所以我们有大约70人中的13个理解了这个。那么如果大家有问题,能不能请某人提问一下?好的。很好的问题,Carson。那么我有一个关于索引嵌套循环连接的问题。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_7.png

关于这部分的成本,有一部分是依赖于总的数量。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_9.png

记录。对。那么这部分来自哪里呢?你指的是R吗?第二个R,对。对。基本上,我是这样想的。你在读取所有的R,所以R的每一页都算在成本内。所以这是方括号的地方。然后对于每一个R的元组,这就是R的基数起作用的地方,你基本上是在查看。

将所有的S的元组加起来。哦,没错。没错。所以想象一下,如果你有100,000个R的元组,对于每一个,你都需要遍历这个索引。所以你就有了100,000倍的索引遍历和检索匹配元组的成本。对了。所以成本来自这里。那么我们今天就到这里结束吗?如果大家没有问题,或者如果你们。

如果没有问题的话,请继续待着提问,还是我们继续?我认为我们应该停止,开始下一堂课。我不会再讲解算法,因为它大约有50页动画,但我可以回答学生们有的具体问题。所以,我很乐意回答关于算法任何不清楚的方面,然后从那里继续。

如果到下一堂课没有人有问题,那么我们就会再次播放相同的动画,一遍又一遍,直到你们理解为止。好吧,反正谢谢大家。周四见。如果有问题,欢迎留下来。哦,我有一个简单的问题。所以,似乎在这堂课中,当我们分析时间成本时。

我们主要关注的是IO操作,而不是算法的时间复杂度。那么是因为IO成本主导了算法的时间复杂度吗?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_11.png

是的,这确实如此,传统上,这通常比计算要慢得多。好的,所以,IO曾经是数据库系统中成本的主导因素。这是一个我们仍然遵循的惯例。在现代,IO已经变得非常快速,因此你理论上可以进行一个更平衡的成本估算,涉及计算和IO的成本。

计算在内存中以及IO操作。因此,你可以有一个更复杂的成本模型。为了简化起见,我们这里只考虑磁盘部分,也就是磁盘的IO操作。这是一个相当大的成本,但我不会说它是唯一的成本。我明白了,感谢你。实际上,如果你还记得排序讲座的内容,是的。

实际上是在堆文件和不同类型的文件上,对吧?所以记住,我们讨论了所有这些不同的操作,对吧?比如插入、删除,然后是等式谓词,然后是范围谓词,对吧?

所以记住,如果我们实际上去看复杂度,就会发现它们都是很无聊的复杂度。它们都是多项式的,或者像是n的平方,或者n log n之类的。所以这其实非常无聊。即使对于联合操作也是一样,对吧?比如说,你知道的。如果你看我们今天讨论的不同算法。

它们都是二阶算法。它们都是n平方算法。细节实际上都在IO成本中,比如添加到其中的常数因子。就像我做的其他操作一样。所以,实际结果是,这成为了更主导或更显著的因素,如果你真正进行这些操作的话。是的,感谢你。如果你看像Postgres的成本。

比如说,这种模型会比简单的模型更复杂。如果你展示它。例如,我鼓励你去看看一些开源系统以及它们的成本。索引成本?不,通常来说,它们是如何进行成本计算的?如果你感兴趣,我很乐意提供资源。没问题。所以我有一个关于迭代器部分的问题。

讲座。当你接下来调用时,是在调用下一个你应该看的记录吗?还是你是在从项目操作符切换到选择操作符?你是如何区分从一个操作符到另一个操作符的?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/61dd289783a605e30da0eca1ed23e63b_13.png

顺便说一下,Alvin 应该停止录制。也许我们应该。[BLANK_AUDIO]。

P11:Lecture 11 Iterators & Joins II - main - BV1cL411t7Fz

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_0.png

好的,你看,嗯,所以我要继续我们停止的地方,就泥连接的种类而言,但我想先问你,大家对我上次经历的代码有任何疑问,在我进入之前,呃,如何进行成本计算,没有问题,也许只是一个快速的概述,好的。所以我不想遍历整个代码,我会试着做更高级别的总结,嗯只是为了,呃复习一下,好的,所以嗯,在最高级别,排序合并联接,呃,基本上整理了这两个关系,然后你试图找到这两个元组,或者元组和两个关系,嗯。匹配的权利在联合键,所以如果你有这两个关系,假定这两个关系是排序的,你想确定这样一个事实,即这边的这个元组连接,用这两个元组,在右手边,对于这两个的元组关节也是如此,呃等等,以此类推对。为什么伪代码如此棘手,呃,呃,因为你需要,当你扫描R的元组时,你也在扫描S的元组,但是扫描一次通过R和一次通过S是不够的,做对了,如果您只是在这里进行合并排序,这就足够了。

有多对元组可以为给定的联合密钥连接,所以你在右手边要做的是,你仍然可以扫描我们右手边的所有序列,你经常向下扫描然后向上扫描,这样你就可以把两边的对子都盖住了,所以要记住,发生什么事了?这里是。它本质上就像元组的叉积,在共享相同联合密钥的两侧,你这样做,那是你的记录,一个标记,可以让你追溯你的步骤,回到一个元组块的起点,对于联合密钥都具有相同的值,好的,所以这是一个高级的想法一种泥浆连接。所以基本上排序的状态合并连接嗯,将由三件事代表,一个是沿着左手边的关系向下多远,你在右手边的关系向下多远,你的标记对在哪里,所以马克基本上允许你回到之前的位置,它是所有具有相同联合值的元组块的开始。所以希望就直觉而言,这就足够了,嗯,我不打算走过动画,因为那会花太长时间,但如果有其他具体问题,我非常乐意回答他们,在这里或脱机,好的,所以让我们谈谈成本,谢天谢地,成本,你不需要理解其中的复杂性。

算法的复杂性,嗯要通过成本来工作,好的,所以加入SOA的费用基本上是,对应于将我们的排序为,然后你把R的所有页面按顺序放进去,S的所有页面按排序,记住我们不计算产出的成本,好的。所以这基本上是排序保证金的成本,现在,上学期,r的页数和s的页数可以,在最坏的情况下,实际上和这两个的乘积一样大,对呀,所以它可以和r的元组数一样大,S的页数你什么时候,遇到这种交叉积,就像Z问的那样。如果输入已经按排序顺序,你如何表示标记,如果你只是流元组,所以这将是一个你不能,嗯,不能,简单的说,一套看看它,所以这不是一个动态算法,对呀,所以你确实需要,呃,保持额外的状态要做,也像这样。所以你确实需要,好的,所以我会考虑以下方法,所以你一次生成一个元组,但是每个元组肯定要做更多的处理,现在的问题是,如果输入是按顺序排列的,你会怎么做,或者你如何表示标记,如果你只是组合元组。

如果你代表代表标记,您只需存储一些额外的状态,请记住,对于迭代器,您可以轻松地将其他状态存储在迭代器内部,事实上,这个州可能非常大,我也是,所以标记只是另一个内部变量,比如说,好的。所以最后一个学期可能是,呃,不仅仅是加法和,嗯,所以在这两个术语中不是线性的,但实际上可能是二次的,最坏的情况什么时候发生,有什么想法吗,R中的每一条记录都匹配,每一张唱片都是对的。所以最糟糕的情况是当你有一个叉积,因为两边的接头键都是匹配的,这是一个非常糟糕的情况,在实践中经常不会出现,所以我们真的不需要担心,这更有可能是r的页数和s的页数,这就是全部费用的一部分。因为r的元组数和s的元组数,对应于匹配不太可能很大的给定联合密钥,对呀,所以它不会二次缩放,它更有可能线性缩放,所以我们需要担心的是,嗯,两个输入的大小之和,所以现在下一个问题是缓冲区需要多大。

为了能够在两次传递中对R和S进行排序,每一个等等,呃,所以如果你还记得我们的排序讨论,我们要求缓冲区,基本上大于给定关系的页数及其平方根,对,所以为了能够对R和S进行排序,我们想最大限度地利用这一点。好的,这是因为,嗯,对于R和S进行两次排序,您需要考虑可以生成的排序运行的数量,您需要确保对于每个排序的运行,您的缓冲区中有一个代表,以便在第二次传递时正确地重新组装输出,成本是多少,所以成本是。基本上嗯,用r读,把我们写的东西写出来,排序运行,读入r,然后写出排序的输出,好的,所以这是四倍的成本,R的页数,同样,4倍于s的p,加上嗯,你在看书吗?呃,R的页面,然后在S的页面中阅读。所以它的整体尺寸是,R的页数,加上S的页数,一共是七千五百元,好的,所以这就是,这类产品的总成本,合并联接,嗯,你能再解释一下吗?我们如何在第一线的初始成本分析中得到R和S,所以如果你已经开始了r。

你现在已经对s排序了,r和s基本上是r和s所占的页数,嗯,你要读入的数据量是多少,进行最终的合并,对,所以基本上这是,读取RNS进行合并的成本,但有些是有道理的,但有时我们不得不。我们可能不得不连续多次投球,不是在合并的时候,不是在合并的时候,所以当你进行合并时,你基本上就像在翻阅一页页的R和S,在已经为您排序的RNS页面上漫步,对呀,所以你只是一次一个地经历,然后进行连接。然后产生输出,所以它实际上基本上只是你在读它,所有的页面都是S的所有页面,然后做关节,然后产生输出,事情变得有点棘手的情况,就是,如果很多r的元组和很多s的元组,具有相同的联接键。但这在实践中不太可能出现,所以我们不会担心这个,所以比较典型的案例,如果你有一页r和一页s,你可以只做关节,输出一些元组,然后继续这样做,当你走下R和S的页面,我明白这有道理,谢谢。好的,酷,好的。

所以呃,我们本可以做的另一种选择,如果我们想做一个排序,我的加入,记忆,沙发连接的输出是排序输出右,所以你基本上是在整理这两个关系,然后你把它合并,作为副产品,你得到了排序输出。实现相同结果的另一种方法是首先使用,例如,块嵌套循环连接,然后在后面做一个排序,所以让我们在这里举一个具体的例子,所以在这个特殊的例子中,我在做R和S之间的连接,然后呃,按联接键排序。所以这里我有两个关系,呃,预备役和水手,这些是基数,好的,所以在这个特殊的情况下,嗯,使计算变得容易,嗯,好的,所以每个保留地都有一个水手,这意味着我知道输出是什么样子的,输出基本上,嗯我们的元组。好的,输出将适合我们的页面,那么现在为什么这很重要,我们会按顺序看的,如果我先加入,后排序,让我们从讨论块嵌套循环连接开始,好的,所以我用它来做连接,如果你还记得我们做块嵌套循环连接的方式。

你有一个外部关系,它是s,然后你在内部关系的页面上循环,通过一次引入一大块外部关系,所以块的数量基本上是s除以b减去2,这是你加入的费用,现在,我这种类型的价格是多少。我现在知道连接的输出基本上是我们的页面,这种成本基本上是R的四倍,好的还有,所以我只需要两张通行证,这就足够了,所以这将是总成本,如果我先做一个连接,然后再做一个排序,而不是排序合并关节。它有七千五百英镑作为成本,对此有什么问题吗,嗯是的,因此,执行块嵌套循环连接的替代方案,或者块嵌套循环联接,然后做很多,或者你做一个Merjoin,首先是这种替代类型,然后再加入,或者更确切地说。进行排序和连接,在这个特定的例子中,成本更低,好的,关于排序mod连接,我想说的最后一件事,你实际上可以更多地改进排序mod连接,如果你认识到你不需要把它分成排序,然后合并。

您实际上可以在排序的第二次传递中进行合并,好的,因此,这将合并排序的最后一次传递与联接传递结合在一起,所以要做到这一点,我需要确保我有足够的缓冲区,这样我就可以,我可以适应所有的跑步。在最后一次排序中对R和S进行排序,所以如果我有一个两次传球,呃,排序可能加入,所以我在最后的合并过程中做了一个连接,我基本上读入r并写出排序的运行,所以这是你过去的零读入s并写出排序的运行。呃也是零度以上的一部分,然后我在R运行和S运行中合并,所以我在内存中有多个从R和S排序的运行,我在找我们酒吧的火柴,所以我基本上把合并作为其中的一部分,这种融合,再次使用相同的术语,合并右步。所以基本上呃不只是呃,对R和S进行排序,它也在同时进行合并,所以这种改进实际上成本更低,所以基本上你是在读写画画,我们排序的R,你在读s,写出有序的s,所以每个都是两次。

那么您在R和S的排序运行中都读取,然后进行排序合并,所以它基本上是我们的三倍大,所以比较便宜,所以这里的约束使这个工作,在我做合并的时候,我需要一个代表从每一个排序运行在内存中,好的。所以如果我没有一个代表从每一个排序运行在内存中,我做不到这种精炼,好的,所以这就是我要说的,有什么未回答的问题吗,你能做到吗,即使你必须做两次以上的排序是的,对这样你就可以,基本上,呃,排序。到目前为止,您可以创建排序运行,合并到目前为止的排序运行,并创建更大的排序运行,合并其中的已排序运行并创建更大的已排序运行,然后进行排序合并,基本上是在最后一次传递中合并排序的运行。所以你当然可以这样做,您也可以将其扩展到多次传递,在什么情况下有两个以上的通行证,嗯,类似的场景,当您需要多次传递来排序单个关系时,对呀,嗯,所以那些是,呃是,嗯,我们可能想使用这个的相同场景。

排序多关节如何保证工作,如果我们不跑过一个,所以说,嗯,你这是什么意思?你对此有什么担忧,的,你为什么认为这行不通?你只是根据我们谈论的,就像上一节课结束时一样,所以我们有两个完全分类的,就像一堆书页。如果我们只是走,跑一次,我们会得到一堆片段,每个片段都会被排序,但如果你把它们放在一起,它们不一定都分类了,好的,所以让我给你两个选择,对呀,所以我基本上,好的,都整理好了,好的,所以这些是R页和S页。嗯,这就是你会得到的,你会把这些,你就会,也许它不会纵横交错这么多,但你会把这些,嗯,在合并阶段,在你做完排序之后,好的,这是我们在幻灯片之前谈到的另一种选择,现在,让我们来谈谈这个精炼。这种改进需要什么,而不是做一个完整的R和一个完整的S,我只需要对r和s进行排序,好的,所以我会运行我们的一个,运行我们的两个,然后让我们说运行呃,一个s,运行两个s,好的,好的。

如果我有足够的内存缓冲区,这样我就可以在内存中从这些排序的运行中保留一个块,那么我目前可以正确地进行跨连接,um rns,尽管我没有排序,哦,我明白了,我明白了,好的,所以我们要做的是过去的一个。当我们把它们捣碎的时候,我明白了,谢谢。我开始在,你去争取。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_2.png

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_3.png

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_4.png

到目前为止还有其他问题吗,所以我希望现在,你们知道为什么,我们之前一直在喋喋不休地谈论排序和散列,因为就像你现在看到的,我是说我们一直在用,分拣以实现关节,对呀,我是说不仅仅是为了分类,就像那样很有用。我希望是对的,你们可以看到所以,如果我们谈论,因为我们谈论排序和哈希,如果有我们可以使用sort来实现联合的情况,所以很明显现在就像你知道的问题是,我们是否也可以使用哈希权来实现联接,答案是,当然啦。是呀,我是说这就是为什么喜欢你,我们有向量的部分,就像你的哈希联合算法,好吧,嗯,正如你所记得的,我们的目标是基本上,不管我们是做嵌套循环连接还是基于排序或基于哈希的连接,对呀。这里的目标是把所有的姿势,应该可以在内存中同时组合在一起,这就是总体目标,对呀,所以我不在乎,我们如何实际实现它,目标基本上是他们所说的会合,对呀,呃,所有应该组合在一起以形成联合输出到主存储器的元组。

同时,所以我们到目前为止所看到的,在上一节的片段中,基本上是通过排序来做到这一点,所以现在这里有另一个想法,所以让我们再次尝试将散列用于相同的目的,我们试着像你的RNS一样加入,对呀,所以在这种情况下。这个算法只有在我们做等式的情况下才有效,呃,右关节,所以说,这意味着我们要么做连接,要么做自然连接,好的,那么这是如何工作的呢,所以说,让我们现在假设R是两者的较小关系,特别是我要求r必须适合主存。事实上,它必须适合,就像你知道的B-,呃,呃,对不起,它必须适应,就像你知道的B减去两边的记忆,好的,简单的算法如下,所以我要把所有的r都加载到主存中的哈希表中,对呀,我可以一次加载R的每一页。在内存中创建哈希表,然后我该怎么办,我接下来要把每一页都拿进来,一次一页,然后向右探测或检查s中的任何元组是否应该匹配,uh匹配r中的任何uh元组,当然,如果你聪明的话,您将在属性上使用哈希函数。

或作为关节一部分连接的属性,所以我们首先使用哈希函数除以r中的所有元组,和主存基于我们正在加入的内容,然后我们将从,It’一次一个,然后弄清楚,然后检查它是否与我们主存中的两极中的任何一个匹配。如果是这样复制呃,组合元组作为输出右,一次一个,然后我们扫描完所有,这有什么意义?所有权利,好的,所以这是正确的,那么这里的内存需求是什么,所以就像我说的对,我们假设R必须适合于内存,特别是R右的大小。或者R所占的页数,必须小于b-2倍,某种软糖因子,我称之为哈希填充因子,我是这么叫的,因为就像你知道的那样,维护哈希表是有开销的,然后每个页面在五月最小的主存可能不是均匀分布的,对呀。我是说可能会有技巧,哈希函数中的哈希,以此类推,所以我们最终可能会有你的一页和一桶,或哈希表的一页,就像你知道的,只有很少的元组,相对于你知道的,可能会有一些,呃,就像另一个桶,有很多元组的地方。

所以我们做b减去2,因为我们需要保留一个页面来读取输入,我们真正需要保留的一页,呃,写出输出,是啊,是啊,准确地说,然后这里的总和是速度对吧,所以我做了两次b减,我刚才谈到的某种软糖因素,就这么简单。运行哈希函数并计算出,这就是问题所在,是啊,是啊,什么能放进记忆中,所有的权利,所以这是个大问题,对呀,所以你知道,这就是为什么我说喜欢,这是这里最大的假设,那么如果R不适合内存会发生什么,好的。所以这里有一点改进,所以这里有一个代数性质,它实际上会帮助我们,所以你记得在这种情况下,这个片段是选择权,然后我们的领结是关节,然后这个V形的东西在这里只是表示析取或,所以我们基本上是在做一个联合。我们在挑选,呃,s i等于4的水手,或者s i d等于,联合呃结果对吧,所以你可以重写这个关节的一种方法,或者这个选择的工作,然后是一个关节,实际上是把它分开,使用联合,所以请注意,在这种情况下。

就像你知道的那样,水手ID是唯一标识符,对呀,这是主键,所以所以,就像你知道的,与之相连的东西,就像你知道的我等于四,不会加入等于,i等于6,对呀,所以这对两个rns都是正确的,因为在本例中i是主键。所以让我们把它分开,所以我在找,就像,你知道,i等于4或6的水手,让我们只做连接,你知道的选择分为两部分,对呀,所以这里的第一部分,只要做连接,SID上的选择等于4,SID的另一部分也是如此,等于6。所以总的来说你可以来你可以,我们基本上可以分解,就像你知道的,看起来像这样的东西,进入更小的部分关节,对呀,所以在这种情况下,就像你知道的,有一种方法我们可以重写联合算法,基本上。说我们试图得到的每一个关节基本上都是一样的,就好像我们在一个特定的ID上加入,然后遍历所有的ID,从R和S开始,然后我们要把它们组合在一起,使用联合,这有道理吗,所以我们只需要选择一个哈希函数。

这样我们就可以在内存中构建这个哈希表,所以这将解决我们喜欢的问题,你知道吗,你说得对,整个关系不能适应,所以现在需求改变了,只说好吧,只要有一个特定的切片,我们在主存中的适合权,然后我们就可以做。我们可以做,我们可以运行我们在上一张幻灯片上讨论的简单算法,到目前为止还有什么问题吗?所以这一定不是它的结束,对呀,因为如果这就是整件事的结束,然后就像你知道我,我是,我做得很好,你知道我,我可以喜欢。你知道吗,今天剩下的时间休息,对呀,所以肯定还有更多,嗯,但在我们真正尝试将这个算法推广到,呃,你知道更大,或者喜欢,你知道更多的实质性,让我们先来看看,原来如此原来如此,现在是时候休息一下了,首先呢。你们都知道今天是期中考试周,我完全明白明天有一个复习会议,你们可能也在为其他班做期中考试,你正在服用的,如果是这样,祝你期中考试好运,嗯和嗯,你知道,我们期待喜欢,你知道。

和你们一起为即将到来的期中考试工作,下周,对呀,所以只要记住有复习会议,助教们让我们提醒你们,请尽量在您的办公时间票中包括描述,因为这会帮助他们,嗯嗯,匿名找出,就像你知道如何把人们聚集在一起帮助他们。你们所有人同时,如果你还没想过,如果你还没有弄清楚,还有一种叫做派对模式的东西,它基本上允许人们在办公时间排队时分组工作,在等待一个TH到来的时候,所以呃,这也可能是一个功能,你可能也想尝试下一次。当你,呃在,试着使用办公时间网站让我们知道进展如何,我是说这是我们从61年开始采用的一个相当新的功能,嗯,他们做得很好,呃,那里学生的接待,但后来就像,你知道吗,我们有兴趣听听你们,呃感觉就呃而言。就你为你的六分之一的任务做这件事而言,所以如果你尝试,然后你喜欢它,让我们知道,如果你不喜欢,也让我们知道,然后我们可以试着改进它,一次又一次,对呀,所以你知道,请尝试打开你的视频,如果可以的话。

你知道,向今天在课堂上真正表现出来的人致敬,我明白就像,你知道吗,这是期中考试前的最后一节课,你们阿波罗,看起来很忙,所以谢谢大家,我想对殷来说是特别的荣誉,沃伦,杰克,罗曼、凯西和斯特凡的出现。也让我们知道你还活着,那很好,我希望你们能挺过期中考试,但就像你知道的,我有七十个人,就像你知道的,如果Gabe和Aditya是我们唯一看到的人,那我想我们会很沮丧,对呀,所以是的。我只希望我们不是对着一堵空白的墙说教,所以哦好吧,i,好的,所以任何关于喜欢的问题,你知道我是什么,我是,我们在上一张幻灯片上说过,这是一个最简单的哈希联合算法,你可以想象对,我是说它利用了。就像我们在哈希课上谈到的最简单的哈希算法,它完成了我们想做的事情,它带来了我们需要的所有元组,需要同时汇集到主存中,然后你知道,然后我们基本上就这样形成输出,对呀,那太好了,嗯,所以让我们试着理解。

让我们试着谈点更有趣的事,还有一种散列绘制算法叫做Grace绘制算法,格蕾丝哈希绘制算法,这个名字来自,呃,叫做Grace的数据库机器或数据库原型,这是东京大学在80年代开创的,这是一张照片。我不确定它是否应该看起来像,你是黑人还是白人,但我想也许这强调了,你知道那是对的多久,基本上是在上个世纪,它是如何再次工作的,它需要像你的质量谓词,对呀,所以你知道你的关节和你的自然关节,呃。和你的自然关节,它有两个阶段,第一阶段,我们试图对我们试图连接到磁盘上的两个关系进行分区,所以通过分区,在这种情况下,我们真的只是说像,运行你最喜欢的哈希函数,然后你知道把输入元组分成多个分区。所以如果你,运气好的话,然后你基本上最终,呃,拥有所有的元组,在同一个分区中,具有相同哈希值的,对呀,但如果你运气不好,然后也许就像你知道他们,它们是哈希冲突,对呀,所以在同一个请愿书中有多个元组。

即使他们,呃,他们实际上有不同的价值观,对吧,但是因为它们有相同的哈希值,它们是相同的哈希,他们把哈希放在同一个桶里,或者它们在末尾写入了同一个分区,对呀,所以我们可能会倒霉,不过没关系,因为再一次。我们的目标不是把整件事整理好,我们的目标就是喜欢,你知道吗,确保应该组合在一起的东西在内存中,同时,所以只要哈希函数是一致的,然后它将把R中的所有元组,在相同的桶或相同的分区中具有相同的哈希值。对于S也是如此,我们接下来要做什么,所以我们要把所有这些不同的分区都写到磁盘上,然后我们要应用同样的东西,就像我在上一张幻灯片中所说的,为每个分区构建一个内部内存哈希表,然后在另一个关系上引入元组。一次一页,然后看看是否有匹配的东西,如果有的话,然后我们产生关节输出,然后像之前一样写出来,在这个意义上没有什么变化,所以现在我要假设较小的关系实际上适合记忆,虽然你已经可以预示,就像,你知道吗。

我很好,我就是我要说的,对呀,在实际上没有的情况下,我们已经了解到,在外部哈希讲座中,就在我们喜欢的地方,你知道的,做递归,所以如果你不明白,先等等,然后我们会看到一个例子,嗯,就在一秒钟。就像发生了什么,好的,所以你记得,或者到期中考试的时候你会记得,呃,我们谈到了这个外部哈希算法,就在我们第一次试图分开的时候,就像你知道的,所有输入元组到B减去一个不同的分区或桶,把它们写到磁盘上。这就是中间的罐子应该显示的,然后我们有第二道,就在我们把这些分区中的每一个都带进来的地方,一次一个,并尝试在内存中构建一个内部哈希表,只是为了确保我们真的有,呃,具有相同哈希值的元组在磁盘上连续,对呀。这一切都是需要做的,这都是因为你知道我们,我们可能不会同时看到主存中的所有元组,在第一个右边,在第一阶段,然后可能有多个不同的元组具有不同的值,实际上被散列到同一个分区,因为,呃,的,你知道,呃。

哈希冲突问题对,所以这就是为什么我们有这个,我们有这两个阶段,但现在我们不是想,我们不做杂凑,我们正在做的加入权利,那么我们能用这口井做什么呢,所以在第二阶段,我们仍然可以在内存哈希表中构建这个。但我们实际上要引入另一个关系,一次一个进入主存,然后尝试在第二阶段做联合算法,嗯罗马人,你有问题吗,是啊,是啊,呃,你能提醒我P和R在哈希语中代表什么吗,所以它只是两个不同的哈希函数。所以p代表分区期间使用的哈希函数,然后R代表,呃,呃,关节过程中使用的配分函数,呃阶段或合并阶段,第二阶段,是啊,是啊,只是两个不同的哈希函数,所以不像之前的外部哈希讲座,对呀。我们在第二阶段构建的地方和内存哈希表,把东西放进不同的桶里,把它们写出来,我们就大功告成了,但在这种情况下,我们实际上是在尝试正确地连接,所以这就是修改的地方,呃出现了,所以我们要修改算法。

你知道S关系的一页,一次一个对吧,然后这里我们假设也已经孵化了对吧,所以这就是你看到这个的原因,呃,这里的数字,所以我们先把所有的请愿书都写下来,把所有的请愿书都写出来,然后我们会得到B减1的请愿书。对于R和S都是对的,然后我们会带来像,你知道从我们进入主存的分区,构建哈希表,然后你从S关系中取出几页,对应分区的一次一个,然后进行连接,所以请注意,在这种情况下,这里唯一的要求是。我们正在使用的请愿书,因此创建哈希表,在这种情况下,在这种情况下,r必须适合主存,没有要求喜欢,你知道来自S的请愿书必须符合主存,因为我们基本上是在一页一页地流媒体,所以不管它是否占用,你知道吗。b b,呃页,它捡起了大约一百万页,我不在乎对吧,因为我们一次带一个进来,所以这并不重要,这是一个伪代码,实际上,呃,将算法形式化,所以对于两个rns,这是第一行,我们要像R一样浏览每一页。

然后下一个喜欢是的,我们要读缓冲区里的那一页,然后就像你知道的,基本上建立我们的B减去一个不同的分区,所以你知道,然后就像以前一样,我们要冲水,就像你知道的,磁盘的输出缓冲区,当这个失败的时候。到目前为止,这整件事只是在谈论,就像你知道的,的第一阶段,就像你知道的,呃,哈希算法的,对呀,这有道理吗,所以到目前为止一切都没有改变,我只是想写出如何做外部散列的第一阶段,通过说我们将阅读R的每一页。然后我们单独构建b减去一个不同的分区,然后如果失败了,然后我们写到磁盘上,然后我们就完成了,所以这就是我在这里要说的,除了我们迭代r和s,有趣的部分来了,所以现在我们以,呃,b减去其中一个分区。R和B的权利减去S的一个分区,然后你有一个问题,哦耶,所以在天真的方法中,只有哈希不是s,但是为什么在这种情况下我们要对r和s都进行哈希呢,因为在这种情况下,我们假设它们不再适合,全部在主存中,因此。

我们不能把整个东西读入主存,然后,我做了它的权利,但如果我们把我们的,我们将构建一个完整的哈希,艺术表,我们还只是从广告中推销一页,你可以你可以,你确实可以你可以,但想想就像,那里的费用是多少,对呀。所以如果我们不哈希,因为我们基本上要经历整个对吧,为了弄清楚我们是否需要加入什么,但把它散列的好处是,我们只需要用同样的哈希集会阅读相应的请愿书,我们现在正在处理的,然后我们可以保证没有其他元组。并询问,将与我们目前正在处理的任何事情结合在一起,所以我们假设,我真的认为我们有办法知道哪把钥匙,侦察,哪些页将在哪些分区上退出,是啊,是啊,所以如果我们是在ID上加入的那我们最好是在ID上散列的。所以假设我们,我们知道所有一到五个人都将主持这个分区,准确地说,好的,好的,我看对了,对呀,我是说,否则我们也可以照你说的做,但我们基本上会招致更多,因为我们需要读取整个升级,所有的一切,是啊,是啊。

所以这就是为什么它没有那么有效率,我明白了,谢谢。好的,所以这里是算法的其余部分,对呀,所以对于所有这些分区,这就是这句话的意思,我们基本上要把它建在,构建内存中的哈希表,在这种情况下。我们将把它构建在内存哈希表中,从我们的,这就是你在这里看到的,对R的每一页,我们要读取每一个元组,然后我们要在内存中构建一个哈希表,对应于R的特定位置,然后这里的下一步是,这是我们实际上在做连接的部分。对呀,我们现在要阅读S的每一页,以获得相应的请愿书,所以这是个问题,对吧,所以请注意,就像你知道的,它们都有相同的下标,所以我们将在我们的第一个分区中读取,将其填充到内存哈希表中。然后我们要把的第一个分区,That’是的,不是第二个不是,第三个,只有第一个是对的,因为我们知道任何匹配的东西都将驻留在那个分区中,没有别的地方,然后剩下的基本上只是做绘图算法,检查是否有匹配的东西。

如果是这样,在输出缓冲区中写出来,满了就闪,然后然后继续,对此有什么问题吗,好的,这是一般的算法,对呀,所以我将在下一张幻灯片中说明这一点,但是一般的算法就像这个两阶段的东西,对呀。所以我们有一个分区阶段,在这里我们创建这个呃和B减去一个不同的分区,然后我们有一个征服阶段,我们带来的地方,就像你知道的另一个关系上的分区,然后做连接,所以我们在这里,所以我们有两极的rns。所以请注意我们的i的所有元组,呃用蓝条给它们上色,然后是或对应于两个s,我用橙子把它们盖住了,然后星星和方块,所有这些圆圈基本上代表了不同键的值,所以在这种情况下,我们应该加入,就像。你知道这个有正方形的元组,用另一个元组,正方形从S开始,比如说,好的,所以我们要看看这到底是怎么发生的,好的,所以第一步我们要在,假设选r右r是一个较小的关系,所以我们选择了我们在每一篇文章中读到的。

R的每一页,一次一页,然后在记忆里面,我们要把它分成不同的桶,基于我们最喜欢的哈希算法,然后我们读第二页,就像我们的外部哈希算法一样,嗯,然后我们把它冲到磁盘上,当呃,这些缓冲区中的每一个都会满。或者当我们做对的时候,所以你注意到这里的第二个位置有一个额外的悬垂,因为你知道页面已经被填满了,所以我们需要给它分配一个新的页面,然后把它放进去,然后向右走,所以现在我们已经完全散列了,嗯。你知道r的所有元组,所以我们对这部分做了,所以我们只是重复同样的过程,所以我们在S的每一页都读到了,然后就像你知道的,做同样的事情,做同样的事情,然后你知道同样读在另一页呃,哦,我想我倒车是R,对不起。好的,所以好吧,所以我知道为什么早些时候只是翻转,呃RNN,所以我们首先为S创建请愿书,然后现在我们为R创建请愿,完全相同的事情,然后在这种情况下,你知道第二页也溢出来了,因此。

我们知道我们需要为剩下的元组分配第二个页面,最后一个,到目前为止还没有,这正是我们之前讨论过的外部散列算法的一个例子,所以没什么有趣的,到目前为止,现在是绘图部分,对呀。所以我们现在有了我们在桌子上创建的所有这些分区,所以我们要读嗯,就像你知道这些分区一次一个,对所以,但你可能会注意到的一些事情是,例如,所有带有绿色星星的键,只驻留在分区一,就像你知道的。这些是r{\displaystyle r}中的,这些是s{\displaystyle s}中的,特别是,然后对所有其他的人也是如此,所有其他形状,对,但我注意到,就像在外部散列算法中一样。这实际上可能会向右倾斜,例如,你可以看到紫色的圆圈实际上有,你知道的比,比如说和星星相比,对呀,然后分成两个,在本例中实际上比分区1大,所以不能保证就像你知道的那样,它将均匀分布,所以你知道,哎呀。

在这种情况下,就像你知道的,这些请愿书可能是在不同的文件上背诵的,不同的机器或不同的磁盘,没关系,当我们试图将它们带回主内存时,有趣的事情发生了,所以我们现在要构建一个内部内存哈希表,所以我们要做一个。我们将通过读取每个分区来构建这些哈希表,一次一个,假设我们第一次读到,呃,一页,呃,从R的第一个分区,我们读到了,然后我们把它分成不同的桶,就在记忆中,我们下一步怎么办?我们将再次读取s右的第一个分区。现在的想法是,就像你知道的那样,任何应该与我们读过的元组连接的东西,r只能驻留在s的第一个分区中,所以我们只需要读取分区1,如果它驻留在多个页面上,我们只是在所有这些不同的页面上读到,一次一个。然后我们要执行连接,所以现在,例如,你看到就像你知道,星星应该是匹配的,所以你知道我们只是,创建联合输出,然后将其写入输出缓冲区,就像你知道的那样,我们只是把剩下的,对呀,所以现在三角形来了,呃。

然后我们做完之后就完成了,呃,与星号连接最后一个元组,到目前为止还有什么问题吗?现在我们完成了第一个分区对吧,我们基本上已经形成了我们应该创建的所有联合元组,呃,是啊,是啊,罗曼举起。所以如果你把一些东西放在记忆中,然后我们把它拉回输入缓冲区,然后我们再次对它们进行散列,将其中一些组合到,然后将其放入输出缓冲区并将其放回内存中,把它放回磁盘上对,就像我们做了所有最初的哈希。把它放进记忆中,然后我们把它从做这项工作的内存中提取出来,和缓冲区来组合其中的一些,然后把它放回记忆中,是啊,是啊,是啊,是啊,完全正确,我们这么做的全部原因是因为。原始关系R本身可能不完全适合主存储器,如果r本身已经适合主存,那我们就不用做这些事了,好的,是啊,是啊,这就是我们这么做的全部原因,呃,我有个问题,是啊,是啊,告诉我,发生了什么,如果呃,那个呃。

让我们说的一切,呃,在分区一,属于s,不适合b减去两个缓冲区,所以如果呃,是啊,是啊,所以发生的事情是,它不适合主,呃缓冲器,所以你还记得外部哈希讲座吗,对呀,一种处理方法,那就是基本上做递归。所以我们可以递归地分区或重复,对,呃,越大,体型越大,在这种情况下我们可以把它分成更小的分区,但我想我们需要把从S到绿色区域的所有东西都放在你的呃,动画,然后我们只需要适应,呃所有的。我们只需要在主存中放入一个特定的分区,对呀,一次一个分区,所以这不是在整个s或整个r中读数,因为那件事没有,我是说我每次都有一个所以啊,一个分区中的所有东西都属于s,它已经比呃大了,b减去2。所以你进不了果岭,会发生什么,所以我们不会把所有的请愿书都读到主存里对吧,所以我们实际上是一次一页地流,例如,在这种情况下,请注意在第二个分区中,这个实际上占了,你知道的,两页以上,一页以上。

所以我们实际上要一次一个地播放,唯一的要求是在我们阅读了与该请愿书相对应的所有页面后,那么如果我们在上面正确地构建哈希表,那么最好是在那个时候它可以容纳到主存中,这是唯一的要求,如果这不起作用。然后进行递归分区,找出另一种分区方法,就像你知道的那样,呃,我们就可以,我们,我们可以实现我们在这里试图做的事情,好的,是啊,是啊,所以耶,你知道,继续这个对吧,所以现在我们要,你知道吗,阅读所有的。呃,R右分区2的所有页面,所以我们读了进去,然后我们将创建内存中的哈希表,对,然后我们就结束了,然后注意到我们还没有完成,因为我们有第二页要从R读,所以我们要在第二页读,然后就像你知道的,呃。构造哈希表,呃,同样地,一旦我们现在完成了,我们有,在这种情况下我们很幸运,所以所有与请愿书相对应的页面,r中的两个实际上适合于b减去两个缓冲区,所以我们不需要做递归分区。

所以我们现在可以从嗯开始流元组了,所以我要一页一页地把它带进来,然后你知道,就像我们要弄清楚之前一样,比如这是否应该与任何东西匹配,如果是这样,那么我们只创建输出,呃摆姿势,和输出缓冲区。然后在我们处理第一页之后,我们处理第二页,然后我们形成所有的,所以我们应该创造,然后,如果输出缓冲区在此过程中满了,我们只要立即把它写到磁盘上,这样我们就不必等待一切都变得,呃,连接在一起,然后在最后。就像你知道的,我不打算在这里经历每一步,你可以,你明白我的意思了,对呀,所以我们基本上形成了联合产品,所以这里有一个通用的算法,对,我们刚才谈到的,也称为格蕾丝哈希连接,所以我们首先要经历这个分区阶段。就在我们做外部散列算法的地方,它的第一部分,通过把它变成b减去两个关系的一个不同的分区,然后我们要把他们一个一个带进来,创建内存中的哈希表,如果我们说的是像,你知道关系的较小关系或关系中的一个。

然后我们做完之后,我们已经创建了内存中的哈希表,我们要把其他分区页带进来,一次一页,形成联合产品,那么你认为与这个算法相关的成本是多少,我是说我可以告诉你你的第一部分,你知道的,呃,我们已经知道了对吧。因为它的第一部分只是分区阶段,然后是我们从上一节课中学到的分区阶段,对呀,只是要花很多钱,呃,阅读和写作每一个关系,呃一次对,一次是因为读了它,一个是写出来的,所以这里的总成本是,就像,你知道吗。页数是,呃,个人会采取,对呀,到目前为止,就像你知道的,没有什么是新的,匹配阶段,然而,其实也很直接,对呀,所以假设我们不需要做任何递归分区,那么这就和我们读r一样了,然后创建内存中的哈希表。然后读成事后,然后进行探测,然后构建联合产品,所以它只是页数,呃,呃,被r加占用,就像你知道的,S所占的页数,所以在这种情况下,总成本是,呃,对于两路算法的两路,对呀,只是会是这个数字的三倍,原来如此。

现在就像我们谈论哈希和排序时一样,我们想吓坏罗曼,你有问题吗,是啊,是啊,在分区后阶段,关于第二阶段,我们不应该少几页吗,因为我们只是把这些东西弄得乱七八糟,所以呃,匹配阶段不应明显少于分区阶段,否。我的意思是我们必须在最后读所有的元组,我是说我们不能逃避,就像,你知道的,我们只是分布在B减去一个不同的分区上,但我们必须阅读所有这些请愿书,一次一个,所以总的来说,就像我们读了我们的整个关系,对呀。对呀,但散列的全部意义不是为了节省空间吗,就像这样,它需要更少的页面,否,所以事实上,它可能需要更多的页面,因为就像你知道的,这取决于b-,B减去1的请愿书实际上是如何表现的,因为如果他们歪斜了。那么我们可能最坏的情况是什么,对呀,最坏的情况是我们有一个元组,呃,对于其中一些分区,所以在这种情况下,然后就像,我们实际上会占用更多的页面,如果我们在进行散列之前一开始就合并所有内容。

所以这根本不是哈希的目标,对呀,所以在这种情况下,我只是假设我们很幸运,一切都是均匀分布的,所以我们不像,你知道的,处理这些长到页的东西,对所以,是啊,是啊,嗯是的,所以有一个很好的问题,对呀。那么我们是否忽略了实际上写回来的成本,而且是的,对呀,就像盖比说的,所以现在我们忽略了它,因为就像你知道的,我们实际上可能就像,你知道吗,呃,不将其流回磁盘,但把它流到另一个,呃,下游操作员,因此。我们可能不会写到磁盘上,只是还如此,所以我只是忽略或挥舞我的手,说我们现在不在乎那个成本,呃,我还有一个后续问题,让我在聊天中问,哦像这样,所以呃,基本上,问题是数据是否足够熟练,你知道。假设是性别问题,然后雄性就太多了,没有办法分割它,所以它适合B减去两个缓冲区,那么接下来会发生什么,让我们说,然后你可以用这个,是啊,是啊,那你就不能用这个,好的,所以它只是不能不,好的,是啊,是啊。

因为在那种情况下,没有办法在内存哈希表中实际构建它,就像你知道的那样,它会适合记忆,好的,好的,所以基本上我在聊天中也说了,通常在这种极其扭曲的情况下,您最终将默认为chness和loop,恰恰嗯。这通常是可靠的,工作正常,独立于…,呃,排序分布是有意义的,是啊,是啊,或者你知道你可能真的很幸运,也许就像你知道的,它不适合其中一个关系,也许是为了另一个关系,它可能很合适,即使一切都是一样的哈希。呃请愿书,我是说,只要那份请愿书,呃足够小,这样您就可以在内存中为它构建一个哈希表,用b减去两页,我的意思是你仍然很好,只是这种关系太大了,就像你知道的那样,您不能在内存页中把它放在B减去2的范围内。在构建哈希表后,就在那时我们进入了这个麻烦的情况,呃,为什么,呃,是啊,是啊,所以如果这种情况真的发生了,我们是否事先知道我们不能使用Grace哈希连接,所以我们就像在执行这些关节之前,我们呃。

决定准确地使用不同类型的绘图,所以事实上你是在为我们将要报道的事情做铺垫,期中考试后,就像,你知道的,这正是查询优化的目标,对呀,它是决定使用哪一个权利,所以我们已经通过了一堆哈希算法来排序。然后现在也加入算法,对呀,那么实际上应该使用哪些,但这是一个百万美元的问题,期中考试后你会听到这件事,是啊,是啊,所以让我们回到这个右边,所以现在让我们来回答这个问题,让我们试着正确地回答这个问题。那又怎样,输入关系r的最大大小是多少,可以使用这个构建和探测阶段的一次操作来实际处理,一个离开分区阶段,对呀,你还记得,我们之前在做外部散列和外部排序时问过这个问题,我是说,很明显你也可以做多次传球。对呀,因为您可能需要递归散列,在这种情况下,也递归地合并,但现在让我们假设我们只能,我们只在一次通过内完成所有事情,只是为了让计算更容易,所以请记住,这里的目标是在我们的,假设我们可以,你知道的。

你知道的,在所有不同的分区上统一请愿,所以要求是,正如你们在外部散列课上所记得的,我们需要把,呃,r减b减1,其中一个不同的运行对,每个大小,就像你知道的,所占总页数r除以b减一,对呀。如果我们有十页不同的内存然后如果我们的,你知道输入关系R总共占了一百页,那他们每一个都是,那么每一次运行都将占用100除以9,对呀,尺寸正确的数量,然后在匹配阶段,要求是你知道每一个都运行正确。它必须适合B减去这些不同的页面中的一页,因为我们试图用它构造一个哈希表,所以这就是为什么你在这里看到,我们有额外的限制,r除以b-1必须小于b-2,那是第二阶段的,所以如果你把这个倒着解r。这基本上告诉我们,就像你的R必须小于,或大约小于b的平方,不足为奇,就像你知道的那样,这也是类似的计算,就像我们在外部哈希讲座中所做的那样,但在这种情况下需要注意的是,就另一个关系的大小而言。

实际上没有限制,我们又不是在特定的s上构建哈希表,原因是我们一次一页地把S的每一页都带进来,所以我们真的不在乎,可能是一个像,你知道的,一万页大小,就那件事而言,我们不在乎。所以把我们之前说过的所有事情都带回来,对呀,所以我们现在明白了,比如不同类型的哈希算法,所以有一个幼稚的,我谈到使用哈希连接,抱歉使用哈希,然后是我们谈到的格蕾丝哈希连接。然后你会记得在哈希连接的幼稚版本中,唯一的要求是我们构建哈希表的关系的大小,页数必须小于B,因为我们需要能够将它放入整个主存,这样我们就可以建立一个哈希表,嗯,所以这就是我想在这里不接受的。就像你知道的,用这个,只有一次机会,因为在那之后我们什么都不需要做,然后是恩典分区,我们用于格蕾丝联合算法,我们至少需要做两次不同的传球对吧,嗯,就像我刚才说的,呃为了成绩加入对吧。

所以这实际上是三分之一,Grace连接算法的代价,就IO而言是的,因为如果你还记得那个幼稚的版本,是的,我只读了一遍,然后我就写回来了,然后就这样了,因为我们一次一个地引入S的每一个元组。然后在内存中执行连接,然后把它都写回来,所以它实际上是成本的三分之一,就像r的页数加上呃的页数,右页,这是分数的总成本,测试联合算法对,我们需要做两遍,然后要求是,r所占的页数必须小于b的平方。因为现在我们实际上在这里你可以看到一个权衡,所以在天真的版本中,有一个非常严格的要求,就像你知道的,R必须小于B,对,然后,但因为恩典有很强的,其实是比较宽容的,它的长度可以小于b的平方,没关系。所有的权利,但我们在这里交换传球次数,对,因为他们有天真的乔,我们只做一次传球,与格蕾丝版本相比,我们需要做两个甚至更正确的,取决于我们是否需要进行递归分区,所以你可以很容易地想象,就像你知道的那样。

人们可以想出一个混合算法,试图利用,两全其美,对,确实存在,但就像你知道的那样,为了本课的目的,就像你知道我们不需要经历所有这些,因为那可能,嗯实际上进入了很多细节,你可能不需要,他们在最后相当棘手。对呀,但就像你知道的,正如前面提出的问题,嗯,你可以想象,要确定你实际上使用的是哪些是相当棘手的,当嗯,所以这实际上是下周课程的目标,但你知道我在这里想强调的是,对了,有一件事你需要记住,人们知道什么。这代表,时态文件,十十式,这就是奖金,对你们来说真的出现在今天的讲座上,它代表天下没有免费的午餐,好的,所以只有真正喜欢你的人才知道这里,就像你在课堂上知道的,或者如果你看回放,你其实不明白。你其实明白吧,我把这个放在演讲幻灯片里,对于那些没有观看的人来说,或者谁以后不看讲座,然后每一个像你知道的,其中一口井就像,你知道的,这个缩写是,好的,所以现在你们明白我们为什么要谈论这些事情了。

因为就像你知道的,只是很难预测哪一个是对的,好吧,你以后会记得的,但我们也会讨论优化,就像生活中的许多事情一样,所以这里有一个总结,对呀,所以我们两个都过了,呃。排序合并关节和哈希关节以及不同类型的关节,对呀,那么什么是,什么是,做你提到的基于排序的算法的好处是什么,嗯,显然,如果您的输入已经排序正确,那太好了,因为就像你知道的你还不如直接从右边开始。你只需要把它合并在一起,然后你就完蛋了,您不需要运行任何外部散列–类似的东西,嗯,它对任何类型的数据偏差都不敏感,这样我们就不用担心性别问题了,对呀,一切都是因为一些不好的原因,就像你知道的,呃。哈希到同样的请愿书哈希专业人士是,就像,你知道吗,呃,这是一个小的,是一个更宽松的标准,我想是的,如果,如果就页面大小而言,较小的关系实际上小于B,然后你就把整个东西读到主存里,然后你就完蛋了。

不需要运行您喜欢的排序算法的成本,你知道的,尽管你可能真的很喜欢,就像你们在61年经历的不同类型的事情,没关系,把整件事都放进去,建立一个哈希表,这很好,如果很明显,如果您的输入已经散列,因为就像。你知道的,在这种情况下,可以直接运行联合算法,实际上没有对它进行任何其他哈希哈希,所以要确保你理解我们讨论过的这些不同的绘图算法,所以我们从外部开始抱歉,我们从嵌套循环关节开始,它可以处理任何任意谓词。或试图运行的联合谓词,然后只是它表现得很差,对吧,因为我们需要保持,我们多次阅读另一个或s关系,对吗,好的,所以这就是为什么那很糟糕,然后我们谈到了这个案子,如果你有索引,你可以做嵌套环关节。但然后使用索引来帮助您查找实际上匹配的tohold,对呀,然后我们讨论排序,呃,甚至是基于哈希的算法,就在你不需要索引的地方,但你只能对同样的关节这样做,然后呢,就像我现在说的,比如免费午餐。

所以在所有这些中没有明显的赢家,对呀,所以有时候就像,即使像嵌套循环接头这样简单,实际上也是最好的方法,这就是你,呃,我们需要注意的是,和,就像我说的对,这基本上就是查询优化的用武之地,只是现在。确保您理解所有这些不同的算法,就像我之前说的,这部分的目标,课程的,就是确保你们理解关系运算符的不同实现,因为一旦我们理解了不同的实现,那么下一步是现在谈谈,我们怎么知道用哪一个。什么时候这就是下节课的主题,TM可能会对关节的这个部分有任何问题,我是说,顺便说一句,还有一件事你们需要知道,我是说计算机科学的每一个领域基本上都有这样的一些,他们所说的圣杯问题,就像这样。如果你能解决它,你基本上赢得了图灵奖,对于那些不知道的人,图灵奖基本上是计算机科学领域的诺贝尔奖,对呀,所以你已经猜到了,绘制算法是这些圣杯问题之一,你知道吗,很多人试图在,没有喜欢,你知道吗。

据我所知,完美的解决方案,所以如果你们想把你们的职业生涯花在这个巨大的好运上,如果你想出完美的联合算法,恭喜你获得下一个巡回演出奖,你们会在其他所有的人身上遇到,在你上的所有其他计算机科学课程中。或者你在计算机科学中学习的任何其他科目,计算机科学的每个领域基本上都有这样一个问题,只是喜欢的问题,你知道他们有多少,有多难,呃那些圣杯问题,好的,卡森,你有问题吗,是啊,是啊。所以如果我们必须递归分区,嗯,假设我们加入兰德,我们正在建立我们的心,我们的哈希表从我们的,所以说,假设我们递归地从我们的YEP分区1,嗯,这是否意味着每次我们从分区1和S流式输入页面时。也就是说我们也要用,再次使用递归哈希函数,只是为了重复这一页,是呀,是啊,是啊,所以基本上,如果你碰巧递归地请求任何一个,任何一个关系,那么你基本上也必须递归地划分另一个关系,任何其他问题,好的。

所以沃伦问我们是否会谈论混合测试接头,嗯,不是在这个讲座,嗯,事实上,我想我们可能没有覆盖它,呃,直到学期结束,取决于我们是否有时间,嗯,如果你们想知道更多关于关节的事情,不同类型的联合算法,我是说。让我知道,我是说,就像你知道有很多不同的事情我们可以谈论,然后你知道这可能会变得任意困难,但我只是不想混淆大家,因为我认为在项目3中实现这一点需要一些时间,嗯给你们,所以我不想让你们太困惑,好的,呃。这和上一个问题有点无关,但是嗯,在我们的聊天中,有人提到我们并不真正关心两者之间的哈希函数,呃,一对一,我们只关心,嗯,我在想为什么,我们不太关心一对一的财产,因为它似乎,呃,如果我们有一对一的财产。我们可以避免递归分区,但也取决于对吧,所以说,你知道的,在这种情况下,我们的目标只是确保,就像每一个请愿书都适合光束,之后在主存中减去两页,所以即使有碰撞,我是说作为输入,十种不同,呃,十个不同的值。

输入的右侧摆到同一分区中,我是说就这样吧,我的意思是只要它在主存中适合第二次通过,对呀,那我们就没事了,所以不需要一对一,如果是一对一,那么好吧,我的意思是,它不能保证所有的东西都能装进B减去两页。所以你知道这很公平,但我的意思是,嗯,如果是一对一,我们仍然必须递归分区,因为它不合身,我们可以用同样的,当我们做递归分区时,哈希函数,有点,我想就像,你知道的,取决于在这种情况下执行递归分区的含义。对呀,所以我们只需要这样做,如果你知道,页数大于b减去2右,所以你知道,当这种情况发生时,我是说,即使使用另一个不完美哈希函数,对呀,那不是一对一的,还是可以的,我是说只要我们,就像,你知道的。小于b减2,对呀,所以我想这是同样的论点,即使你有一个完美的哈希,你可能仍然会得到大于b-2的结果,从这个意义上说,我们仍然有麻烦。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_6.png

我要开始我的广播,和阿尔文关于联合算法的观点有关,有一件事一个有趣的小细节是,你可能会想嘿,数据库已经存在了三四十年,所以我的意思是,至少连接算法,人们现在一定已经想出了该怎么办,我是说。在联合算法领域没有什么新的东西可以想出来,但结果就像三年前一样,有一篇论文谈到了如何做,排序最坏情况最优联合算法的一种新分析,让数据库字段,呃着火了,在某种意义上,嗯,所以你还可以做一些新的事情。即使在这样一个成熟的领域,呃,就像数据库系统领域,所以呃,我只是想告诉你这件事,我很高兴给你一个指向那份报纸的指针,如果你有兴趣,是啊,是啊,所以有很多奖项,还在等你们。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_8.png

好的,所以,嗯,我没有一吨的时间,但我认为这个特定的话题不会是,呃,超级参与,所以希望它仍然很有趣,你仍然有精神空间谈论新的东西,离期中考试这么近,好的,所以嗯,所以基本上我们现在已经讨论了,嗯。联合算法,我们还没有讨论过排序和哈希,嗯,我们现在已经讨论了迭代器模型,我们上去吧,呃,堆栈右边,所以我们是,呃,现在,呃,终于,在数据库系统的这个块中,我们还没有完全研究过,所以查询解析和优化块。这真的是我们首尾相连的堆栈中缺失的一块,至少从一个用户与数据交互的角度来看,这是缺失的一块,这就是我们现在要关注的,我们学到的所有教训,呃,关于单个运算符的排序,实现都将帮助我们,也是关系代数。我们讨论过的关系运算符都将发挥作用,在这一节中,所以我想从查询优化开始,我们将要谈论的话题真的很像魔法,好吧,所以你有,呃,用户以声明的方式提供他们想要的东西,在域中,特定语言,SQL。

您指定您想要的作为答案的一部分,然后呃,查询优化是采取,然后生产呃,计算查询结果的命令式计算机程序,这有点像魔法,它是数据库系统做一些事情来计算你查询的答案,这有点酷,所以另一种思考的方式是,首先。很多聪明人,并且在查询优化上花费了大量时间,我是说我们仍然不知道如何把它做得非常非常好,我们就可以,我们就可以,嗯,呃,我们可以做得很好,但还有更多的工作要做,好的,所以这是一个已经研究过的话题。在这一点上像几十年,在这一点上,这也让人想起了许多前沿的人工智能问题,我也是,对呀,所以在某种意义上,查询优化是定义一个搜索空间,并定义一组策略,允许您遍历搜索空间,以有效的方式,所以从这个意义上说。这真的是一个非常古老的学校人工智能问题,对吗,它类似于你在现代音乐中听到的,um上下文是基于人工智能的软件合成,所以你是在用智力综合解决你的问题,这就是查询优化的真正含义,它是一种智能。

在某种程度上综合解决方案,呃你的问题,所以,开始的那种,或者查询优化的曙光发生在1979年,呃,由帕特·塞林格领导的一组IBM研究人员,这是帕特·塞林加和她在1979年写的论文,嗯,所以这是呃,的,的。他们在这篇论文中所做的贡献是,他们正在构建的系统的优化器,叫做呃,以及我们将要关注的优化器变体,在这次演讲中,他们想出了什么,俗称气缸优化器,好的,这是来自阿尔马丹的IBM研究小组的,所以在南湾。所以这是另一个优化器框架,它被称为级联优化器框架,这是另一种常见的优化框架的方法,你经常在商业和开源数据库系统中看到它,嗯,它有一些不同,但有类似的高水平,呃想法,所以至少在这门课上。我们将专注于气缸单元优化器,嗯,这很有影响力,呃呃,非常简单,非常巧妙的想法,还将帮助您了解级联优化器,如果你这么想,所以这就是我们要关注的,我也想留意时间,好的,我只有五分钟,嗯。

那么让我们谈谈这个气缸优化器,但在此之前,我想给你一个高水平的图片,当SQL查询是,呃,某种问题,对呀,所以我们讨论了框图,但是SQL查询实际上会发生什么,这里有几个组件,所以第一个是查询解析器,好的。因此,它被提供给一个查询解析器,该解析器产生一个内部表示,呃,对于此SQL查询,然后由这个查询重写,以各种方式改变它,然后是查询优化器,在它身上传递,对呀,所以查询优化器,它要做的是想出一堆等价的。查询计划,基本上是您可以执行的所有方式,那个查询,执行原始查询,它还会试着为这些计划中的每一个提出成本,为了选择成本最低的计划惊喜价格,好的,所以这是查询优化器的一个目标,所以它会和目录经理谈谈。这个目录管理器将保存一堆不同的统计数据,和其他元数据信息,这将在成本方面帮助查询优化器,还可以根据模式信息重写和解析查询,好的,所以目录对所有元数据和背景知识都很有帮助,当涉及到查询处理和优化时。

这将是有帮助的,最后选择的计划,优化器认为是的计划,数据库系统应用于此查询的计划,提供给查询执行器,如果你还记得,查询执行器框架基本上实例化了这些运算符,每一个都有,我吃接口的某种工具,然后流到呃。或者有元组的数据流,从底部,与高层的关系,所以说,呃,更详细地,让我们看看这些组件中的每一个,嗯,所以说,纳撒尼尔,你有问题吗,我要去看看目录经理吗,目前没有,但我的意思是你可以想到目录经理。我想我们之前也提到过目录经理,当我们谈论磁盘的时候,所以基本上目录管理员存储了所有相关的信息,嗯,围绕数据的所有背景信息,这将有助于数据库系统,对呀,所以会是,呃,排序存储关于各种属性的统计信息。它将存储架构,它将储存他们的类型,它将存储这些关系存储在磁盘上的地方,以及到文件的映射,所以所有这些都是目录经理管理的事情,所以它基本上是管理一切,嗯,不是数据本身,但与数据有关,它是关于数据的元数据。

所以让我们讨论查询解析,查询解析器负责获取SQL查询,然后生成解析树,好的,所以这是您的SQL查询的内部表示,所以这就像一个抽象的抽象语法x树,所以这个查询解析器,它将检查SQL查询的正确性。确保你什么都没做啊,有点不正确或不合适,所以基本上它也在检查授权,比如说,你不应该被允许对一个关系发出查询,你不应该接触到的,好的,所以它会,会有点,呃,阻止和防止这种访问,所以这两步很简单。所以这不是我们的重点,嗯,它基本上生成了一个抽象语法,续集查询的抽象语法树表示,然后我们重写的查询,接受这个表示,然后把它转换成一个,呃,规范的规范形式,那么它要做什么,是嗯,做一堆不同的事情。其中一些包括扁平化视图,因此,如果在此查询中提到任何视图,它将用呃取代那个视图,这种观点意味着什么,因此,与视图相对应的查询,嗯以及嗯,取代了呃,子查询,呃,有点,呃,嗯,减少查询块的数量,例如。

它可能会尝试用连接或uh替换子查询,与其他关系运算符(如联合),好的,因此,联合是首选,因为它可以最小化查询块的数量,但如果需要,也可以对子查询使用联合或差异,我想我们讨论了一个如何重写子查询的例子。只使用传统的关系代数运算符,所以再一次,查询重写不是我们的重点,我们的重点是查询优化器,所以它将在这个基于成本的查询优化器上,和,这个成本,基于查询优化器,一次优化一个查询块,那么查询块是什么样子的。嗯,基本上是一个精选的项目,加入一个小组,聚集在一起,呃,及订购,如果这将是最多的街区,所以如果这将是外部应用程序使用的块,如果是内部块,自动驾驶没有那么重要对吧,因为它不会被任何人消费。所以这个查询优化器很棘手,嗯啊,那么我们如何定义查询块,所以查询块是从查询重写,查询重写可能需要一个复杂的SQL查询,然后将其分解成这些查询块,好的,所以如果你有一个复杂的SQL查询,涉及呃te。

所以呃,公共表表达式,子查询,嵌套查询,还有一大堆就像在原地一样,以各种方式引用表格或定义表格,但后来经常,查询重写变得更加复杂,标识少量查询块,但他们会试着做对,所以它会尝试产生尽可能少的查询块。就像它能做的那样,它将给出查询的最终答案,因此,查询优化器的工作更简单,即一次关注一个查询块,而不是查看整个SQL查询,这通常是相当繁重的,所以它会试图找出如何最好地一次执行一个块。它将使用目录管理器的统计数据,了解如何估计该查询块的查询计划,所以,基于成本的查询优化器的问题是,即使您可能会想象它会找到最佳查询计划,它实际上不会找到最佳查询计划,把所有可能的都看透往往是很难的,嗯。给定查询的计划,所以你最终使用了某种启发式规则的组合,粗粒度估计,还有魔法,所以这就是你最终要做的,好的,所以我将停止在,在这一点上,我要谈谈,呃,查询优化,更多,在我们期中考试之后,有什么问题吗?呃。

在这一点上,好的,那我就不拍了,然后呃,祝大家期中考试好运,我们星期一见,是啊,是啊,祝大家好运,i,如果你有问题。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/fabe9c0031f718eb2f4036be0bfc3c8a_10.png

P13:Lecture 13 Query Optimization Costs & Search - main - BV1cL411t7Fz

看起来不错。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/f84281b8dc53cea57672e23e92e1377e_1.png

好的,嗨,伙计们,大家都好吗?好的,我看到几个竖起大拇指,嗯所以我嗯,我想我们会在中间的某个地方做公告,所以我很容易,结束这组幻灯片相对容易的工作,然后我会把它交给阿尔文,好的,所以说。我们在讨论各种查询计划,所以我们选择了一种查询,我们正在研究各种查询计划,这些计划可以用于该查询,以及它们如何有各种不同的成本,好吧呃,在iOS方面,所以我们从Pnested循环一路走来,呃到方块圈。中间还有一堆其他的东西,好的,所以呃,我们尝试了选择推送,各种类型的推下,我们尝试了物化作为另一个技巧,连接排序作为另一个技巧,所以这些都是我们尝试过的东西所以出现了各种成本,好的。所以我们要看的最后几个技巧,是投影级联以及索引的使用,好的,所以让我们先看看投影级联,所以这是迄今为止我们最好的计划这是一个块嵌套循环,与物化结合,并在一定的选择条件下,我们将用谓词下推来研究这个计划。

我选择这个计划的原因是,我可以为它提供一个理由,但更简单地说,这是一个成本相当低的计划,所以这就是为什么我想看看它,好的,所以这个计划,那个呃,预备役和水手按这个顺序,所以左手边的储备,右手边的水手。选择谓词投影,呃每一个对吧,然后这些元组,呃被喂进呃,块度环,最后你有一个投影到S的名字上,所以让我们谈谈这个的成本,好的,所以我们有五个缓冲区,嗯和嗯,和以前一样,对于块嵌套循环,你得扫描,所有的。你必须考虑扫描左手边的所有元组,然后对于每一大块,呃,或者每块页面,你扫描右手边的元组,或者扫描右手边的页面,对呀,这就是事情变得有趣的地方,所以扫描关系,或者更确切地说,储备关系,嗯。有多少页有一千页对吗,现在有一百艘船,因为有一百艘船,我选择了一块板,大约一百个,我基本上只有十页,现在我要做一个假设,每个保留元组是40字节,ID是我选择的唯一属性,嗯,呃,在我的投影是四个字节之后。

这意味着在这个投影之后,我只剩下一个字节或一页,对呀,从十页降到现在的一页,因为只有一页,它基本上只是黑色循环的一大块,所以我要用这一大块扫描所有的页面,从右手边,好吧,我扫描了所有的水手。我在飞行中应用选择,我在飞行中应用投影,然后我执行工作,所以我基本上有这个,这个问号说明了左手边的块数,五百是扫描右手边的费用,所以这里是1乘以500,所以我的总费用是一千五百,所以这里的一个问题是。我们真的找不到比这更好的了,好的,所以和我们的2300美元相比这是一个相当低的成本,一些东西,这是一千五,我的主张是,如果没有索引,你不可能做得比这更好,你为什么认为那是。因为这只是触摸每一页一次完全正确,原来你是,我是说,你实际上是在做两个关系的连接,水手关系中的后备关系,你必须触及这两种关系的每一页,一个占据了一千页,另一个占了五百页,所以你不能做得比一千五百好多少。

对呀,嗯,不使用INDE,好的,所有的权利,所以这是我们非常好的计划,基本上只是扫描两个关系中的每一个,这就是我们现在所需要的,让我们看看我们怎样才能做得更好,为此,我们需要使用索引。所以我需要对索引的类型做一些假设,所以我会假设我在船上有一个索引ID,这是一个聚集索引,我有一个水手索引,id,它是一个未聚集的索引,所以我的谓词在b上,并且连接在id上,好的,我还将假设索引足够小。这样它们就可以放进记忆中,所以我只需要说明,检索这两个关系的页面的IO成本,好的,所以如果我有我的B ID索引群集,这意味着如果我想查找b的页面等于一百,呃,这样的页面有十页,这些将是磁盘上连续的页面。和蝙蝠对应的所有元组,相等的100个将被打包在这十页里,另一方面,我不能对海豹突击队或希德做出这样的假设,这是一个未聚集的索引,所以你基本上有这种乱七八糟的指针,但这并没有真正伤害我们。

我将在下一张幻灯片中解释为什么,所以希望,索引的样子对你们所有人都有意义,好的,所以有了这个关于储备的B ID的聚集指数,问题是我们需要多少页的储备才能很好地访问,因为这个关系聚集在b i上,有十页。如果考虑到页数,在应用了这个谓词之后,有十页,那么我们只需要访问十页,好的,所以我们只需要访问十页,因为现在东西都聚集在一起了,这十页上有一千个元组回到我们的假设,在这里,我每页有一百个元组,然后嗯。一共有十页,所以总共是一千个元组,然后因为我在做索引,嵌套循环,在这十页中加入每一个这样的元组,我要去找一个完全匹配的水手元组,只有一个这样的水手元组匹配,对于每个这样的保留元组,好的。因为每个水手ID都有一个单独的元组,因为水手ID是,水手,好的,所有的权利,所以我去拿了这个,每个储备元组的um,我得去叫水手们,所以这是一个,伊奥,所以总成本就是扫描的成本,呃,检索所有页面的费用。

使用保留表中的聚集索引,只有十页,加上检索一个水手元组的费用,对于这十页中的每个结果元组,呃,所以你有一千个储备元组,你有一只眼睛让他们每个人取回,匹配的一个水手元组元组,所以总成本是一个零一个零。好的,所以这就是总成本,这比一千五百好,对呀,呃,就因为我们可以用这个索引,好的,所以这就是整个故事,嗯,所以我们得到了一个相当低的成本,然后随着索引的使用,我们变得更低,但有趣的是。这仍然是此查询所有可能计划的一个非常小的子集,嗯,他们中的很多人都不是很好,但可能有比我描述的更好的,好的,所以嗯,本例中的高级图片,我们看过的各种计划是有很多计划,即使对于一个相对简单的查询。只有一个连接和两个只有两个关系,人们常常认为他们可以选择一个相当好的计划,如果他们只是因为他们没有更多的领域知识,或者他们

手动查询规划往往非常繁琐和技术性,你并不真正理解其中的细微差别,尤其是如果数据经常变化,嗯,你最终可能会成为一个次优的计划,机器通常更擅长列举这些选项,人们估计他们的成本,并在数据变化时跟踪数据。所以跟踪统计数据,比如说,所以我们可以看看下一个算法是如何描述优化器,查询优化器做出某些简化的假设,检查计划的合理子集,然后在计划的子集中找出,它是一个,呃,什么是,呃,那个子集中最好的计划。所以这就是我的全部,我要转交给艾尔文,除非有任何问题,我也很乐意在聊天中回答一些问题,我看到一些讨论突然出现,我为什么不在此期间交换呢,听起来不错,去争取吧,如果在B+树中有重复的替代项会发生什么。所以这就是,呃,我认为这是个有趣的问题,我们下课后再谈这个好吗?因为这与我们的,嗯,我们今天的话题,它是,这是一个,因为它更像是B+树,我们可以在下课的时候讨论,没关系,我们能把整个索引放入内存中吗。

如果我们不能容纳整个关系记忆,用下面的方式来考虑它,对呀,假设我有一百个属性关系,我正在索引这些属性中的一个,索引可以是一个量级。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/f84281b8dc53cea57672e23e92e1377e_3.png

关系大小的百分之一。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/f84281b8dc53cea57672e23e92e1377e_5.png

所以理论上你可以拟合整个关系,整并索引到内存中,即使你不能把整个关系放在记忆中,好的,我想艾伦你该走了,好的,酷,所以嗯对,所以这基本上是我们刚才讨论的延续,所以现在有趣的部分来了,所以我们一直在讨论。喜欢做职业规划的不同方法,嗯,以及您可以生成的不同计划,所以现在基本上就像,你知道吗,让我们付诸行动,那么我们如何实际利用这些查询计划呢,我们如何选择最优的,对嗯,所以这是一篇评论,对呀。所以我们已经谈过了,呃,查询优化器在输入和输出方面会做什么,所以一般来说,查询优化器只接受不同的查询计划,由关系运算符组成,这些是我们以前见过的关系代数树,也有不同的选择,呃,您可能进行的不同物理实现。所以这些基本上是不同类型的,Aditya和我之前也谈到过的不同类型的实现,这些都是你的连接算法,就像你知道的,您所有不同类型的排序,和所有其他类型的东西,下一步是什么,所以我们现在有了所有的东西。

然后我们已经讨论了如何生成,呃,可能植物的空间,所以这只是,你刚才说的,现在我们基本上需要另外两个组件,那就是我们如何估计这些工厂的成本,反过来,我们实际上已经更早地了解了成本估计。对于不同类型的关节和不同类型的关系代数实现,我们实际上还没有谈到的是估计每个关系的大小,如果它是基关系,那肯定很容易,我是说我们已经知道有多少元组,但是中间体呢,在执行一项联合行动时产生的关系是什么。比如说,好的,这就是我们要讨论的下一件事,最后是搜索算法,我们如何实际利用所有这些成本估计和其他工厂,为了选择最好的一个,对呀,所以就成本最低而言,最好的一个,所以作为一个提醒,我们要。我们将专注于挑战者的所谓系统R,该类中的优化器,嗯,很明显,随着时间的推移,许多事情都有了进步,对呀,所以这是80年代发展起来的东西,从那以后,事情确实有了进展,事实上,我们今天看到了一些改进。

这绝对是数据库中正在进行的研究领域,呃,或数据管理,呃,一般研究,所以这里有一个非常简单的查询优化器,你可以想象建造,嗯,假设我们得到一个输入查询,我们将基本上列举所有可能的计划。您可以为特定的查询提出的,对呀,所以你知道我们学习了所有这些不同的关系代数运算符的不同实现,所以只要把它们都列举出来,无论你能想象到多么正确要考虑的计划太多了,你不是已经说过,呃,你知道早些时候。我们一会儿就会明白这意味着什么,你知道在那之后,让我们说,如果我们能以某种方式列举一切,接下来就是估算这些工厂的成本,你知道的,然而,这里的问题是就像你知道的,有时很难给出准确的估计,对。因为就像你知道的,我们不知道中间尺寸是多少,将产生的关系的关系,我们无法预测缓存会做什么,我们不知道,就像你知道,呃,缓存算法,我们可以使用Right来驱逐缓冲池中的页面,例如,这样就很难估算成本了。

最后我们只挑成本最低的,就像我说的对嗯,然而,如果我们用这种天真的方法,那么问题就像,你知道的,我们真的记录了所有这些不同的计划和内存中所有不同的成本吗,我是说,如果是这样,我们可能真的会,呃。甚至没有足够的记忆来跟踪所有的植物,作为一个例子,这里有一个查询,从人们用来衡量不同数据库系统性能的基准,所以这个特别被称为tpc h,所以你知道细节在这里并不重要,但你基本上可以看到。这只是一个常规类型的联合项目,选择您现在可以使用此计划看到的查询,通过这个查询,我可以在这个屏幕上找到你,原来只有两个,两种不同的百万种方式,然后您可以使用不同的算法和不同的实现来执行这个查询。那么现在会发生什么呢当你看到这个两百万的数字,放弃吧好吧,当然是在那之前对吧,你知道猫出现的权利,你们还不够搞笑,所以猫就出现了,所以你知道,我们该怎么办,就像你知道你以为我在开玩笑,对呀。

但事实并非如此,假设你知道我有十种不同的绘图算法,我可以从,你知道一个不错的数字,对呀,考虑到在这门课上我们已经涵盖了四到五个,对呀,如果我有比如说三张不同的桌子,那么选择的数量就只有十比三了,好的。十到三分之一似乎是个小数目,如果增加到六个呢,你知道不是那么大的数字,就在六张桌子里,然后我们突然间就有了一百万,所以你知道这并不难想出,就像所有这些不同的方式,对,你基本上可以用很多方法来生成。就像你知道的巨大的规划空间,所以这通常不起作用,好的,所以我们不能只是做幼稚的事情,所以系统做对了什么,特别是,所以他们已经认识到,肯定认识到这是一个问题,所以你知道他们想出了一堆不同的把戏,嗯。在规划空间方面,所以我们基本上不打算列举所有的东西,因为这是不可能的,对呀,两百万计划,我是说你知道我们甚至不能把它放在记忆中,我是说,让它知道,就像你在列举它一样,对呀。

我是说仅仅是列举可能需要几年的时间,所以你知道我们不会那么做的,所以接下来发生的事情基本上是我们要应用启发式,你知道,也就是通常有效的技巧,但并不总是对的,那是什么意思,所以说,例如。我们只考虑所谓的左深植物,我们需要处理关节的地方,我们将看到一个例子,在短短一秒钟内,我们要避免笛卡尔积,因为笛卡尔积通常会产生,你知道的,呃,就元组数量而言,表非常大,大表意味着,你知道的,呃。需要很多页的内存来嗯,为了储存它们,就像,你知道的,让我们一般地避开他们,好的还有,我们甚至尽量不一次优化整个查询,因此,即使我们想枚举,我们不打算列举,就像你在上一张幻灯片上看到的整个计划一样,好的。所以这些基本上是不同的启发式,我们可以应用,还有更多,就像我说的对,这些东西通常在像这样的方面起作用,你知道吗,呃,呃,优化,你知道的,减少实际优化查询所需的时间,而无需。

就像我们的头撞在墙上等待整件事正确地完成,但你知道,并不总是奏效,从某种意义上说,它可能会提出一个次优的计划,或者它可能仍然无法减少空间,对呀,呃,以戏剧性的方式,我们走着瞧,然后在成本估计方面。我们不需要精确,所以说,毕竟好吧,你知道,这不是期中考试也不是期末考试,对呀,我是说,我们不是在问你们,就像你的四十二四十三页,只要我们能比较不同的方案,对,因为我们基本上只是想对它们进行排名。那我们就没事了,好的,所以所以,就像你知道的,棒球场,就像你知道的,信封背面,估计通常有效,就像你知道的,当然啦,在搜索算法方面,我们实际上要做一些叫做动态编程的事情。你可能记得也可能不记得从61年开始,好的,上一节课我们已经讲过平面图了,来自阿迪蒂亚·索,但在查询优化优化方面,我们将使用基于计划的知识的方式是,基本上说,我们将把整个查询分解成不同的查询块。

我们将一次一个地优化每个块,那是什么意思对吧,所以这基本上意味着,如果我们有与查询不相关的块,那么我们基本上只需要考虑这个块一次,因为我们可以把它压平,大家可能还记得,呃,从以前的关系代数讲座。如果我们能把一个查询弄平,我们基本上是这样做的,这样我们就没有那么多不同的嵌套块要处理,但如果我们能把它们压平,然后,从优化的角度来看,我们基本上将它们视为单独的查询块,所以这里,比如说。您可以看到这里有两个不同的查询块,对呀,所以外面有一个街区,街区里有一个,你可以看到这里的嵌套块或这里的内块正在利用,呃,来自,呃,在这种情况下,实际上甚至不是从外部块。但我们只是考虑把它们当作两个独立的街区,在优化目的方面,所以我们要单独处理每个块,对于每一个区块,我们只需要输入并考虑不同类型的植物,所以访问方法,例如,在这种情况下。

只是意味着我们想要访问每个基本表的不同方式,在本例中,在from类中,我们是在扫描索引吗,我们是在扫描堆文件吗,所以基本上这些是不同类型的呃,我们将考虑的访问方法,呃,分别为每一个。就在这两个不同的街区,然后呢,就像我说的对,如果我们有一个连接,在这种情况下,我们做得对,因为我们这里有一个相关的查询,那么我们只会生成,我们只生成一个所谓的左,在这种情况下。左深关节树的定义基本上是一个,其中右分支始终是基表,所以你在屏幕上看到的一个例子,注意在每个关节上,右手边总是一个底桌,它从来不是另一个操作员,不是联合经营者,这就是所谓的左陡坡,呃,关节树,嗯。然后给了这样的树,我们要,我们将考虑所有可能的方法来真正加入他们,以及我们所知的所有可能的方式来实现共同的权利,所以我们所知道的关于任何问题的不同联合方法,到目前为止,能不能点个赞,非常快的直觉。

为什么我们只想考虑像左深连接树,而不是把像等级制度这样的东西,是啊,是啊,例如,对呀,我是说,在这种情况下,你可以考虑的一件事是一棵看起来像这样的联合树,所以这将被称为平衡树,对呀。它不是一棵左踢踏舞树,因为一切都不是单向的,所以左边深树的一个优势,我们知道右手边永远不会,我们从来不需要等待一个元组,还记得我们讨论的迭代器模型吗,你们一直在实施你们的项目,三三,希望是对的。所以在这种情况下,你知道吗,右手边的一切,我们知道元组总是可用的,除非我们已经运行了我们的水元组,我们为此所做的一切,呃,然而,在这种情况下,如果我们没有左边的树,右边的树,就像这个案子和我画的那个。所以想象一下在顶部连接时会发生什么,所以上面的连接是对的,我们可能需要等待,就像,你知道的,在两边,就在元组真正出现之前,右手边不是底桌,例如,所以左深的树有这样的好处,好的,还说你知道。

为什么不把深树写得很好,但那是在那个意义上,基本上是任意的,好的,那是有道理的,谢谢顺便说一句谢谢,我看到有六个人开着摄像机,我是说如果你喜欢听而不喜欢睡着,你能试着打开你的相机吗,谢谢。如果你睡着了。那很好,对,我是说,我保证我们不会取笑你,嗯好吧,所以我们继续吧,所以让我们给,让我给你举个例子,对呀,所以在这节课剩下的时间里,我要用这个现在著名的或臭名昭著的,呃,你知道的,水手,呃。关系和我们以前见过很多次的保留关系,就像,你知道吗,关于这两张桌子的一些统计数据,我在这一页上给你看,所以在我们真正看这个例子之前,呃,有一堆我们需要讨论的物理性质,你马上就会明白为什么这很重要。所以我们可能关心的一件事,例如,当事情在结果中时,它们是否真的被排序了,另一件事是它们是否真的按照,你知道的,基于,让我们说,哈希函数对,你可能想看看,实际上有人能猜到为什么。

这两件事作为和查询运算符的输出可能很重要,让我们说,由于关节,是啊,是啊,这样他们就可以做,他们可以让呃,操作更快完全正确,例如,如果我们真的有一个下游运营商说,就像你知道的,凭权利订购,例如。如果接头的输出已经开始,你们一定记得,对呀,因为我们已经跑了,假设一个排序合并连接,例如,那就太好了,因为那就意味着你知道,呃,操作员的命令实际上不需要做任何事情,我是说这真的很好,同样地。如果我们也想分组,出于类似的原因,就像你知道的,这只是提醒人们可以产生这些属性的东西,右作为此输出中的A,例如,如果我们有索引扫描,然后对结果进行排序,很明显如果我们,那么结果就肯定排序了。然后以此类推对,对于联合操作者也是如此,正如我所说,就像你知道的,某些运算符或某些实现中的某些运算符实际上需要对事物进行排序,对,因为例如,在排序中浸没关节右,我们依赖于输入已经排序的事实,如果没有。

那我们就得手动分类,这将产生额外的成本,你已经经历过这些了吗,我不会喜欢,你知道,在这里详细说明,所以在这种情况下你可以看到,呃,你知道,我们这里有关节,然后你可以看到两种不同的等价植物。因为它们计算的是完全相同的东西,只是一个人有一种,另一个没有,因为在右手边,我们使用了一个基于哈希的联合算法,所以就像我说的对,系统中的,或者在我们在本课中讨论的优化器中,我们只考虑左深联合计划。就像你知道的,这两个是这两个和幻灯片上的,我们不会考虑,嗯,它肯定会限制或拉低搜索空间,对呀,因为我们正在说明或不考虑一些联合的可能性,嗯,就像我说的对,你知道有一定的优势,如果你只考虑剩下的深植物。呃虽然呃,这可能最终会返回一个次优计划,在某种意义上不一定是最有效的,但这基本上是我们愿意付出的代价,除非我们想处理两百万计划,嗯,所以这基本上是一个启发式,通常是有效的,但并不总是,所以嗯。

为了一个计划,为平面图空间,但正如我们已经谈到的,我们基本上得到了一堆不同的计划,也许使用关系代数等价,或者通过列举基于不同物理实现的不同方法,然后就像你知道的,正如我所说,我们将应用这些启发式。当我们试图产生更少的植物数量时,就像你知道的,呃,然后我们基本上要强制执行,当我们列举不同的植物时,例如,我们根本不会生成笛卡尔积,因此,你知道我们在考虑哪个运营商并不重要,所以每次我们看到一个关节。我们基本上不会考虑任何形式的笛卡尔积,所以现在让我们来谈谈成本估算,所以你们已经知道了一半的故事,就像我说的对,因为我们已经知道每个运营商的成本,对呀,所以,嗯,这是期中考试后。我敢肯定你们现在是对付IO页面的专家了,所以我们需要基本上估计每一个的成本,呃,种树,我们没有谈过的事情,然而,如何估计所有中间体的大小,就内部产生的输出而言的中间体,就像你知道的。

作为查询运算符之一的结果,是啊,是啊,就像我说的,我们已经讨论过不同类型的运营商在IO成本方面的问题,现在我们只需要估计绿色袜子的尺寸,现在,我们要正确使用的一件事是基本上利用这些信息。我们有关于输入关系来估计中间体的大小,我们还没有经营职业生涯,对呀,所以很明显我们不知道,就像知道有多大,呃,关节要去了,呃,你知道一个特定的关节可能正是,但是我们可以根据输入关系的大小来估计,对呀。我们将看看如何真正做到这一点,在系统中,他们基本上是首创的,呃,结合两者的成本估计,呃,iOS的数量,这就是我们已经知道的,就像你知道的,一些叫做CPU因子时间的东西,元组数。所以我们已经熟悉的第一个术语,对呀,所以第一个学期,第一个术语基本上就像,你知道的,相关费用,让我们说,将输入基表的页面输入到内存中,以及所有其他好的东西,第二部分,然而,是我们嗯,所以哎呀。

我们的东西我们的东西,我们刚才谈成本估计的时候,它撒了谎。所以记得当我们谈论成本的时候,我们现在基本上是说,您可以忽略与实际处理在内存中设置的元组相关的任何成本,所以现在我们不能再忽视这一部分了。所以我们基本上要在计算方面把这个因素加回来,不管我们怎么称呼它,所以基本上与处理元组次数相关的成本,我们实际需要处理的元组数,我们将在这节课中看到如何真正做到这一点,正如我所说,就像你知道的。有目录和数据库,基本上可以跟踪你知道的事情,元组大小的大小,输入的大小,每一个基本关系,等等,所以这里就像你知道的,您可能会在任何类型的数据库目录中看到一个典型的表。所以基本上我们将在每张桌子上存储两个帖子,存储每个基表的磁盘页数,以此类推,然后根据这些数字,我们基本上要估计我们产生的这些中间关系的大小,当我们试图计算关系代数查询树时,到目前为止对此有什么问题吗。

好的,所以你知道,就像我说的对,所以这些东西基本上,目录将作为,呃,作为数据库管理系统的一部分,所以我们基本上要利用这样的信息,试图估计中间关系的大小,你还记得之前的讨论吗,对呀。输入关系的大小也称为船长基数,对呀,所以你看到了,基本上出现在幻灯片上,嗯,现在有一件事,哎呀,我们要做的一件事是为了估计,就像,你知道中间的输出基本上是有选择性的概念,我们稍后会。我们就会明白为什么我们实际上谈论输入基数的乘积,嗯好吧,所以选择性基本上是与,与查询中的每个术语相关联的东西,特别是具有罕见谓词的查询,所以你知道班级选择和关节,对呀,所以所有这些东西都有谓词。所以选择性基本上是一个用来描述它们的术语,所以对于那些上过概率课的人来说,例如,它基本上是一个措施,呃,试图计算实际命中的概率,呃,对于其中一个谓词,我们基本上要用它来测量和估计结果,呃,输出表。

这么正式的说,选择性被定义为,呃,输出表的大小除以输入的大小,在书里,呃,你也,这本书还称之为还原因子,因为这总是一个介于零到一之间的数字,例如,如果我们在做像选择星这样的事情,对吧。我们基本上返回每一个元组,那么在这种情况下,活动基本上是一个权利,因为一切都很顺利,所以说,因此,输出的大小与输入的大小之间的比值正好是1,与不返回任何东西相比,它的活性为零,因为它不会输出任何东西。因此,在这种情况下,活动将为零,除以输入表的大小,意思是零,那么叉积呢,在这种情况下,在这种情况下,叉积权,它实际上不会过滤任何东西,就像你知道的,你实际上可以看到如何对叉积进行活动估计。在关节的背景下,现在,你也会听到人们谈论的事情,对呀,当他们谈论选择性时,它实际上违背了你知道的,我们称之为自然语言中的高度选择性,嗯,这实际上与我们在这里定义事物的方式完全相反。

所以别问我为什么要这样定义,所以说,例如,就像你知道的在正常的谈话中,当我们说某件事是高度选择性的对吧,这意味着很少有事情能正确地通过,比如你说你知道这份工作要求很高。意思是这基本上意味着我们不会选择很多候选人,对呀,但在这种情况下,高选择性右意味着数字真的很高,右,意思是接近一个,所以这实际上与我们在正常对话中的意思相反,意思是某物有很高的选择性或有很高的选择性。意味着就像你知道的,它只选择很少的元组,好的所以要确保你明白我们是在什么背景下谈论事情的,所以就像我说的对,每个术语都有选择性,在它的where子句中,每个选择基本上都是对的,所以在这种情况下。我们有k个不同的,这里有一个选择抱歉的选择谓词,所以你可以想象我们将有K个不同的活动,那么我们将如何估计这个查询的大小,你在屏幕底部看到的,嗯,只是基本上会是,我们可以产生的最大元组数。

这是根据塞林格的说法,对呀,在这种情况下,我们能生产的最大数量正好是,就像,你知道我们在这里的所有关系,对呀,如果只有一个关系,那就像你知道那个底座的大小一样,基于uh的关系,如果结果是一个关节。那么如果你知道可以产生的最大元组数基本上是,所有桌子尺寸的产品权利,所以不要太担心,现在你不明白,但你们在这张幻灯片上理解的是,事实上,我们正在应用所有活动的产品,我们在上一张幻灯片上看到的。所以如果我们在这里有k个不同的术语,那么整个集合活动将是所有这些的乘积,你可能会问为什么我们真的能看到,为什么会是这样,所以让我们通过一些简单的活动,呃估计,我们如何实际计算它们。所以让我们假设其中一个where类或其中一个选择谓词,说了一些你知道的,检查等于特定值的列,假设我们得到了你知道的,和钥匙对,让我们用这个呃,此处的符号,用于标记列的唯一值的数量。

很抱歉这个特殊的独特价值,嗯专栏右,所以假设我们在寻找像H这样的东西,等于二一什么的,好的,那么你认为这个特定谓词的选择性是什么,天真猜测只是唯一列数的一个对吧,所以这将是完全。所以它只是1除以唯一值的数量,对呀,就像你知道的,这基本上是,就像你知道的,这个特定的谓词可能会击中某些东西,或者它可能会返回正确的东西,让我的意思是,如果你不明白这一点。考虑一下我们实际上在寻找适龄学生的情况,正好等于二,一什么的,对呀,如果我们有喜欢,你知道,呃,这张桌子里不同年龄的人,那么就像你知道的那样,我们可能会找到一个元组,呃,符合此条件,对呀。它只是一个除以不同唯一值的数量,我们拥有的,沃伦,你有问题吗,呃,是啊,是啊,所以这不是假设我们没有副本吗,是啊,是啊,在这种情况下,就像,对于这个特殊的情况,对呀,就像,比如说,你知道他们是。

没有重复的,所以如果有重复的对吧,那么这并不是说这不完全等于概率了,是的,这是要记住的最基本的事情,但这基本上是我们使用的估计,所以这就是为什么这是持续的活动,但不是概率,因为这个原因。所以现在让我们继续吧,那么像这种其他形式的呃,选择谓词,假设选择谓词是这样的,你知道吗,嗯,水手等级等于,对不起,水手,水手ID等于喜欢,你知道吗,这些书找了一些,呃作为谓语,对呀,所以你可以想象。例如,这对关节也很方便,对,因为在关节里,这也是我们将要看到的谓词类型,在这种情况下,事实证明,扇区将是一个以上,这两列之间唯一值的最大数目,你不明白为什么这是最大的。坚持住坚持住你的想法会看到一个例子,呃,在下一张关于这个的幻灯片上,但就目前而言,这么说吧,这就像是任意决定的公式,呃的活动,这种形式的谓词,现在这个走样怎么样对吧,检查特定列是否大于或小于特定值。

所以在这种情况下,我们将定义活动,会是最大值什么的,就像你知道的,你能从那一栏中得到的最大价值,减去我们感兴趣的特定值,我们要把它除以范围,根据我们为这个特定列提供的值的总体范围,我们要加一个。因为你知道,我们不算不算,我们希望在结束和开始时都是包容的,所以再一次,对呀,这降低了概率,如果你知道我们在处理性,但我们一直在做多套,那就不再有概率了,但我们将用它作为选择性的粗略估计,在任何情况下。然后你可以想象公式是什么样的,你知道的,如果我们翻转符号说像列小于一个特定的值,呃,是啊,是啊,所以当我们谈论一列的最大值时,我们假设它是整数还是浮点数,或者像绳子一样,是呀,嗯,所以对于弦来说。你可以,你不能像用克一样比较,对呀,所以在这种情况下,这肯定是数字或类似的,你知道,至少数字看起来像对的,如果弦,我是说,那就另当别论了,如果我们说的是像一根绳子,等于某物或像某物,不知何故。

你把一个字符串转换成一个数字,然后像词汇一样比较,以图形方式,或者类似的东西,然后它也还原到这里的这种形式,是啊,是啊,Y加1右边,因为我们想包容,所以我们希望同时包含n和值的范围,对呀。包括你知道的最低值和包括最高值在内的一切,所以我们加了一个,如果我们不知道如何正确估计会发生什么,所以我们实际上要得到一些随机值,对呀,比如说十分之一,这其实就是,呃,系统是,呃优化并最终这样做。在它不知道如何再次正确估计一包的情况下,这是四五的猫对吧,你知道为什么这个十,为什么不点赞呢,你知道1除以4 2,对呀,我是说最重要的是,所以让我们一会儿再来回顾一下,但现在我想像你一样解释。为什么我们在这个公式中使用max,这是有原因的,所以我要用,呃动物的例子,就在后面,呃猫,呃类比,所以现在假设我这里有兔子,我想估计概率,左耳右耳,与右耳颜色相同的兔子的左耳,对呀,假设我有一百只兔子。

和,比如说你的左耳和右耳只有两种不同的颜色,实际上它可以有多达十种不同的颜色,所以假设耳朵的颜色是独立的,所以去年就像你知道的一样,被染成了棕色,没有效果,左耳上的任何颜色,匹配耳朵的概率是多少,对呀。所以基本上,呃的概率是多少,公式,我是公式,我把它作为幻灯片的标题,嗯,这和诱惑力基本上是一回事,对呀,如果我们在处理布景,让我们说的权利,所以这和我之前问你们的完全一样,形式谓词的活动是什么。你知道的,第一列等于第二列,对呀,好的,所以如果你还记得你的概率课,嗯,这个概率是所有可能情况的总和,对呀,两个颜色其实是一样的,这两种颜色其实是一样的,所以在这种情况下,我们把所有的C加起来是对的。那么像这样的概率是多少,你知道这两个,第一年,去年就像,你知道布朗,第二年也是棕色的,对呀,例如,那是第一个学期,然后像这样的概率是多少,你知道左耳是黑色的,然后右耳也是黑色的,对呀。

所以这可能是第二个术语,这里是C2,等等,等等,你会注意到这一点,所以这基本上是概率对的,因为,正如我所说,左边只有两种不同的颜色,所以等于c1的概率是二分之一,那么它等于C2的概率也是,对右耳。因为有十种不同的颜色,那么概率均匀分布为对的十分之一,但请注意,就像其他一切一样,抵消了,因为左耳右耳只有两种颜色,所以如果你做,在这里算算,原来这只是十分之一,也称为一除以两个数中的最大值。所以这证明了我们为什么要说话,我们在前面讨论的公式中使用了最大右,这有意义吗,和烘焙警告,当然啦,对呀,我们假设就像你知道的那样,类似的,你知道,它是,呃,这是一套正确的,因为如果是多集。那就不再等于你知道的那样,正好等于概率,所以呃,但我们只是用它作为一个粗略的估计,在任何情况下,好的,所以现在回到十分之一的数字,对呀,就像我之前说的,对呀,为什么十分之一是对的,为什么不好好拉点别的。

事实上,人们一直在叫别的东西,所以这是Postgres的一个相对较新的实现,它是一个开源的数据库管理系统,他们可以在线使用,也可以自己下载和使用,所以你可以看到他们基本上把一大堆,这里有一堆不同的数字。对呀,从像,你知道吗,零点,零五,作为喜欢新事物的默认概率,像一个,你知道三分之一,好像是为了这个,你知道这里a小于b,没什么特别的原因,我想说你的猜测基本上和我的一样好。所以就像你知道任何东西都在这里,所以你知道,如果你不喜欢他们,你也可以改变它们,然后这实际上会影响,呃,你知道优化器的性能有多好,但就像我说的对,如果有办法估计,我们确实想估计对他们。这就是为什么我们给你们上一张幻灯片上的公式,因为在某些情况下,我们确实可以计算一些有意义的东西,所以我们要这么做,如果可以的话,正如我所说,这基本上就是系统艺术所做的,但现在我们实际上更清楚了。

我是说我们可以用这些随机数,或者我们可以使用直方图,艺术没有考虑到哪个系统,但我们现在可以考虑它们,因为我们有跟踪信息的目录,那么什么是数据库世界中的直方图,在这种情况下,这里有一个直方图。它基本上是计算,它基本上是编目不同元组的数量,我们有不同范围的值,所以我们已经熟悉了直方图相等的概念,对呀,所以它的旋转基本上是等宽的,就大小而言是正确的,然后你就知道了,呃,计算特定桶中的元组数。所以这是我们已经熟悉的东西,另一种表示直方图的方法实际上是死亡,所以就术语而言,所以这基本上意味着,而不是让桶的宽度相同,我们想要每个部分的高度我们现在有相同的宽度。实际上我们可能并不希望远足的宽度完全相同,它们可能是一系列的,你知道的,小范围的值,所以在这种情况下,我基本上选择,呃,徒步旅行将高度限制在两到四点之间,所以你可以看到桶的大小现在不同了,例如第三个。

因为这里的第四个桶不再像,你知道吗,呃之间呃,它不再是,一号范围右,基本上是一个更大的范围,然后就像你知道的那样,但这里的第五桶实际上是你知道的范围,就像五点五,对呀,因为我们把高度限制在,呃。两点到四点之间,你可以摇晃它,就像你知道的某个数字一样,对呀,假设你知道两个,例如,对在这种情况下我们得到的是,呃,呃,所谓的背部纺织品,对呀,所以你记得我们有像分位数这样的东西。我们有中位数之类的东西,就直方图而言,这意味着什么,基本上这意味着我们有相等的深度,正好四个桶的直方图,例如:在一个瓷砖的情况下,因为我们基本上限制数字桶的大小是固定的,所以我们有我们,我们。我们现在基本上可以看到对应于每个桶的值的范围,对我来说,它基本上是说我们只被用来,我们把高度限制在完全相同,这样我们现在就可以判断我们是否只有两个桶,基于范围的中位数是多少。

所以我只是选择使用这些不同的直方图,告诉你们不同的统计数据我们可以保存在,在数据库目录中,但就像你知道的那样,呃,为了本课的目的,我们基本上只使用第一种直方图,被称为,用这个你们很多人已经熟悉的直方图。好的,那么我们如何使用直方图来计算活动,假设我有一百排,假设我现在想选择,呃,这个特定谓词的计算机选择性,就像你知道一些属性p会大于99,让我们说的权利,我们如何利用这口井,所以我们用直方图。假设这是一个类似的直方图,你知道吗,呃,在这种情况下,就像随机的随机的事情,呃消耗的土豆数量,每年消费,所以我们想计算选择性或估计尺寸,这个选择谓词的右边,我是说,很容易对吧,我们刚刚从直方图中读出。大于大于的一切,呃,九十九,对呀,在这种情况下,将是最右边的四个小节,然后我们把它除以我们有的总行数,所以说,假设这些蓝条加起来有五十,所以在这种情况下,主观性是第五点,所以在这种情况下。

我们没有使用我们之前讨论过的公式,而是通过读取直方图完全利用了这个空间,这里还有一个对的,所以在这种情况下我们是,这个是一个年龄直方图,因此如果我们对喜欢感兴趣,你知道吗,26岁以下。然后我们再从直方图中读取,然而,这里有一点边缘,对吧,我们希望每个人少,他不到二十六岁,但是这里的直方图,这里的旋转适用于25岁到30岁之间的人,在年龄权利或两个六方面,取决于你想怎么数。那么我们如何获得直方图的切片,我们没有专门的垃圾箱适合2岁6岁的孩子,特别是,所以我们要引用一些被称为均匀均匀性假设的东西,我们基本上要假设那件事,呃,值将在每个垃圾箱内均匀分布。也就是说如果我们有五个人年龄在2岁6到30岁之间,那我们就假设,你知道会在这些年龄中均匀分布,这显然是一个很大的假设,对呀,所以你知道,总的来说,没有人肯定不是真的,但这基本上就是我们要做的。

因为我们没有一个特定的垃圾箱适合那个特定的年龄,所以在这种情况下,我们只是假设,它将是那个垃圾箱高度的五分之一,在这种情况下,你知道,把所有东西加起来,我们得到了这样的东西,你知道吗,四点六,有道理。好的,所以现在让我们继续一些更有趣的事情,所以让我们假设现在我有一个连词,对呀,所以我得到了这个,呃,时髦,这里简单的意思是一个h,所以现在我有了两个谓词的连词,m p大于99,年龄也小于两六岁,对呀。所以我们知道个人,呃,选择性,那么我们在这里做什么,嗯,我们将在这里调用另一个假设,被称为独立,所以如果你知道概率,这是同样的假设对吧,基本上事件的独立性,这意味着像你的年龄和吃土豆是完全独立的事件。所以选择性会成倍增加,在这种情况下,所以再一次,我在这里挥挥手,字面上的权利与,这些基本上都是假设,他们,你知道吗,你肯定会发现它们不是真的,但这基本上让数学变得更容易,它使估计更容易。

所以这就是为什么,就像,你知道,在数据库系统中,我们倾向于使用它,因为没有,你知道,如果我们有大量的植物,我们真的负担不起使用真正花哨的技术来估计选择性,我还想讨论的另一件事是这个交界处。否则称为所有权利,所以在这种情况下,我们有相同的谓词,但我们用析取法将它们连接起来,所以我们已经知道了单个谓词的选择性,现在呃,如果你从概率上记得独立假设,嗯,我们知道我们知道满足第一个元组的元组数。我们知道我们知道满足第二个元组的元组数,同时满足右的谓词数,会是那个,那个呃,我们把这两件事从概率相乘,但如果我们,呃,但如果在这种情况下我们没有权利,我们有一个或者我们有一个析取。所以我们不能重复计算,我们数数,我们基本上想要像,你知道吗,大于土豆的数量,99岁以上的消费者,也小于或小于二十六岁,对嗯,如果我们把这两个数字加起来,这是什么概率,我告诉过你的。

然后我们最终会过了数那些喜欢,你知道吗,满足这两个谓词都是年轻的和二十六岁的,吃了一百多个土豆,对呀,所以我们不想高估这一点,所以这就是为什么我们基本上减去,在最后。所以你可以想象的另一件事基本上是画这种维恩图,所以这将是所有有这个的人的空间,所有的人都会满足第一个谓词,然后这将是满足第二个谓词的人的集合,中间的这个基本上是,你知道的,满足双方权利的一组人。所以既然我们不想重复计数,所以这就是为什么我们在这里基本上减去这个数字,到目前为止对此有什么问题吗,好的,所以你知道更复杂的查询怎么样,对呀,所以我们之前说的是关节,就像在联合案件中发生的事情,对呀。现在我们将根据直方图公式来估计这个活动,对呀,或者基本上基于之前提供给你们的公式,在我们谈论直方图之前,在任何一种情况下,它的工作方式都是一样的,接头选择性,在这种情况下。

让我们用uh作为下划线p来表示,假设我们已经知道了选择谓词的选择性,使用直方图法或公式法,如何实际考虑整体选择性,所以你会记得阿迪蒂亚说过这个特殊的等价性,在上一课的早些时候。我们总是可以把一个关节重写为叉积,然后是一个选择,所以在这种情况下,连接的选择性只是,这里的ps,我们之前说过的,这有道理吗,所以交叉乘积右把所有东西都通过,因此,叉积没有活动,或者活动是一个权利。我是说一切都过去了,然后选择,然后如果我们以这种形式重写它,然后我们基本上,我们现在知道,连接的选择性只是谓词的活动,在我们将叉积应用于基本表后,然后我们要估计这里关节的大小,对呀。那么它只是输入的大小,呃,两个基表大小的乘积乘以这里的选择性,或称为下划线p,呃,文森特,你有问题吗,是啊,是啊,我有一个关于前面例子中平等的问题,所以如果相等可以表示为,嗯,为什么我们不能。

为什么选择性不能在这种情况下独立?假设一个独立于唯一一列,比你大一倍,第二列,而不是做一个超过最大值的,第一列和第二列的唯一性,如果这有道理的话。所以你是在问,像直方图方法和基于公式的方法之间的区别。并且像,基于直方图的方法,为什么我们不能把独立假设应用于平等,而不是在最大单位列上做一个,嗯,我想你真的可以,只是我们用直方图来读取其中一些数字,相对于,嗯,而不是用某种公式来推导它,基本上是一回事,嗯,即使你使用了一种被破坏的方法,我是说,就像你知道的,基本上最终使用了独立性假设。让我们说,如果有一个连词,例如,对吧,它基本上只是两组活动的乘积。这是一回事,好的,是的,任何其他问题?好的,就像,你知道的,这里还有一个更复杂的情况。所以在这种情况下我们有一个选择运算符和一个连接。所以现在如果你重写,嗯,我之前和你们谈过,通过将连接重写为叉积。

通过选择,我们已经知道如何实际计算整个查询的选择性,因为我们可以将其重写为一个叉积,然后我们也可以把选择性提取出来,假设你喜欢的话,嗯,你知道的,我们正在正确地命名所有的列等等,诸如此类,对吧。所以在这种情况下,选择性就是选择性,它仅仅是选择性的乘法。关于单个谓词p和q的选择性,如果你接受独立假设,那么就像你知道的那样,我们可以预测这个查询将产生基表大小的乘积。这两项的个体选择性,挺酷的,对吧,我是说,就像你知道的,现在我们实际上不需要单独的公式来绘制图表,我是说我们只是把它们重写成交叉乘积和选择性,然后我们已经知道如何估计选择活动,所以这很整洁,我觉得。那么,像这样的东西怎么样,也许我们现在有兴趣看看这种东西,你知道吗,t点p等于t点h,对吧,这是土豆和H的例子,我们已经知道一种方法,从,呃,基于公式的方法,用直方图如何计算,嗯,这里有一个办法。

我们实际上可以从直方图中扫描所有年龄段的食物和消耗的土豆数量是否相等,从直方图中读取值数,然后计算选择性。那么这个想法是什么呢?只是为了给你直觉,对吧。所以我们基本上要通过我们拥有的每个引脚进行交互,呃,从直方图中读取值,然后当它们有像Mac一样相应的匹配值时,例如,在这种情况下,右边的绿色条对应于,比如你的土豆在40到45岁之间消耗的量,也在40到45岁之间,对吧。所以这将构成匹配,根据这个谓词,如果你不想使用基于直方图的正式方法,我们基本上遍历这些条,当它们相等时,相应的条,然后我们只数这些条的高度。得出选择性估计,对吧。所以我们基本上遍历这些条,然后我们可以分解这个,呃,公式,呃,呃,抱歉,我们可以把这个计算分解成这个公式,所以我们基本上要迭代所有的垃圾箱,或者所有的值,特别是对的。

我们要检查它们是否匹配,不管是喜欢,你知道吗,呃,消耗的土豆数量等于四十,然后年龄也等于四十岁,然后四十一个,四十二,依此类推,对吧,基本上沿着过道行进,然后你知道我们基本上要用分离来连接它们,对吧。因为这两项活动,呃,这两列对齐了,只要它们有相同的值,对吧,所以如果我们在这种情况下应用,像你知道的那样,嗯,这个计算公式,对吧,我们基本上要把它们加在一起,然后,呃,之后,如果我们应用独立性假设,我们基本上要把这些单独的活动加起来,因为它们是独立事件,对吧,这是独立性假设。那么这里的个人生产力是多少呢?比如说你知道土豆等于四十,所以如果你使用直方图的方法。对吧,发生的事情是,我们基本上要看看与40相对应的垃圾箱,呃,基本上是P的垃圾箱,本来应该是下划线,四十岁是上限的地方,然后我们要看横杆的高度除以,就像你知道的那样,呃,那个特定的宽度。

呃,你知道的,呃,范围的右边,因为就像你知道的,每个桶储存的不仅仅是40个,对吧,基本上是一个范围,你知道,四十到四十五岁,然后我们还需要把它除以n,因为这是我们有的元组的总数。所以同样的事情,对吧。如果我们想追踪年龄,基本上是我们需要应用的完全相同的公式,所以它总是会,就像,你知道的,整个垃圾桶的高度,然后我们需要把它除以垃圾箱中包含的值的范围,因为我们之前的假设是一致的。例如,垃圾箱实际上可能包括十个不同的值,所以就像你知道的,如果高度是一百,然后就像你知道的,等于40的东西的数量不会正好是100,而是100除以10,因为我们有十种不同的东西被放进同一个桶里。然后我们基本上把它除以,也只是为了让,呃,使它成为零到一之间的数字,这里基本上是我们有的元组的总数。然后如果你把它插回公式中,我们基本上只是把所有的值加起来,然后你可以得到,呃,对此的估计。

在这种特殊情况下,就像你知道的,某些列一等于列二,这基本上是另一种不使用公式计算选择性的方法,我刚才和你们谈过了,所以在本例中,我们从直方图中读取值,如果有的话,所以到目前为止我们说的。基本上是计算活动的不同方法,我是说你可能会问哪个更好,我是说,真的不太,你知道的,真的很难比较,对吧,因为就像你知道的,有时从直方图中读取值更容易,如果有的话,但如果没有,如果它们很贵,就像你知道的,呃,使用基于跟随的方法也可能很有效,在这两种基本方法之上,你需要记住的是,你知道的,我们一直假设的事情,例如,在直方图的情况下,我们一直假设均匀分布,我们已经,呃,假设这两种方法中的事件是独立的,对吧。所以在这两种情况下,我们假设你可以将活动叠加起来,如果它们通过一个连词连接,等等,然后对于连接,我们可以作为特例重写它,作为交叉积,通过选择性,你知道的。

就像我之前说的,在,在,对吧,我是说,就像你的选择性估计真的是一门黑色的艺术,嗯,你可以想出许多不同的方法来打破,就像你知道的这两种不同的方法,事实上,人们已经,事实上,他们也发展了许多不同的,呃,估计活动的复杂方法,当然,大家可以猜到,现在人们也在尝试将机器学习应用于同样的目的,嗯,所以你知道,基本上有很多不同的方式来估计活动,但在这节课中,我们只想向你们展示这里的基本方法。好的,现在让我们谈谈搜索算法。所以我们现在知道如何实际列举所有的计划,我们知道如何估计每一个计划的成本,现在让我们来谈谈我们将如何再次进行搜索。我们不会用列举所有方法,然后选择最小值的方式,对吧,我是说这行不通,因为计划的数量太大了,而且,像你知道的,和成本估计相关的成本并不是完全免费的。之前,我有两个简短的声明。

第一个是今晚将有一个项目派对,从六点半开始,所以如果你们在项目上需要帮助,请尝试,请参加,另一个是关于期中考试的视频,所以你们中的一些人,呃实际上设置了一个密码,你们为期中考试录制的视频。我想这是缩放的默认,嗯,如果你已经提供了,嗯,密码作为笔记的一部分,当你填写表格的时候,那太好了,非常感谢,如果你没有,嗯,请尝试禁用,呃,你视频里的密码,您可以在Web门户上这样做。我相信我们已经为你提供了关于中期设置广场帖子的指示,所以请试着这样做,否则我们得把你们抓到,呃,查找密码,就像你知道的,嗯,那样你可能会收到我们的来信,但如果你能禁用它,我想这对双方来说都更容易,呃。两种偏见,到目前为止对此有什么问题吗,好的,所以在剩下的十五分钟里,所以让我们看看多久,我们实际上能覆盖多少搜索算法,所以嗯,在搜索算法方面,我们基本上要把它分成两箱,我们需要担心的,第一个是案子。

第一种情况下,我们只需要处理一个表,基本上没有关节,另一种情况是我们实际上需要处理多个表,或者基本上用关节查询,嗯,我们首先要处理,呃,在以下方面的单表计划,所以这些是只有选择的。只有预测基本上只有团购,仅聚合,又没有关节了,对和呃,对于那些我们只是要去,呃,为这些查询选择不同数量的不同实现,然后我们就选一个最少的,我声称就像,你知道的,对于这些单一关系的,他们通常很容易。或者没有太多不同的方法可以执行,就像,让我们说像一个选择,嗯,所以所以,呃,这使得实际列举它们变得容易,即使我们必须,所以只是一个例子,我们怎么做对,所以让我们首先尝试估计这些单独的单一关系计划的成本。所以让我们说,我们在主键上有一个索引,最终与我们想要执行的选择相匹配,取决于我们如何,什么样的,呃,指数,我们有权利,如果我们有一棵B加树,那么成本呃,在找东西,就像你记得的那样,就像你知道的。

从上一节课开始,它只是B+树的高度,嗯,加上一个,因为我们需要锁定叶节点,然后再加一,因为我们需要把臀部页面,嗯,所以这就是我们所拥有的,然后如果我们有一个聚集索引,然后就会像,你知道的,呃,呃的页数。被索引占用的,加上r占用的页数,因为我们有一个聚集索引,然后在这种情况下,我们只需要乘以选择性,我们在期中考试前没有谈论过,对呀,但现在既然我们说的是一个选择,我们读的不是所有的书页都对。所以即使我们读了所有的页面,你知道的,输出不会和输入完全一样,所以输出的大小将是,我们需要为输入读取的页数,乘以选择性,最后,如果我们有一个非聚集索引,不同只是在改变,我们需要从输入关系中读取的页数。到我们需要从输入关系中读取的元组数,因为每个元组可能再次驻留在不同的页面上,我们乘以相同的选择因子,为了得到输出大小的估计,然后顺序扫描是一样的,只是会一样,呃,呃,你知道的,呃,关系占用的页数。

所以在本例中,假设查询类似于Select Star,那么我们就不需要乘以选择性,因为在这种情况下,活动将是一个,如果我们想做这样的事情,确保你也为这项权利收费,但这些基本上是呃,你知道的,呃。我们如何为我们估计输出关系的大小,对于不涉及关节的单一关系计划,这有道理吗,所以让我们来看一个例子,所以在这种情况下,这是一个例子,我们在水手表上有一个简单的选择,选择等级为,哦哦对。所以我不打算在这里详细说明,你可以看到右边,就像你知道的,基本上我想做的事情,当你看到的时候基本上是,如果我们有一个未聚集的索引,嗯,我们要检索的页面数,会更正确,正如我们从上一节课中学到的。与聚集索引案例相比,对呀,你知道如果我们有SI的索引,我们也可以利用这一点,所以希望所有这些基本上都是在这一点上回顾的,我只是基本上插入了前一个公式,从上一个,呃现在滑动,当然百万美元的问题就像。

我们实际上如何处理连接权,这实际上是最有趣的部分,我们如何处理,呃,这些离开了,我们的计划,你知道吗,有一点要注意的是所有这些剩下的植物,他们之间唯一不同的是,就像,你知道吗,呃,叶子的关系是什么。以及什么是访问方法,也称为,我们将对每个关节使用的联合算法是什么,所以你可以想象用下面的方法来解决这个问题,对呀,所以我们首先要找到所有的单一关系计划,就像我们要弄清楚之前一样,比如访问一个。访问B的最佳计划是什么,我们要做臀部文件扫描吗,我们要索引扫描吗,等等等等,然后我们要把树搭起来,对呀,我们要去,呃,联合呃,使用,把不同的方案结合起来,用关节,然后我们将列举不同的方法来处理。执行一个关节,然后对于每一个关系,当我们建造树的时候,我们将只保留最便宜的计划,总的来说,我们到目前为止看到的,呃,实际上有一个非常有趣的算法,我们用来。

解决这个问题的方法实际上是基于一种叫做动态规划的东西,那个那个实际上就像,你知道有根,就像呃,回到六十年代,从这本书中我相信,嗯,由罗伯特贝尔曼称为动态编程,所以呃,对于那些可能不熟悉这个概念的人来说。这并不重要,呃,别担心,所以基本上这里的想法是说,呃,最佳的,呃,我们总体上有的查询计划,它基本上将由,呃,最佳查询计划,嗯,在分计划一级,所以这是一件大事要说,对呀,我要再重复一遍。所以我们可以找到的最佳查询计划,我们现有的最佳查询计划,必须包括我们对一棵树最好的职业规划,仅由一个运算符组成,仅由两个运算符组成,如此如此如此,比如说,在关节方面,对呀。所以假设我们试图找到这三种不同关系的最佳结合点,这里,嗯,我们基本上要,呃,根据这个最优性原则,我们基本上要说,加入这三种关系的最佳计划,要么加入他们中的两个是最好的计划,a和b,比如说,然后加入C。

或者是加入A和C然后加入B的最佳计划,还是加入BNC的最佳计划,后面跟着一个,你已经可以看到这不一定会是这样,对呀,因为就像,你怎么知道,就像,你知道的,连接B和C的最佳方式。我最终成为加入他们三个的最佳计划,我是说这只是我在这里做的一个很大的假设,对呀,嗯,这不一定是真的,但这反过来对我们帮助很大,在修剪我们需要考虑的可能计划的空间方面,和卢卡斯作为一个问题,是啊,是啊。我想知道是否有一种方法可以严格地证明这一点,我是说,不管这是否包括在内,或者嗯,你的意思是,证明这个原理,或者证明这对图盒有效,呃,证明这个原理,我猜,但两者都是,我想更具体的情况是,我也会对,是啊。是啊,所以一般情况超出了该类的范围,用于查询计划的情况,我可以告诉你没有证据,因为这不是真的,对,因为就像我说的对,这里有一个很大的假设,我是说,你怎么知道,就像,你是加入A和B的最好方式。

也是加入A B和C的最好方法,我是说其实不是转弯,原来不是真的,我们马上就会看到一个例子,嗯,但就像你知道的,这里的好处,就像我说的对,它帮助我们削减了相当多的搜索空间,也使搜索算法实际上易于处理。所以请忍耐我一会儿,然后我们会去谈论,就像,当它真的坏了,好的,所以这很棒对吧,因为现在我们只需要优化每个子计划,然后我们把它们连接在一起,只是像你一样每次多抽一根,然后我们会找到全局最优的计划。那么我们要怎么做呢,特别是对联合问题的权利,顺便说一句,这实际上也是在系统R中首创的,就像我们之前说的选择性一样,所以第一关,我们要找到最好的高度图,一右,基本称为基本表,我们在扫描吗,整个基表。我们喜欢使用索引之类的东西吗,所以你们已经知道了,我们将记录我们为个人找到的最好的计划,呃,表中的关系,它有点像一个泵,对呀,在这种情况下,一张桌子在这种情况下,就像,你知道一些。

我们把这些东西都藏起来的地方,对呀,这不是真正的关系,只是一张桌子,你马上就会看到,然后就像你知道的在第二次传球时,我们现在要构建2号高度的最佳平面图,这意味着我们将其中两个基表连接起来。我们再次记录了我们迄今为止发现的最好的,然后我们也把它储存在这个单独的桌子里,我们一直在身边,然后以此类推,直到我们基本上连接所有的植物,嗯,我们需要,我们需要考虑,或者像你知道的。我们已经找到了最好的计划来加入我们需要加入的所有终端关系,然后我们就结束了,这是我之前说过的桌子,对呀,这不是亲戚,就像一个,你知道吗,一个便笺簿,顺便说一句,我们正在存储所有这些临时结果。有时候人们会,有时人们基本上把这种记忆称为对的,因为我们正在存储结果,到目前为止我们看到的最好的计划,我以后不会再去看他们了,好的,所以在这种情况下,这张桌子,um键在第一列上。

所以你可以看到我们基本上是在储存,加入基地关系的最佳方式对吧,例如,连接这两种关系的最佳方式,RNAS可能使用哈希,加入成本,就像你知道的一千对吧,一千可能是IO成本的数量,呃,用于阅读位置表,加。就像你知道的,CPU因子或选择性乘以元组数,这意味着过程是正确的,就像我之前和你说话一样,然后第二排这里,我们有办法加入R和T表,这个什么时候坏,比如说,如果我们真的也关心订购呢,我是说。我们准备谈谈为什么这将是有益的,对嗯,现在有些事情可能是有趣的,因为它可能最终会被下游使用,就像我之前说的,因为集体,因为秩序等等等等,之前就像你知道的,之前存储的便笺簿表。我们实际上并没有跟踪这些信息,所以这意味着我们用来,让我们说,就像你知道的,连接在一起rns对,在这种情况下,让我们说,在本例中使用哈希连接,它完全不会喜欢,你知道,呃,生成任何排序与。

如果我们使用合并接头,那么我们实际上都保留了排序的顺序,代价可能是更高的成本,但是因为这个最优性原则,我们假设出生,我们只是在跟踪连接两个基地关系的最佳计划,因此,我们不会保留我们本来会使用的计划。在我们会保留条例的情况下,所以我们能做到这一点的一个方法是基本上说好吧,让我们也跟踪订单属性,现在你注意到我们在桌子上展开了,所以我们不再只跟踪,就像,你知道的,只是我们连接在一起的桌子。我们也在跟踪它们是否产生了任何有趣的性质,我们以后可能会感兴趣,在这一点上,我们不知道他们是否会引起我们的兴趣,很可能我们实际上不需要对任何东西进行排序,所以说,因此,就像你知道的,我们其实不喜欢。你知道的,这个属性实际上是,呃,对我们来说毫无用处,对呀,但可能谁知道呢,所以在这一点上,因为我们不知道我们唯一能做的就是,基本上把它添加到他们到to,到桌子上,到划痕处,轻敲划痕,我们有的那张桌子。

所以这基本上意味着我们现在的桌子上有了扩展,对呀,所以你注意到,即使我们只考虑加入兰德的两个,我们现在有两排,而不是只有一个,我们有一行对应于这种情况下的最佳计划,没有生成订单的地方。然后我们有另一个与案例相对应的角色,在输出处生成了一个排序的顺序,所以你可以看到,这是有代价的,如果我们不想跟踪订单,那么我们就不需要跟踪所有这些不同的行,这意味着便签板桌子的尺寸会更小。这对我们有好处,因为我们不需要跟踪很多事情,最坏的情况就像,你知道的,我们基本上我们需要跟踪一切正确的,我们基本上保留了我们能想出的所有计划,连接两个基表,所以如果是这样的话。那么我们基本上不再使用动态编程了,我们没有记录任何结果,我们不妨回到基于枚举的基本方法,但现在我们要处理两百万计划的问题,所以再一次,对呀,天下没有免费的午餐,对不起,伙计们,所以有些东西必须向右。

所以我们要么留一张小桌子,但我们就扔掉了信息,对呀,我们没有跟踪这个有趣的财产,秩序井然,对呀,或者我们在跟踪,但代价是可能会炸毁这张桌子,到目前为止对此有什么问题吗。所以当你在做这种动态编程方法的时候,是不是假设我们已经做了尽可能多的事情,在选择方面,向下推向下投影,是呀,所以在这种情况下,我们假设我们已经修剪了,我们需要考虑的植物数量。我们不会考虑选择没有被推低的情况,例如,再次,你冒险是对的,谁知道呢,也许它不起作用,但你知道,足够好,那么这里的一般算法是什么,一般算法是对的,我们将像动态编程一样使用这个,处理所有关节的基本方法。就像你知道的,就像我说的,我们要避免笛卡尔积,对吧,这意味着我们甚至不会考虑一个计划,就像你知道的,它有一个笛卡尔积,我们如何在这种情况下实现这一点,不同的关节,嗯,我们基本上要看看所有的关节条件。

在我们需要加入它们的所有关系中,我们要正确处理它们,假设我们有N张桌子,假设我们只有,呃,你知道的,在极端情况下,一个关节条件,两个特定表之间的一个联合谓词,所以在这种情况下。我们基本上要先处理那个特定的关节,因为我们希望避免列举涉及笛卡尔积的计划,我们只需要使用笛卡尔积,因为我们和我们没有共同的条件,这意味着我们必须使用,我们要把这个,你知道的,在这两个表之间形成条件乘积。因为他们不会,我们将无法修剪元组的数量,使用联合谓词,例如,我们基本上会最后处理它们,在我们用动态规划处理完所有的关节之后,那个那个那个,那时我们可能没有其他计划了,我们,你知道的。我们必须形成笛卡尔积,我是说,我们要像这样,你知道的,假设嵌套循环作为一种机制,所以在规划方面,我们没有太多其他的选择可以使用,对呀,所以我们不需要,我们不需要担心太多,在炸毁植物的搜索空间方面。

在我们处理了所有的关节和笛卡尔积之后,然后我们基本上处理所有的自动购买和所有的团购,右和聚合作为后处理步骤,意思是我们都是,我们希望能有一个有趣的计划,对呀,所以如果是这样的话,那么我们是那太好了。因为如果我们有订单,如果我们真的有一个联合计划,会产生订单,对不起,我实际上会产生一种结果,那我们就选对了,因为这基本上意味着分类是免费的,嗯,或者我们可以类似地,如果我们没有一个有趣的,呃。带着那种特别的酸痛计划,对呀,然后我们基本上必须使用并应用一个额外的运算符,根据成本估计,你们知道这样做的成本,嗯,不幸的是对的,尽管所有这些印刷仍然是指数级的,就这个算法的复杂度而言。只是基本上我们减少了世博会x,这种情况下的指数,与基于穷举枚举的方法相比,所以这基本上是,我们用于,呃,用于查询计划的搜索,对呀,我们要先处理所有的关节,首先使用动态规划,然后我们要处理笛卡尔积。

如果我们必须跟着那个,我们将按组处理这批货。我们基本上希望,我们作为产品生成的计划之一,因为做了,嗯,做动态编程已经有了很好的属性,呃,我们可以利用,所以我们实际上不需要使用显式排序或显式哈希。所以乔在问我们是否已经覆盖了足够的内容来开始项目的一部分,是呀,所以对于项目的一部分,它基本上需要实现这个动态规划算法的某些部分,这就是我们在这节课中刚刚讨论的,在这个问题之前,我实际上是准时的。所以现在我应该让你们问我问题,如果你有,嗯,嗯,如果没有,然后你知道我们实际上会通过一个例子,下一节课的下一个,但我也会把幻灯片放在网站上,因为你们可能需要把它作为你们项目的参考,如果你有任何问题。请随时留下来询问,如果没有,然后呢,我们星期二见,祝你周末愉快,期中考试好运,我也是,如果你还有什么要做的,嗨,我有个问题,但是是的。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/f84281b8dc53cea57672e23e92e1377e_7.png

P14:第14讲 事务与并发 I - main - BV1cL411t7Fz

好的,我正在录制到云端。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a35d787499b28aec4dd3ca25f0963939_1.png

哦,我抱歉。好的,大家好,欢迎来到周二的讲座。希望你们的期中考试都已经结束,应该都在恢复中或者已经恢复了。稍后我们会在讲座中说几句关于期中的话题。那么今天呢……

计划是基本完成我们上周讨论的创建优化部分。然后我们会继续深入讨论事务这一激动人心的话题。所以我会先展示一些上周的幻灯片,如果有问题随时打断我。所以再说一次。

我们的目标基本上是给出一个查询,找出最佳的退出方式。最佳方式可以是从时间、IO、内存等多个角度来考虑。有很多不同的标准。但对于这门课,我们现在只考虑时间和IO作为最重要的因素。好的。

这里是一个清晰优化器的简单实现。我们将列举出所有的可能性,然后估算每个我们列举出来的方案的成本。然后选择那个成本最低的,无论“成本”指的是什么。正如你们看到的,我们已经面临了很多问题,对吧?当然。

最有趣或者最难的部分是我们有太多的职业计划。如果我们不想让Kat出现,对吧?那么我们如何处理这个问题呢?我们需要一种更好的方法,从所有可能的计划中选择不同的计划。还需要一种更智能的方式,来在这些计划空间中进行导航,而不需要列举出每一个具体的计划。

这就是为什么我们开始讨论选择活动,正如你可能记得上周所讲的。这里的目标是能够在中间表的大小范围内进行查询。为什么我们要根据中间表的大小来查询呢?嗯,我们之所以这样做,是因为我们需要计算每个职业操作符的成本。

正如我们所回忆的,它现在由两个不同的部分组成,对吧?

第一个部分是关于与将数据带入主内存相关的IO操作。到现在为止,你们应该已经是这方面的专家了,对吧?因为我们已经在期中考试前讲解过这部分内容。你们也有过相关问题,记得吗?所以那是第一部分。

第二个更有趣的部分是关于这个术语,对吧?

这基本上是我们之前忽略的部分,对吧?之前我们说过,对于这门课程,你们可以完全忽略处理任何元组时的实际CPU成本。在那个时候,我们只需要关心将这些元组加载到主内存的成本。至于其他的,可以暂时不管。好吧。

你们现在已经成长了,经历了第一次期中考试,对吧?

现在我们可以变得更复杂一些。我们将讨论的是,嗯,你不能完全忽视CPU。所以我们基本上会估算出在将所有数据加载到主内存之后,需要处理的元组数量,并乘以一个CPU因子。这个CPU因子基本上是一个修正因子,对吧?关于需要多少。

到目前为止,我们已经讨论了处理每个元组所需的时间,特别是它们被加载到内存后需要多长时间。到这里为止,理解吗?好的,希望这些内容都是复习,对吧?好的,太好了。但现在问题来了。我们只知道基本关系的大小,因为它们存储在磁盘上,我们可以依靠目录来告诉我们它们的大小。但我们不知道在执行每个查询时产生的中间结果的大小。

抱歉,每个查询树中的操作符。因此,现在我们需要估算中间结果的大小,以便将其代入这些公式之一。所以在这门课上,我们将假设中间表的输出大小是输入大小乘以某个选择性因子。这就是为什么我们开始讨论选择性的原因。如你所记得。

选择性基本上是一个介于零和一之间的数字。最坏的情况,输出和输入的大小完全一样。最好的情况,所有数据都被过滤或丢弃,所以我们最终什么都得不到。这是好的,对吧?当然,在执行时,可能不符合你想要的答案。哦。

这个公式适用于连接操作吗?是的,适用。那么,在连接的情况下,输入大小基本上是两个输入表的乘积。然后,选择性。在这种情况下,它只是连接谓词或多个谓词的选择性。如果涉及多个谓词。如你所记得。

我们讨论了多种估计选择性的方法。比如所谓的原始系统,或者我们自己的方法,如你所记得的那样,包含了许多不同的公式。对吧?例如在谓词的形式是某列等于某个数字或某个值时。

我们将通过每列的唯一值数量的倒数来估算活动量。如果我们在做集合操作,这基本上可以转化为概率,即从输入表中随机选择一个元组匹配特定谓词的概率。

所以,我想提醒一下,谓词在这种情况下可能是像“h等于21”的形式。那么,我们实际上命中“h等于21”的概率是多少呢?如果表中有100个不同的年龄,那么概率就是1除以100。

所以这就是系统艺术是如何做的。在某些情况下,比如你知道的,谓词必须是像某列等于另一列的形式,举个例子,可能是“水手ID等于评分”,那么他们使用的公式就是1除以最大数量。

这两列中的唯一值。我们上周在课堂上解释了这些是如何产生的。对吧,所以如果你感到困惑,我鼓励你重新回顾一下这个内容。最后,谓词的形式大致是像列大于或小于某个数值的形式,那么它将是我们所覆盖的范围除以整个范围,再加一作为分母。

对,因为我们希望包括两个“nappa”的情况。实际上,这里有个错别字,应该是将加一放在分母中,而不是后面。好的,我们还讨论了直方图方法,类似地,我们也讲解了类似的公式。所以在使用直方图的情况下。

估算像“列等于某个值”这种选择性的方式,就是查看包含我们感兴趣的值的柱状图的高度。比如说,年龄等于21岁,那么我们就看包含21岁年龄的柱子。然后将其除以实际包含在该柱子中的值的数量。

所以,如果桶里包含10个不同的年龄,那么我们就将该数字除以10,这里的数字是柱子的高度。然后如果每个桶里只有5个不同的年龄,我们就将其除以5。正如你回想的那样,这基本上是我们在这里做的所谓“均匀性假设”。

所以,正如你回想的那样,这基本上意味着我们假设同一个桶中的所有值是等概率的。所以,如果桶中有10个不同的值,那么我们就将其除以10。如果有21个值,我们就除以21。那么接下来,对吧,第一列等于第二列。我们基本上将其分解成若干个析取式,或者说“或”对吧。

一堆不同的“或”操作,然后我们会检查所有不同的可能性。我们会说,如果第一列等于某个特定值,且第二列等于相同的值,然后一直进行下去,直到我们测试完所有值。然后总体活动就会像你知道的那样,应用这里的这个公式。

我们将遍历两个列可能匹配的所有可能值。然后,最后对于大于某个特定值的情况,我们基本上会将符合此标准的所有条形的高度相加,并除以表中总行数。

所以如果你不记得我们是如何推导这些公式的,你可以直接看一个直方图,然后说服自己这确实是正确的。对吧?你可能会问,哪一种更准确?好吧,在很多情况下,直方图方法会更准确。

因为我们基本上是在保持每个范围的单独值的桶,而不是仅仅假设一切都是均匀分布的。要理解这一点,只需看一下这里的第一个公式,右侧的列等于一个特定的值。在系统的情况下,我们将广泛假设一切都是均匀分布的。

因此,它只是表中项数的倒数,而如果我们有一个直方图,我们实际上可以通过查看对应于目标值的条形图来稍微提高准确性。

这对大家有意义吗?但你显然也可以批评直方图方法,认为可能有更准确的方法。因为例如,均匀假设其实只是一个假设,没有什么表明在一个包含10个不同年龄的桶中,所有值都是均匀分布的。

一切必须是均匀分布的。你肯定可以找到这样的一些例子。正如我们上周讨论的,这确实是一个正在进行中的活跃研究领域。而且,为了设置平台部分,请参加相关部分,我们将基本展示如何处理那些列实际上包含浮动点的情况。到目前为止,我们在讲座中使用的所有示例都假设我们的列包含整数值。

如果你想了解我们在处理浮动点数时会发生什么,请去查看该部分。但这些并不是唯一的谓词形式,对吧?我的意思是,这些是基本的形式,因此在我们了解了基本形式之后,我们现在可以推导出更复杂的形式,通过找出它们的活动。

所以,你知道,联结形式或者内容需要谓词的形式,对吧?所以如果我们有像P1和P2这样的形式,那么我们就可以认为选择性将只是单个谓词的选择性。

在析取(or)或析取(or)情况下,我们则认为它是两个数值的加法,减去它们的乘积。正如你在概率课中学到的那样,最后一项如果两个事件完全不重叠时基本上为零。

比如说,如果我们查询年龄大于60岁,或者说年龄小于21岁,那么这两者发生的机会是零,因此第二项也是零。所以我们就将两者的选择性加在一起。最后,如果说我提到“非P1”这种情况,那么选择性就是1减去正向选择性。

所有这一切基本上源自所谓的独立假设。我们假设这些谓词是完全独立发生的。所以并不是说你可以通过看某人的住址来预测他们的优雅程度,显然这并不总是成立。虽然如此,为了本课程的目的,也为了让我们的生活更轻松理智,我们还是假设这些条件,简化问题。

目前为止有任何问题吗?是的,关于选择性的问题,我想说的就是这些。现在我们理解了选择性,你基本上可以回到前一页的公式,来推算中间表的大小。

然后,计算查询树中每个查询操作符的个别成本。到目前为止都还不错,但是现在我们仍然面临一个问题:如何选择计划,并快速找出最低成本的计划?因为我们无法枚举每一个可能的计划,计算它们的成本,再看看哪个最便宜。

所以,这就是我们提到的最优性原则,也就是动态规划。正如凯特上周在聊天中说的那样,这与编程并没有太大关系,我鼓励你查找这个概念的真实含义,或者它的名字是怎么来的。

如果你感兴趣的话。对于我们的目的来说,目标基本上是理解我们在这里做了一个重要假设,即最优职业规划(成本最低的规划)由子查询的最低成本构成。换句话说,最好将三张表(A、B、C)连接的方式,就是先选择连接A和B的最佳方案,再接着连接C。

或者,先选择连接A和C的最佳方案,再连接B,以此类推。如果你需要一个现实生活中的类比,可以想象交通情况。假设你想从伯克利开车去圣荷西。最好的或最快的方式不一定是从伯克利到弗里蒙特的最佳路线。

对吧,或者最快的方式是什么?它可能是的,也不一定是,或许你实际上需要走一条更长的路去弗里蒙特,然后再去像80号公路这样,或者说不需要走到南方。基本上这意味着,去圣何塞的最佳或最快路线并不等同于从某个中途地点出发的最佳路线。

但再次强调,这里做某些事情是正确的,意思是没有证据证明在查询增加的情况下这绝对不成立,就像在交通情况中一样。但是对于我们的目的来说,这将大大减少枚举和规划枚举的成本。

这基本上就是我们在这里尝试实现的目标。它在系统中如何工作呢?如我所说,我们基本上假设最佳计划由最佳子计划组成。因此,我们应用这个原则的方式是首先尝试找出最佳的高度计划。换句话说,基础表。访问它们的最佳方式是什么?我们是否使用了索引?

我们是使用全表扫描还是在做其他的操作?然后,在弄清楚这些之后,接下来我们要做的就是找出最佳的高度计划,它由基础表和其他内容组成。接着我们可能会有联接操作,或者像选择操作符等,等等,如果没有联接的话。然后我们基本上会以这种方式构建查询计划树。

假设我们迄今为止找到的最佳计划由最佳子操作符的计划组成。那么我们在周四讲座中展示的插图几乎是这样的。所以我们需要一种方式来跟踪所有找到的最佳计划的表格。我们基本上会通过使用一个临时表来跟踪它们。

所以这里基本上我展示了两种将两种不同关系结合的方法。在这种情况下是联接。所以在我们的最佳方法中,迄今为止我们发现的最佳方法是使用哈希联接,例如在一定成本下。然后连接这些表格,可能我们实际上想使用合并联接。例如,假设我们只是跟踪这个表格,如果涉及更多不同的关系,我们只需向这个临时表格中添加更多条目,记录下最好的一个。

我们周四也讨论了一个问题,就是做这些联接时可能作为副作用产生的属性会怎样。而我们现在已经知道的一件事是,关于联接的这些高级方法的实现就是排序问题。

所以你会记得,这些连接操作实际上会生成一些副作用,作为副产品。它们实际上会将两个表的结果进行排序,因此我们可能在后续过程中利用这一点。假设在查询中涉及到一个排序按钮。但是,我们会遇到一个困境,因为在之前的草稿查询中,使用的那个草稿表格存储了我们目前为止找到的所有查询计划。

我们实际上并没有记录所有这些有趣的属性。那么,这里的解决方案是什么?好吧,我的意思是直接增大表格。所以我们现在可以再添加一列,对吧?现在你会注意到这里有两列,第一列对应的是涉及的表。

然后第二列对应的是任何所谓的有趣属性或由于执行计划而生成的有趣排序。所以你会注意到,如果我们在rs之间运行一个排序合并连接操作,那么作为副产品,r.as.b将会被排序。

也许是因为在这种情况下,连接操作涉及到这两个属性。那么,这里有一些权衡对吧?因为现在我们添加了第二列,所以我们现在在使用这个动态规划算法时会产生更高的成本。

你知道的,我们可以跟踪其他类型的有趣排序,不仅仅是排序。这样做基本上会引入另一个列,这意味着我们实际上在增大这个草稿表的大小。对吧?所以这是权衡。我们不一定非得像你知道的那样,去跟踪排序,但这样一来我们可能就会失去找到一个可以为我们做排序的计划的机会。

对,因为在这种情况下,例如,如果我忽略了排序,那么我就会去掉这里的第二行。对吧?因为第一行在进行连接时的成本比第二行低。所以如果我们后来在查询中遇到一个order by,我们就会陷入困境,因为我们唯一能做的就是显式地对输出关系进行排序。

与使用副产品相比,你知道的,正如你所知道的,我之前已经提到过了对吧?这是又一个例子,说明了什么原则?没有免费的午餐。是的,没错,确实没有免费的午餐,不幸的是。所以这绝对是一个权衡。这里的最坏情况是什么?

这里的最坏情况是我们没有丢弃任何计划,我们只是跟踪我们枚举的每一个计划。所以最终我们就得到了之前所说的穷举枚举算法,我们没有使用任何动态规划。所以这是最终的权衡。你会尝试安慰自己。我就拿出一张可爱的彩虹玉米图片给你享受,同时我回答她的问题。是的。

所以我只是想知道,只有在存在group byorder by时我们才考虑有趣的顺序,还是我们在其他情况下也会考虑有趣的顺序?是的,像你知道的,你当然也可以考虑其他类型的有趣顺序,对吧?所以它不仅限于排序。

所以这实际上就是使用它,因为它是非常明确的,并且与任何类型的排序都相关,如果你的查询中有order by,那么这绝对会非常有用。另一个你可能会考虑的有趣属性是,数据是否被哈希处理。

所以这也是哈希连接的副产品。我们为什么需要它呢?也许最终有某个唯一的东西。如果查询的结尾有一个唯一的条件,那么我们基本上就需要做去重操作。如果我们已经拥有了所有的数据,那么这就非常容易了。

是的,这也是你可能需要跟踪的另一件事。还有其他问题吗?好的,那么让我们通过一个明确的示例来阐明这个观点,这里是一个涉及我们现在著名的水手、预备队员和图书表的查询。所以我将展示几种实际访问基本表的方法。所以我们有B树索引。我们也可以通过读取所有内容来访问数据。

所以在动态规划算法的第一次遍历中,正如我所说,我们将尝试找出访问这些基本关系的最佳方式。好的,那么对于水手和预备队员,我们可以做文件扫描,也可以使用这些B树之一,对吧?所以在这一点上其实挺无聊的,我的意思是,基本上就是一直跟踪到目前为止访问基本表的最佳方式。

所以在动态规划算法的第一次遍历之后,我们能找到的最佳计划可能看起来是这样的。所以我们可能得出结论,对于水手来说,实际上有两种方式可以做:要么是文件扫描,要么是使用B树。

所以我们可以采用一种新的访问方式,然后你知道的,我在这里省略了实际的成本计算。所以这基本上会是一个数字。但请注意,我们已经开始跟踪有趣的顺序了,这是通过使用这些不同的访问方法后可以发现的。

那么对于第二次遍历,我们要做什么呢?我们现在将枚举不同的连接方式,因为在这种情况下,我认为你们可能已经看到实际上涉及到连接操作。所以我们会考虑涉及的不同表的对,然后尝试进行枚举,找出给定任意两张表时,执行连接的最佳方式。我刚刚列出了这里幻灯片上看到的这些不同的方法。

例如,您可以考虑通过使用预定表作为外部关系,书籍表作为内部关系来进行嵌套循环连接,依此类推。如果您愿意,您也可以应用您最喜欢的连接算法。所以像是这样,我不会在这里穷举所有的方式。

但是目标基本上是只保留所有方案中最便宜的一个,针对任何给定的表对。除非它们能够生成有趣的排序,举个例子。所以在这种情况下,例如你会注意到,书籍表和预定表之间连接的最佳方式是使用排序合并连接。显然,我在这里编造所有的内容,这里没有实际的IO数字,但假设就是这样。

所以我们,做这件事的副作用是,我们也可以获得连接列被排序作为输出的好处。假设有另外一条规则,比如书籍和预定表。如果我们不在乎排序顺序,那么可能有另外一个最佳计划可以使用,也许是使用“连接排序”之类的操作。所以基本上这个过程会一直继续下去,直到第三次遍历及以后,我们基本上会尝试使用前两次计划中的最优方案。

然后我们执行那些涉及连接两张表的计划。接下来,你会进行下一步,弄清楚如何将下一个连接添加进去,并且只保留最小成本的一个。然后,在我们枚举所有连接计划并找出最佳方案后,我们将继续添加任何后续需要执行的操作。

由于在这种情况下有一个分组操作,所以我们需要弄清楚事情是否已经被分组,如果已经分组,那么我们就完成了。如果没有,那么我们需要进行显式的分组操作,例如使用基于哈希的机制。聚合操作也是如此。最后,我们只需要选择最佳的计划,我喜欢所有的一切。就是这样。对此例子有任何问题吗?我们都是专家,对吧?

既然你们已经在做第三个项目,且希望已经完成了优化器的实现。那么,简而言之,为什么你要了解优化器的工作原理呢?首先,当然有一个实际的好处,就是你能完成第三个项目。

但是不仅仅是这门课,实际上你们也可以尝试影响下一个遇到的优化器。理解优化器的人,也会理解如何编写查询来避免生成糟糕的计划。所以下次当你听到有人抱怨,哦,我写了这个查询,然后结果表现很差。你可能首先要问的是,你知道它选择了什么计划吗?

为什么优化器会选择它用来执行的计划。我的意思是,现在你已经理解了优化器在做什么时的怪癖和不足,你可能可以尝试重写查询,通过智能地导航搜索空间来驱动优化器,使其最终生成更好的计划,甚至是最优的计划。所以这是我目前想说的关于奇异优化的内容。

然后我们实际上会重新探讨在并行数据库环境下的奇异优化问题。也就是如果我们尝试在多个数据库之间同时运行查询,会发生什么,如何在学期末的奇异优化过程中使得这种情况变得更加复杂。

在我准备下一组幻灯片之前,我会回答你们关于奇异优化的任何问题。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a35d787499b28aec4dd3ca25f0963939_3.png

到目前为止有任何问题吗?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a35d787499b28aec4dd3ca25f0963939_5.png

好的,一切都交给你了。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a35d787499b28aec4dd3ca25f0963939_7.png

哈尔文,你想回答伊桑的问题还是你来回答?好的,所以我们必须考虑外部所有可能的传递选项,是的。所以在那时,我们就不会再拆解过去的两个计划,也不会拆解成个体关系。我们基本上会把两路连接的输出视作外部。例如,如果你理解的话。是的,过去的两个计划已经是动态构建的,所以我们已经有了。

所以这就是我们使用创建成本估算的原因。我们会估算来自过去两个计划的输出的成本和大小,然后用这个来选择与过去第三个计划结合的那个。好的,好的,那么我们从公告开始吧。

所以你们可能已经注意到的第一件事是期中成绩已经出来了。如果你没注意到,期中成绩的中位数大约是50%。所以,我可以说这是一个很难的考试,绝不是简单的考试,而是很难的考试,这也是为什么中位数大约在50%左右。但是放心,这只是你成绩的一个组成部分。

如果你考得不好,很可能班上其他同学也有类似的情况,所以这并不会不公平地影响你的成绩。我想要消除大家的这些担忧。而且还有一个俱乐部政策,允许我们选择你表现较好的期中考试成绩,这样我们能帮你获得更好的成绩。

好吧,所以,我认为我们从这次期中考试中学到了很多,我们会尽力把所有反馈考虑进去,用于下次期中考试。但我也想说,嘿,我的意思是,这次期中考试算是适中的难度,但我觉得你们很多人做得很好,所以要为此感到高兴。我要说的另一件事是,即使期中考试的中位数大约是50%,对于一些没有上过更高难度课程的同学来说,可能会觉得你只得了50%是很震惊的。

事实证明,这对于186课程来说并不算异常,甚至在上学期,两个期中考试中有一个的中位数就低于50%。我认为这对于期中考试的中位数来说是很典型的,可能在某些情况下会稍微高一些。

所以,确实是我们需要努力的方向,确保我们设定一个公平的考试,能够很好地测试材料内容。如果你有任何关于我们如何改进期中考试的反馈,请不要犹豫,通过调查问卷告诉我们,调查的目的就是让我们了解你的想法。我们的目标是不要让你感到压力,对吧,我是说,确实。

中位数大约在50%左右,但这并不意味着我们想要用棍子打你,然后强迫你感觉很好,对吧,这不是我们的意图。我们希望你学习材料,没错,我们问了很难的问题,但我们并不想因为你不知道某些内容就惩罚你。我们希望你能学到东西并且做得好。

所以,如果有任何方法能让我们更好地帮助你,请告诉我们。因此,出于灵活应对的精神,我们延长了项目三第二部分的截止日期,考虑到有很多人提出了这个请求,我们上周五还增加了一个延迟提交日。当我们意识到有很多学生仍在调试第一部分时。

同样地,我们正在尝试一些与办公室相关的变化,我们正在尽最大努力确保你的调试问题能够尽快得到解答,这样你就不必等上好几个小时了。我们也在帮助其他时区的学生,如果你位于亚洲时区的话,例如。

你登录后,排队已经很长,根本得不到帮助。所以我们意识到这是一个问题,我们正在积极尝试解决这类问题。当然,我们也欢迎反馈,我们会尝试各种方法,看看是否有效。比如我们正在尝试清理办公室排队的情况,以避免出现长时间的积压。所以我们会试试看。如果你们有其他建议,欢迎在期中调查中提出。

我知道期中调查很长,确实非常长,很多页面,但这正是因为我们想要从你们这里得到非常高分辨率的反馈,了解课堂的各个方面,哪些做得好,哪些做得不好,这样我们才能真正适应并加以改进。

我们尽力而为,支持你们。关于行政事务有什么问题吗?是的。我想要回应一下T.S.的评论,关于你们可能会被期中考试压垮,实际上已经快到了。我们在设计下一个期中考试、期末考试时,也会考虑到这一点,还会考虑到如何评价全班同学。所以,大家都知道这是一个疯狂的学期,这是一个疯狂的时刻,对吧?我现在也不明白为什么我盯着屏幕。

我现在看到的是一堆 Zoom 窗口,而不是像你们那样面对面。所以对于核心工作人员来说,这也同样疯狂,就像你们一样疯狂。所以,我们只好边走边摸索,因为这些问题其实是没人遇到过的,不幸的是。所以并不是我们有某种神奇的办法能够解决所有问题,我们也希望有这样的办法。所以,像时区问题,办公时间的问题,都是这样的。

我们实际上进行了广泛的讨论,工作人员内部探讨了很多,试图找出最佳方案,但最终仍然没能得出最好的解决方案。所以我们现在能做的最好办法就是尝试不同的方式,然后让你们告诉我们哪些有效,哪些无效。

所以,是的,基本上任何事情都可以尝试。如果你们愿意提出,我们也愿意尝试,如果有足够的支持的话。是的,这就是我想说的。谢谢大家。我想,唯一能做的就是尽量让讲座变得有趣一些,让大家更投入。

确保你们真正得到的是一场享受,而不是像其他那些只看回放或者根本没来的同学错失了机会。我能说的就这些了。是的,也许我们应该从一些 TikTok 视频开始。好的。

其实我觉得不妨想一个主意,如果你们有想法可以分享,也许我们可以有一个匿名链接供你们提交,然后选出最好的一个,之后我们休息一下。你知道,在每节课之间休息两分钟,看点有趣的东西,我是说是的。对。

所以我还想强调一下,我们的目标是不要让你们感到压力,如果有让你们感到压力的事情,告诉我们,对吧?我们会尽力帮助你们。好的,好吧。是的,我们肯定要为视频做点TikTok内容。

你们不知道,也许已经有了。也许已经有了。好吧,好的。那么我们稍微转换一下话题,我们要讨论事务了。所以从某种意义上来说,这个话题与我们之前讨论的内容相当不同,从某种程度上它不需要那么多的背景知识,这很好。

如果你们对查询优化感到有压力,查询优化是一个相当复杂的话题,对吧?意思是有选择性估计、操作符、以及要弄清楚连接计划树是什么样子的。所以,是的,确实有点令人不知所措,需要一段时间才能习惯。所以我认为,你们通过动手构建查询优化器,最后将成为这方面的真正专家。

好消息是,意味着你可以将其放在一旁,现在专注于一个新主题,事务的内容是新的。它与我们迄今为止讨论的其他内容非常不同。好吧,令人兴奋的消息是我们完成了数据库课程。虽然不完全是,我们已经完成了迄今为止我们谈到的所有数据库堆栈的内容,对吧?我们从SQL开始,讨论了空间管理、缓存管理、文件和索引。

我们讨论了操作符、查询处理和优化,对吧?所以我们确实完成了整个堆栈。这个堆栈的缺点是它的实用性有限,对吧?不能很好地支持多个用户访问,而且对于故障的可靠性也不高,我们讨论过我所说的故障类型。

所以这就是今天讲座的重点,如何以正确、无错误和无故障的方式支持多个用户与数据库进行交互。所有这些都是事务管理器的职责,事务管理器在某种意义上位于数据库系统堆栈的一侧,负责确保数据的各种正确性和安全性。

所以,事务管理器帮助处理并发访问,确保多个用户能够与数据库交互,并确保数据的可靠性。它确保数据能够长期保持不变,这里有两个组件,锁管理器和日志与恢复组件,锁管理器帮助处理并发访问,而日志与恢复帮助保证可靠性。

所以稍微回顾一下,几乎所有维护某种状态作为数据的服务,今天都是在某个数据库系统上运行的应用程序。比如,你的社交媒体应用、旅行预订应用,虽然现在可能很多人都不再使用这些了。还有购买产品的应用、银行应用,甚至是TikTok,特别是tin类内容或视频,以及记录相关的点击数据。

我甚至不知道这个缩写是什么。抱歉,稍等,我得想一下,是tin什么的?好的,记录点击和点赞,所有这些都需要通过某个数据库系统来完成,实际上它是一个运行在数据库系统上的应用程序。

好的,那么我们希望数据库系统在这些应用程序中的角色是什么呢?显然,我们希望它能够支持单一的查询和更新。这是你在构建这些内部查询优化器时试图优化的内容。

除此之外,这些实际的应用程序还会生成多个SQL语句,这些语句是由用户行为引发的。比如说,你可能想把钱从支票账户转到储蓄账户。这是两个独立的SQL语句,但在某种意义上,它们是一个单元,需要一起在数据库系统中执行。同时,很多用户会在同一时间使用应用程序,也就是说在任何给定的时刻,可能会有成千上万的用户,甚至数十万用户同时发起请求。

对。所以你需要处理这些访问,并以一种方式确保这些用户的查询不会相互干扰。所以我们今天要讨论的内容分为两部分,第一部分是并发控制,并发控制的核心就是确保在多个用户同时访问和修改数据的情况下,能够正确且快速地访问数据。

所以这通过交错处理来完成,并不是用户发起请求时,一切都按顺序进行,而是内部采用交错方式进行。但对于每个用户来说,感觉上是没有干扰的。所以如果我在查找我的银行账户信息,感觉就像我在独立进行,而没有其他人干扰我的交易。如果我把钱从支票账户转到储蓄账户,我也是在没有他人干扰的情况下进行的。

所以恢复组件是为了确保数据库的故障恢复。好的,实际上有许多不同类型的故障,可能是在应用程序层面,可能是在数据库系统层面,也可能是电源故障或介质故障,所有这些都是不同类型的故障。

我们希望我们的数据库系统对这些故障具有弹性,这也是恢复组件的重点。因此,从某种意义上讲,这是在确保关键数据的存储保证和持久性保证。对吧,对于许多以数据盈利的应用程序来说,它们的数据是绝对至关重要的。而且,根本上来说,我们确保的这两种属性或提供的保证,都是为了减轻程序员的负担。

对吧,系统提供了这些保证,而这些保证反过来使得应用开发人员和程序员可以更加轻松地进行开发。对吧,他们可以想,“我知道数据库系统会为我处理这个问题。”例如,它可以处理多个用户访问,能够处理容错和可靠性问题。所以,它为我处理所有这些问题,作为应用开发人员,我在开发应用时不需要担心这些问题。

所以从可用性角度来看,这非常强大。那么,为什么我们要启用并发执行呢?实际上有两个独立的原因。首先,从并发执行的角度来看,它是为了确保系统中同时运行的多个查询。正如我所说,有两种类型的优势,第一个优势与吞吐量有关。

这通常以每秒查询数来衡量。因此,整体来说,我们希望增加处理器和磁盘的利用率。例如,如果你使用的是单核机器,大多数人并非如此,但如果你使用的是单核机器,一个查询可以通过CPU进行一些计算,而另一个查询则可以进行读写磁盘操作。所以,你可以使用多核机器,这在现在已经非常常见了。

你希望通过增加处理器的数量来扩展吞吐量,而不是按顺序处理查询。对吧,所以下你希望能够并行处理任务。好的,这就是我们想要提高吞吐量的一个原因。另一个我们想要的目标是降低延迟。对吧,这基本上是指每个查询的响应时间。

通过确保多个查询可以同时运行,基本上可以确保你不会在执行查询之前等待任何一个查询完成。对吧,一个查询的延迟可能不依赖于其他无关查询完成才能开始处理。

对,举个例子,像查询可能不会受到时间较长的查询的瓶颈限制。对,举个例子。我刚刚在查看我的银行账户。这个操作可以与一个更耗费资源的查询并行进行,后者生成的是跨所有账户保存的总金额报告。对,或者至少这将是一个切入点。对,所以你希望通过并行处理来实现。

并发处理使你能够减少延迟并增加吞吐量。所以这两者都是非常强大的优势。所以这两者都很重要。那么我们从讨论为什么管理并发访问很重要开始吧。假设我正试图把我的预算从广告类别或市场类别转移到库存和销售。

好的,那么我可以通过这三个 SQL 查询来完成。我从广告类别中扣除了一些钱。我把它添加到库存类别中,然后再添加 300 到薪资类别。好的。那么假设我想在网站上运行这个查询,或者在另一个应用程序中运行。

另一个客户请求运行这个查询。现在,如果这个查询在查询序列之前或之后执行,那么我们没问题,对吧?因为资金是保存的。你得到了预算的正确视图。对。但是,如果你在这些操作之间或其中执行查询,那么你将得到一个不正确的视图,因为你处于处理中。

对,你从广告部分扣除了资金,但并没有把它添加到薪资部分的库存中。所以如果你。如果这个查询在一系列操作的中间执行,你将得到不正确的响应。所以在这些情况下,资金没有得到保存。

这并不理想。所以这里出现了两个问题。第一个问题是这些操作的执行顺序以及它们的交错顺序对正确性至关重要。好的。第二个问题是,用户需要提供一个从应用程序角度来看成功的可接受规格。对,例如,右边的查询是否可以在这些更新语句之间查看预算。

对,在预算中,资金并没有得到保存。所以这些是我们需要通过事务的概念来解决的两个问题。好的,哎呀。好吧。我丢失了。停止共享我的屏幕。给我一秒钟。好的,那么我们停一下。讨论一下可能发生的各种问题。那么。

之前的问题可能是语义上的问题。但现在我们可以讨论如果没有人或系统来调解这些并发访问,可能会出现的更严重的问题。所以让我们从讨论第一个问题开始。在这里,你有一个用户。

这个用户基本上是从这个产品关系中获取所有产品。那些价格小于或等于0.99的,它们将被插入到这个美元产品关系中,也就是说将所有价格低于1美元的产品放入一个单独的关系中。随后,它们会从原始产品关系中删除这些产品。

所以,他们从某种意义上将这些产品从原始的产品关系中移到这个名为“美元产品”的新关系中。现在,像我们之前的例子一样,假设有另一个用户正在执行两个查询,一个是从产品中计算星级。

然后从美元产品中计算星级。所以,如果这个问题。这个用户的查询被执行的顺序是其中一个在用户想要的查询开始之前执行,另一个则在用户想要的第二个查询结束之后执行。然后用户就会读取到内部数据不一致的内容。

这两个查询读取的数据彼此之间并不一致。所以在这个特定的例子中,产品被重复计算了。对,计算了两次,一次是原始产品的计算。所以,这是一个不一致读取的实例。也就是说,读取不一致,因为如果你读取了相同的信息两次,却得到两个不同的结果。所以从语义角度来看,这是不好的。那么,我们来谈谈另一种类型的问题。

好的,在这里我有一个用户1,正在通过降低“酷玩具”产品的价格10来进行更新。我不。好的,另一个用户2则在另一端通过将价格乘以0.6来提高或降低“酷玩具”的价格。

那么,如果这两个用户都读取了价格,然后独立地进行了更改,会发生什么呢?对,所以他们都读取了原始价格P1。然后,用户1进行了更改,用户2也独立地进行了更改。数据库系统中会记录其中一个更改,而另一个更改则丢失。

因为他们最终都读取了相同的价格值。对,这是一个丢失更新的情况。也就是说,这两个更新中的一个丢失了,而不是正确执行查询的结果,在这种情况下你可能希望用户1的更改完成后再应用用户2的更改。

或者是先应用用户2的更改,再应用用户1的更改。对,在这个特定的例子中,这两个更新中的一个丢失了。所以这是一个丢失更新的例子。对,所以这两个用户中的一个,他们的查询执行正确,但他们的更改并未反映到数据库中。

这是另一个问题。对吧?假设我在用户一的更新语句中打了个错字。用户一基本上说:“嘿,我要通过更改账户中的金额来设置我的账户信息。”他们将账户金额更改为1000万美元。对吧?用户二查看同一个账户时,看到的金额也是1000万美元。

然后他继续进行了一系列购买。对吧?他想,“等一下,这个账户里有1000万美元。我现在可以去买我那百万美元的房子,买游艇,甚至买一个小岛。所以我可以做这一切,因为我在用户一的更新中看到了这些信息。”然而。

用户一意识到这并不是他们实际想要的金额,于是他们中止了查询。对吧?这个查询要么被系统中止,要么被用户中止。于是,用户二最终使用了一个在数据库中并未反映的错误数据。这是所谓的“脏读”。对吧?那就是一个。

这里提到的问题被称为“脏读”。用户二最终执行了一个脏读,读取了一个实际上并未持久化到数据库中的数据。好吧?这些都是不同用户之间的访问和更改数据库时可能出现的潜在问题。如果你有不正确的用户交互,可能会发生这些问题。到目前为止有任何问题吗?好的。

让我们来谈谈事务的概念。对吧?事务是我们之前提到的那个问题的解决方案。事务有两个方面,一个是面向用户的概念,用户如何看待事务,另一个是系统实现技术的一组方法。

那我们的数据库系统是如何向用户提供事务这一概念的呢?事务是数据库系统中极为重要的组成部分,对于大多数应用程序来说至关重要。如果你的数据,如果你的应用程序计划使用多个用户,那么你最好具备事务支持的概念。所以,顺便说一下。

多年来,数据库系统曾多次获得图灵奖。我想指出,其中一项奖项颁给了吉姆·格雷。四位获奖者是查尔斯·巴克曼、埃德加·卡德、泰德·卡德、吉姆·格雷以及迈克·斯通布雷克。对吧?迈克·斯通布雷克以其在伯克利大学的众多贡献而闻名,他的大部分职业生涯也都在那里度过。

吉姆·格雷获得了图灵奖。如果我没记错的话,他是伯克利ECS的第一位博士生。因此,吉姆·格雷因事务处理获得了图灵奖。这个话题如此重要,以至于它是数据库系统中四个获得图灵奖的主题之一。那么,事务的概念是什么?对吧?事务是由用户指定的多个操作的序列,目的是作为一个原子单元执行。所以,它意味着在某种意义上,所有操作要么全部执行,要么完全不执行。而事务的所有效果,所有这些操作。

它们的效果必须反映在数据库中,或者没有效果的部分必须反映在数据库中。不能半途而废。所以,应用程序的视图,或者你如何在SQL中表达这一点,你会使用某种形式的“开始事务”语句。

你会发出一堆SQL查询,然后你会说“结束事务”,那时你就结束了一个事务,对吧?所以,这是一个单元。它作为原子操作单元在数据库上执行。这是数据库系统提供的保证。所以,这里有一个例子,如果你正在进行账户之间的资金转账,就像我们之前看到的例子。那两条查询,一条是从一个账户删除,一条是添加到另一个账户,这两条会作为一个单元一起执行。

在查询之前和之后添加“开始事务”和“事务结束”的语句。同样,我们希望一起预定航班、酒店和租车,像Expedia一样,这也会是“开始事务、预定航班、预定酒店、预定租车”和“事务结束”。对吧?所以,所有这些操作都会作为一个原子单元被组合在一起。所以。

我们将使用什么样的事务模型呢?事务,通常缩写为TXN或X Act。所以,如果你看到这些缩写中的任何一个,知道它指的是事务。事务的概念,我们将通过对程序试图做的事情或程序试图完成的活动的抽象视图来理解。这将被视为各种类型的数据库对象的读取和写入序列。

这可能意味着页面、元组、关系,但我们不需要太担心这些。我们会更多地考虑概念上的独立性。所以,这将是一堆区域写操作。然后是这批工作,对吧?所以,这个工作单元,本质上是更新的一个序列,无论是读取还是写入,这整个工作批次必须要么提交。

这意味着它必须完成,或者它必须被中止,对吧?或者在这种情况下,它的效果不会反映在数据库中。这一切都作为一个原子操作单元完成。事务管理器,之前在堆栈中看到的那个组件,控制着事务的执行。程序逻辑。

应用逻辑对数据库系统是不可见的。事实上,可能会执行任意的计算。数据库系统,尤其是事务管理器,只会看到这些读写操作的变化。好吧。所以,它只会看到这些读写操作,并使用这些读写操作来确定如何将各种类型的查询或事务交织在一起。

这里有一个示例,如果我有一个事务,尝试从R账户转移$100到S账户。我有一个应用程序中的操作序列。首先是开始事务语句,读取R账户,减去100,写入R账户,读取S账户,给S账户加100,写入S账户,然后提交事务。这些具体的变化对数据库事务管理器来说是不可见的。

事务管理器所看到的只是读写操作。这些变化很可能发生在应用逻辑中。对吧。所以,事务管理器看到的只是,你是在读取什么,还是在写入什么。对吧。它看到的仅仅是这些。变化的语义对事务管理器来说是不可见的。

因此,事务提供了几个保证。对吧。事务提供的保证就是所谓的资产(ACID)保证。这些是一些相当高层次的属性保证,事务的概念支持这些属性。所以,ACID是一个助记词,代表原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

事务的原子性概念,我们将依次讨论这些概念,并通过示例深入探讨每个概念。原子性基本上表示,事务中的所有操作要么全部执行,要么都不执行。所以,事务是一个原子单位。一致性基本上表示,如果数据库以一致的方式启动,那么在事务结束时。

它也是一致的。所以,如果它遵循某些属性,事务的开始和结束会遵循相同的属性。对吧。我们稍后会讨论这些属性具体是什么。隔离性基本上表示,我可以假设每个事务都是独立执行的,不会受到其他事务的影响。

因此,每个事务的执行是与其他事务隔离的。一个事务的执行不会受到其他事务的干扰。持久性特性表示,如果事务提交,那么它的效果会持久存在。这意味着,如果一个事务提交了,那么它的数据会保留在数据库中。

其效果将反映在数据库中。所以,它们将按照顺序体现每个属性,虽然顺序可能不同。我们从隔离性开始。隔离性完全关乎并发性。它是事务管理器支持的两个方面中的第一个。

所以,隔离性属性基本上是说,数据库系统交错执行许多不同事务的操作,而这些操作可以是对各种类型数据库对象的读或写。数据库系统将确保两个事务不会相互干扰。对吧?

所以,每个事务的执行就像是它独立运行一样。它与其他事务是隔离的。所以并发访问不会影响事务的行为。因此,一个事务可以在不考虑与其他事务同步的情况下执行。所以,从应用的角度来看,这是一个很有力的保障,对吧?

这种隔离性保障。因此,所有这些的最终效果——这种隔离性保障——是,它就像按某种顺序串行执行这些事务。对吧?所以,这就是提供的保障。如果你确保每个事务的执行就像是它独立运行一样。

然后我们要做的事情是,我们会再次执行此操作:用户和程序员已经提供了这种保障,他们不需要担心干扰。对吧?他们不需要考虑来自其他事务的干扰。

你可以将事务看作是独立的。所以并发性,当然,引入了一些挑战。对吧?而这就是我们需要开发解决方案来处理的部分,作为我们事务机制的一部分。例如,如果你有一个事务 T 一,它从 R 中减去钱并写入到 S 中,而你有另一个事务 T 二,它读取 R 和 S 并计算它们的和。

如果 T 二在我们的事务 R 和 S 之间执行,那么它的结果会看到比 T 一之前或之后执行时更低的 R 和 S 的值。对吧?所以无论是在 T 一开始之前还是在 T 一结束之后执行,都是一样的。对吧?所以如果 T 二在我这里执行,它就会看到介于 T 一的变更之间的状态。

我们希望它完全在 T 一之前或 T 一之后运行。所以隔离性可以通过一次只执行一个事务来轻松实现。对吧?但是我们不希望串行执行。对吧?串行执行是不可取的,因为它很慢。它,它没有充分利用资源。对吧?

所以我们希望事务能够交错执行,同时也提供隔离性保障。好的。那么我们将原子性和持久性这两个属性放在一起讨论。对吧?所以一个事务以两种方式结束。它要么提交,要么中止。提交基本上意味着事务已经完成处理。

所以,当你完成所有事务操作后,你就发出一个提交操作。提交实际上是应用与数据库之间的一种契约,表明事务所做的更改需要以持久化的方式反映在数据库中。

永久存在。所以,如果你说提交,这意味着我已经完成了我的事务。我希望你确保这些变更被反映到数据库中。回滚可以由应用程序请求,或者事务本身也可以通过数据库回滚。因此,数据库管理系统在执行了某些操作后。

所以,回滚可能发生在你执行事务的一半时,你可以说,“嘿,我想在某个地方回滚”,也就是说,你希望这些事务的效果不被反映到数据库中。系统崩溃也是等同的。如果数据库系统崩溃,或者丢失电源,这会被视作回滚。如果事务正在进行中。

被视为回滚,你希望撤销该事务的效果,几乎就像事务从未发生过一样。所以我们现在谈论的事务的两个关键属性是原子性和持久性。因此,原子性属性基本上就是说,要么执行事务的所有操作,这意味着事务已经提交,要么一个都不执行。

这意味着事务已经回滚。持久性保证基本上说的是,如果事务已经提交,那么它必须在发生故障时依然存在。对吧,它必须反映到数据库中。它必须是持久的。对吧,它必须永远存在。因此,数据库系统通过日志操作来确保这两个属性:原子性和持久性。

所以它会执行撤销和重做以确保正确性。撤销已经回滚或失败事务的操作,以去除它们的影响。而重做是已经提交的事务的操作,且这些操作在系统崩溃时尚未传播到磁盘。好的,撤销和重做。这是我们在日志上执行的两个操作,以确保提供持久性保证和原子性保证。

以这里为例,我有一个熟悉的事务,它读取了R,然后从中扣除了一些钱并加到了S上。如果事务失败,那么这里的事务就是失败的。这是一个钱丢失的案例,对吧,你会留下一个不一致的数据库。对吧。

所以你从A中扣除了钱,但没有加到S中。数据库系统需要确保这个部分执行的事务更新不会长期反映出来。持久性属性基本上是指,如果用户听到事务已经完成。

如果事务正在提交,那么他们知道,在这种情况下,100万或100美元确实已经从我们的事务中正确地转移。对吧,已经转移了。他们不需要担心这个变更会被撤销。如果事务已经提交,那就意味着它已经完成。资产的最后一个属性是一致性属性,这个属性有点间接。

我不会像其他属性那样关注它,但它仍然值得描述。我们正在处理ACID。一致性属性基本上意味着事务保持数据库系统的一致性。因此,如果数据库中有一个一致的状态。

那么,事务的效果将产生另一个一致的数据库状态。基本上,它将数据库从一个一致的状态移到另一个一致的状态。因此,一致性通常以约束的形式表达。各种形式的声明性约束。这些是提供某种完整性的高层次约束。

我们已经看到这些类型的约束的例子,主键约束或外键约束。这些都是参照完整性约束的例子。我们还可以有类型约束。所以这是另一种约束的例子。还有其他类型的约束,我们在这门课上没有涉及。

但约束涵盖了你希望数据库遵守的断言或不变性。如果事务违反了这些完整性约束,它们将被中止。因此,数据库管理系统确保只有那些不会将数据库带到不一致状态的事务才被允许继续进行。好了,我们已经概述了事务概念。

事务提供了资产保证的概念,通过更并发的交互,实质上提升了性能,并且让程序员不必过多担心正确性问题。所以你可以在一定程度上隐藏并发性,以及跨事务的交互和故障处理。

在这些讲座中,我们将涵盖两个问题。一个是通过两阶段锁控制并发,另一个是通过日志进行恢复。具体来说,就是前置日志。好吧,让我们先谈谈并发控制。但如果有问题,我很乐意回答。Shiro。对,从。

从应用程序开发者的角度来看,通常这个事务是由数据库提供的接口,开发者会使用它。直接使用,还是通过其他某种方式?那么,他们如何访问这些功能?对,正是这样,应用程序开发者使用数据库的方式就是启动一个事务,进行一些更新,然后结束事务。

这就是应用程序与数据库交互的方式,确保例如,如果它们的更改涉及多个不同的SQL查询,则将其视为一个例子。对吧?所以你不希望,例如,在进行转账时,钱消失。对吧?所以通常,比如说,如果有一个不实现事务的坏数据库。

然后就完全没有对事务的访问。对吧。大多数数据库系统都支持事务。那么,你是在谈论不同的应用程序,它们有不同的…所以我不确定我理解你的问题。我只是想,假设有一个糟糕的数据库,就是说,那个实现数据库的人…

或者就像太懒得实现事务一样。是的。所以我的意思是,你需要有事务的概念才能正确使用它,对吧?很多无SQL系统,新一代的大数据系统,很多都不支持事务的概念,对吧?它们只支持类似于单个键值插入的原子性操作,对吧。

他们没有提供任何其他的保证。那么这是什么意思呢?嗯,如果我有一个多步骤的插入操作,那么我必须插入这个,我想插入那个,我想插入那个,然后我想删除这里的内容。那么如果这是一个更复杂的程序,应用程序的开发者就必须处理所有这些额外的负担,对吧。

它们需要处理一个因素,那就是可能有许多不同的应用程序,许多不同的用户同时访问数据库。它们必须将控制逻辑添加到应用程序中,以确保数据中没有异常情况发生。对吧,数据没有损坏,也没有一致性违反。

所有这些都将成为应用程序开发者在这种情境下必须承担的负担。这样说有道理。谢谢。好的,还有其他问题吗?如果没有,我们可以进入并发控制部分。好吧,那么我们来谈谈并发控制。好的, 并发控制的目标是向事务提供一种假象,让它认为它是唯一在数据库上运行的事务。

它提供了隔离性的保证。提供并发控制的一种方式是简单地执行串行操作。对吧,这样就完全没有并发。每个事务一次只执行一个。这是安全的,但速度慢。所以你希望执行能交错进行,以获得更好的性能。而且要以一种合理的方式来解决这个问题。

我们需要定义一个正确性的概念并确保它的实现。对吧,如果我们想提供这个隔离保证,允许哪些类型的交错执行呢?所以让我们形式化并发控制的概念。我们将首先讨论调度。这个调度的操作包括开始事务、读取某些内容、写入某些内容、提交或回滚。

对吧。因此,调度的一种表示方式可以是表格形式。在这种表示方式中,每一列表示一个事务。所以这是T1,这是T2,你会看到每个事务的操作以行的形式列出。因此,在这个表格表示中,每一行只有一个操作。好的。

因为它是从上到下读取的。所以也有一种字符串表示方式,你基本上是把它按线性方式排列。所以在这里你会说R1A表示事务1在A上的读取操作,事务在A上的操作,依此类推。好的。所以在字符串表示法中,根据惯例。

我们只包括已提交的事务,并省略在表格表示中可能看到的开始和提交语句。因此,为了推理在交错执行下的正确性,我们需要提供一个起点。那么我们知道什么是正确的呢?串行调度是正确的,按定义就是这样。对的。所以基本上每个事务从开始到结束执行时,不会受到其他事务交错操作的干扰。

所以在这个例子中,你所有的T1的操作都在T2的操作之前。对的。所以这是完全的隔离。接下来,我们将讨论等价性的概念。所以两个调度被认为是等价的。如果它们涉及相同的事务,每个事务中的操作顺序相同。所以在一个事务内。

它们的顺序是相同的。而且这两种调度最终都将数据库保持在相同的最终状态。对的。所以这两种事务有相同的结果。抱歉,这两种调度对数据库的最终影响是一样的。好的。如果这三个特性成立,那么这些调度被认为是等价的。那么继续我们对正确性的定义,我们知道串行调度是正确的,因为它提供了完全的隔离。

如果一个调度等价于某个串行调度,那么我们认为这个调度是可串行化的。所以基本上,如果你有这个调度,其中T1和T2之间有交错,如果它等价于一个串行调度,其中T1在T2之前或T2在T1之前,那么我们就说这个调度是可串行化的。好的。关于这个概念有问题吗?

所以我们来看一个例子。对的。假设我有这个串行调度。T1将100美元从A转账到B,T2向A和B都添加10%的利息。所以如果我有一个串行调度,先是T1,然后是T2。基本上,最终结果是A等于1。1。也就是你为A加上10%的利息,再减去100。

对的。所以你先做减法和加法,再做乘法。而B是1。1乘以B加100。对的。所以这是串行调度中的最终结果。这是另一个串行调度,其中,哦,T2发生在T1之前。在这种情况下,最终结果是A等于1。1乘以原始A减100,B等于1。1乘以原始B加100。

这是一个不同的调度。对的,这是一个不同的调度,一个不同的串行调度。但从用户的角度来看,仍然是可以理解的,这两种情况都是合理的。现在我们来谈谈调度三。这个调度中,T1和T2的操作处于主导地位。这不是一个串行调度,但因为并不是所有T1的操作都在所有T2的操作之前。

T2的所有操作都放在T1的操作之后。但在这个特定的情况下,这是等同于调度一。它将数据库保持在相同的状态。因此,它是可串行化的。为了看这一点,请注意,T1在A上进行处理,然后T2在A上进行处理。

因此,操作的顺序是正确的,保证了相同的最终结果。所以这是一个可串行化的调度。好的,作为强制执行串行性或检查串行性的一种方式,检查数据库是否保持在相同状态的这个属性通常是棘手的。你到底怎么确保这一点呢?这实际上并不容易。

所以我们需要一个更简单的测试来检查一个调度是否将数据库保持在与串行调度相同的最终状态。最终我们将使用一个更保守的测试。所以它没有假阳性,但有一些假阴性。所以在某些情况下,它会保守地说,“嘿,这不行”,即使它实际上是可以的。所以它会为了更简单的正确性检查而牺牲一些并发性。

那么,这个等价性测试将依赖于什么呢?

它将依赖于冲突操作的概念。对,冲突操作是指其中一个操作是读取,另一个是写入,或者两个都是写入。对,所以基本上是那些相互冲突的操作。两个操作发生冲突,如果它们来自不同的事务,它们操作的是同一个对象,并且至少有一个是写操作。

那么,这些操作发生冲突的场景是什么?所以我鼓励大家思考这个问题并自我证明。非冲突操作的顺序实际上不应该对数据库的最终状态产生影响。所以如果你有两个读取操作,例如,它们的执行顺序是无关紧要的,对吧?

因为它只是两次读取。另一方面,如果是一次读取和一次写入,或者是两次写入,这就很重要,尤其是对于同一个对象。你执行这两次操作的顺序很重要。所以这就是为什么我们要把注意力集中在冲突操作的顺序上。好的。

所以我在这里停一下,因为我们已经讲到课结束了,我很乐意回答问题。我将在下一节课继续讲冲突的概念和串行性。嗯,好的。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/a35d787499b28aec4dd3ca25f0963939_9.png

P15:第15讲 事务与并发 II - main - BV1cL411t7Fz

好的,没问题。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/eae5e6a7bf6abaacd589335a2e1dca71_1.png

是的,今天非常暖和。我不知道学习事务处理是否有帮助,但我们还是继续做吧。我们一直在讨论如何保证隔离性。当你有多个事务同时发往一个数据库系统时,我们在讨论如何保证这些事务不相互干扰。

我们在讨论如何确保这些事务执行时的一些正确性概念。如果你有多个事务同时执行,你如何交错这些操作,才能确保它们的正确性?

做一些合理的事情?稍微回顾一下,我们讨论了调度的概念。调度基本上是一个或多个事务对不同数据位的操作序列。所以它基本上包含了事务的开始(begin)、读取和写入不同对象的操作。这些对象可能是页面、日志、关系等,最后是提交或回滚。

提交(Commit)基本上是说我希望这个事务的效果被持久化。回滚(Abort)基本上是说,撤销这个事务的效果。我不再希望这个事务的变化反映到数据上。因此我们看了两种表示方式。一种是表格表示法,其中每一列对应一个独立的事务。

另一种是字符串表示法,其中你基本上按顺序列出所有操作。所以在这里,事务1读取A,事务1写入A,事务1再读取B,并写入B,依此类推。字符串表示法的约定是,我们只包括已提交的事务,省略开始(begin)和提交(commit)。

在这个字符串表示法中,正如我所说,我们需要一个起点来定义调度的正确性。因此,自然的起点是串行调度。串行调度基本上是说,你按顺序执行事务,每个事务从开始到结束,不会有任何干扰或交错。

而且来自其他事务的操作。这样基本上就实现了完全隔离。在这个波形图中,事务1的所有操作都在事务2的操作之前完成。所以这是一个串行调度。我们需要的第二个定义是等价性的概念。

因此,如果两个调度包含相同的事务集,并且每个事务内部的操作顺序相同,那么这两个调度被认为是等价的。也就是说,每个事务在做相同的事情,而且这两个调度将数据库的最终状态保持一致。所以,基本上你可以从以下的方式来理解。

这是相同的事务集。它是作用于数据库的相同操作集。每个事务都以相同的方式执行。它们有相同的操作集。然后这些事务的最终效果是相同的。这就是等效性的概念。一个调度被认为是可串行化的。所以这是我们定义的第一个正确性概念。

我们在这里讨论的一个调度被认为是可串行化的,如果它等价于某个串行调度。回想一下,串行调度意味着基本上是完全隔离的,D1在T2之前,T2在T3之前,或者这些事务的其他排列顺序。但基本上它是完全隔离的。所以在这个两个事务的例子中,一个调度。

即使S调度有事务间操作交错,它仍被认为是可串行化的。如果它等价于某个串行调度,例如T1在T2之前,或T2在T1之前。那么这就是可串行化的概念,这是我们想要在这里定义的正确性概念之一。

我们将定义其他正确性的概念,这些概念稍后会更容易检查。好了,接下来让我们对这个可串行化的概念有一些直观理解。所以,如果你有这个调度S1,它是一个串行调度——也就是说,事务1的所有操作都发生在事务2之前。在这个特殊情况下,T1正在将100美元从A转账到B。

T2正在对A和B都加上10%的利息。那么如果你有一个串行调度,其中T1在T2之后执行,最终的结果是A’ = 1.1 × A - 100,B也是类似的。好的,这就是一个可能的串行调度。这里还有另一个串行调度,其中T2在T1之后执行。所以在这个特殊的情况下,T2的效果首先反映在你的数据上。

然后T1的效果被反映出来。所以你有A乘以1.1,B乘以1.1,然后发生A和B之间的转账。所以基本上,你从A中减去100并加到B中。所以这是一个不同的调度,但它依然可以理解,依然是隔离的,依然是正确的。

让我们讨论一下另一个不是串行调度的调度。这个不是串行调度。在这个调度中,T1在A上执行操作,将A减少100。然后T2对A值乘以1.1进行乘法操作。T1然后读取并写入B。所以基本上是把100加到B中,而T2把B乘以1.1。现在,在这里,这不是一个串行调度。

但它等同于第一个调度,其中事务T1的操作完全发生在事务T2的操作之前。希望你能说服自己,认为它与此等效,因为在这种情况下,A,你先从A中减去100,然后再乘以1.1。而B,你先加上100,然后再乘以1.1。

所以基本上就像是T1的操作发生在T2的操作之前。这样是可串行化的,尽管它本身并不可串行化。好吧,关于这个可串行化概念的挑战在于,它很难检查一个调度是否将数据库留下的最终状态与另一个调度的最终状态相同。所以我们需要一个更简单、更容易的等价性测试。

所以我们最终将使用一个更保守的测试。它没有假阳性。也就是说,它不会错误地判断某个调度是可串行化的,即使它实际上不可串行化。但是它有假阴性,意味着它有时会说,嘿,这个调度不可串行化,尽管它是可串行化的。

所以你基本上是在牺牲一些并发性,换取更容易的正确性检查。你仍然保证正确性,但你基本上是在说,嘿,有一些调度我将不接受。所以这基本上是更保守的做法。所以我们将要使用的这个测试,是一种不同的正确性概念。

使用冲突操作的概念。所以这些是成对的操作,可以是读写、写写,或写读操作。所以如果两个操作满足以下条件,它们会被认为是冲突的:它们来自不同的事务,操作的是同一个对象,并且至少有一个是写操作。所以基本上,这种冲突会在这两个事务之间引入基本的排序。

所以如果事务二读取了事务一写入的内容,那就是一个冲突。如果事务一和事务二都写入了相同的内容,并且事务一的写操作发生在事务二之前,那就是一个冲突,依此类推。这些都是冲突。所以非冲突操作的顺序。

对数据库的最终状态没有影响。原因是,如果我在写B,你在写A,那就没关系。但如果我们都在写同一个对象A,那就成问题了。或者如果我在写某个东西,而你在我写完后立即读取,那也是个问题。所以我们的关注点,主要集中在冲突操作的顺序上。

应该是非冲突操作,冲突的定义是这样的。所以我们有了冲突的概念之后,让我们定义冲突可串行化的概念。这是我们之前定义的可串行化概念的一种变体。所以如果两个调度是冲突等价的,那么它们就被认为是可串行化的。

如果它们涉及相同事务的相同行为,并且顺序相同——基本上,就是相同的一组事务,在每个事务内的相同行为——并且每一对冲突操作,都是按照相同的顺序排列的。所以每一个冲突都是按照相同的顺序排列的。所以如果事务一存在冲突的话。

如果事务一写了某个东西,而事务二读取了某个东西,并且在第一个调度中的顺序与第二个调度中的顺序相同,那么就必须保持一致。然后,利用这种冲突等价的概念,我们可以定义冲突可串行化的概念。

这实际上是我们之前定义的可串行化概念的扩展。如果一个调度 S 与某个串行调度是冲突等价的,那么它被认为是冲突可串行化的。而由于冲突可串行化是一个更为保守的属性,这意味着 S 也是可串行化的。所以冲突可串行化比可串行化更保守。这意味着 S 也是可串行化的。但这并不是一个完整的集合。实际上,有一些可串行化的调度是不可冲突串行化的。因此,冲突串行化会给我们带来假阴性。

当我们将它作为一个可串行化的测试时。正如我所说,它更为保守。这就是使用保守测试的代价,它错过了一些可串行化的调度,即使它们是可串行化的。这是我们愿意做出的权衡,因为强制执行冲突串行化性更便宜。

有一个与冲突串行化等价的概念,这个概念更容易处理,至少对我将要描述的例子是这样。所以这是这个概念:一个调度 S 被认为是冲突可串行化的,如果你可以通过简单地交换不同事务之间连续的非冲突操作,将 S 转换为一个串行调度。

所以考虑一下不同事务之间的非冲突操作的任何交换。如果你能得到一个串行调度,那么这也意味着 S 是冲突可串行化的。为了让这个定义在你脑海中更为具体一些,我们来看一个例子。这是我的操作顺序,这是事务一,事务二。

所以基本上,事务一,红色,a,路,a,然后事务二,红色,a,路,a。接着事务一,红色,b,路,b,依此类推。所以这就是这个例子。现在我将尝试进行交换。所以我尝试交换连续的非冲突操作,试图将其转换为串行调度。所以我要交换的第一个操作是 RB 和 WA。

所以这是一个交换操作。没问题,我可以交换这两个,因为它们位于不同的对象上,A 和 B。所以我可以交换它们,这不是一个冲突。好吧,我可以继续交换。现在我要交换这两个事务中的 WB 和 WA,它们仍然位于不同的对象上,所以我可以交换它们。然后我可以再次交换 RB 和 RA,它们也在不同的对象上。

然后我可以交换 WB 和 RA。所以现在通过所有这些交换,我基本上把这个调度转换成了这个调度。所以通过交换非冲突的操作,我成功地得到了一个串行调度。这意味着这个调度与这个串行调度是冲突等价的。好的。

因此它是冲突可串行化的。好的,现在让我们再看一个例子,讨论它是否是冲突可串行化的。好的,我有两个事务,T1 和 T2。我们认为它是冲突可串行化的吗?(沉默)不,为什么不呢?

因为A是相同的。是的,所以所有操作都发生在同一个对象A上。所以即使我实际上可以交换这两个操作,我可以交换这两个事务中的RA,但是我交换后就会遇到阻碍,对吧?我不能再移动这个RA了。所以即使我把这个RA移动到这里,比如RA,我也无法再继续交换。

因为我不能交换这两个操作的顺序。这两个操作是冲突的。我不能交换这两个操作的顺序,因为它们是冲突的。所以基本上我只能保留这个调度,不能将其转换为串行调度。好的。所以Alvin提出了一个重要观点。他说他的交换只是一个练习。

检查一个调度是否是冲突可串行化的,对吧?

所以这里我们在尝试测试某个调度是否等价于串行调度。好的。明白了。所以这个不是冲突可串行化的。所以你只能交换RA的顺序,不能做更多的交换。那么我们如何更一般地检查这个问题呢?

所以你是单独进行这些交换,这有点痛苦。那么,为什么呢?我们怎么检查某个事务是否是冲突可串行化的呢?

所以做这个的一个方法是使用这个冲突依赖图,对吧?

所以你基本上每个事务对应一个节点。比如这个例子中的TI和TJ。如果TI中的操作OI与TJ中的操作OJ冲突,并且OI在调度中出现在OJ之前,则TI和TJ之间就有一条边。不是辛普森的,但OI确实在调度中出现在OJ之前。好的。

基本上它说的是这是一个冲突,我不能将其交换到另一方向,对吧?

所以基本上说,这个方向上有一个依赖关系。好的。所以现在我们有了这个图的定理是:一个调度是冲突可串行化的,当且仅当这个依赖图是无环的,对吧?所以,这个,我让你们回家自己证明,但这里的框架是,这些边基本上表示。

就是某些操作之间的时间关系,或者某些事务之间的时间关系。如果你有一个循环出现在这个图中,这意味着你不能找到一个事务的排序,也无法确定一个串行调度,对吧?如果图是无环的。

基本上,你通过拓扑排序这些边。那张图会给你一个串行的调度,而那个串行调度就是你最终得到的结果。如果你单独进行这些交换,对吧?每一个从一个可能的拓扑排序得到许多这样的调度,但这些边决定了你能做什么。

和不能做什么的交换操作。因此,总的来说,定理基本上说:如果这个依赖图是无环的,那么它就是冲突可串行化的。好的,证明基本上是说,如果你有冲突的操作,它们会阻止我们将操作交换到一个串行调度中。

基本上只是对这个图进行拓扑排序。好的,下面我们通过一个例子来更清楚地说明。假设我在 T1 中有这两个操作,R A 和 W A。所以我还没有添加任何 T2 中的操作,但假设 T2 现在有 R A。

由于操作 R A 和 W A 之间存在冲突,本质上有一个时间顺序关系。也就是说 T2 必须在 T1 之后执行,因此你通过一条边来表示这个关系。好的,然后假设 T2 还有其他的写操作和读操作。比如它写 A 读 B 写 B,然后假设 T1 读 B。好的,这就是调度。由于你在最后有 RB,

这意味着 WB 和 RB 之间有冲突,WB 和 T2 之间也有冲突,以及 RB 和 T1 之间也有冲突。这意味着我需要基于 B 从 T2 到 T1 添加一条回边。好的,从这个点开始,给定……所以这个图显然有一个环。所以根据我们之前给出的定义,这不是冲突可串行化的。从直观上讲,

这也非常清楚为什么在这个特定的例子中你不能真正进行交换。也就是说,要么 T1 在 T2 之前执行,要么 T2 在 T1 之前执行。因为这些边,这些关系,基本上表示一个无法交换的冲突。其他的事情你也不能交换。

但是这些是阻塞,阻止你翻转调度,从而使你能够得到一个串行调度。好的,对于冲突可串行化的这个概念,有没有什么困惑?以及为什么图在确定一个调度是否是冲突可串行化时很有帮助?

Ian?哦,是的,只是为了澄清。所以本质上,对于每一对冲突,我们将有一条箭头,对吧?没错,没错。所以在两个事务之间会有多条箭头,指向不同的方向。只要你……

在每个方向上保留一条箭头是高效的。我明白了,谢谢。对了,Warren 还有其他问题吗?哦,是的,除了检查这个图是否有环,还有其他需要检查的吗?如果你试图确定冲突可串行化的话,那就没有了。还有其他形式的变体,你可能会想要……

可以使用更宽松的概念或更严格的概念。接下来我会讲解一个更严格的可串行化概念。还有其他问题吗?好的。这个是更严格的概念,但它的保守性较弱,属于一个较宽松的可串行化概念。所以它有更少的假阴性。对于这个概念,

这个概念叫做视图可串行化(view serializability)。之前的概念叫做冲突可串行化(conflict serializability)。所以这会有更少的假阴性(false negatives)。从保守性角度来看,它介于冲突可串行化和完全可串行化之间。对的。所以在这个概念中,两个调度 S1 和 S2 被认为是视图等价的。

如果基本上它要求你进行非常细致的记录,记录每个事务和每个对象的操作。好吧。于是,记录方式如下。它基本上说明了两个调度S1和S2是等效的。如果你进行的是相同的一组初始读取操作。所以如果T一在S1中读取了A的初始值。

然后T,我在S1中读取了A的初始值,然后T我在S2中也读取了A的初始值。好吧。所以他们做了相同的初始读取操作,然后做了相同的依赖性读取。好吧。所以如果T我在S1中读取了T J写的A的值,那么T我也在S2中读取了T J写的A的值。

所以基本上所有写入并随后被读取的操作,在两个调度中发生的方式是相同的。最后,作为这些操作结果的是什么是相同的。如果T我在S1中写入了A的最终值,那么T我也在S2中写入了A的最终值。

这是一种非常细致的记录方式,记录了哪些值被读取了,哪些是开始的?

那么谁读取了这些值,开始时?然后,如果某些东西被写入了,谁来读取这些?然后最后,谁在最后写入?好吧。这种细粒度的等效性概念如何发挥作用呢?

所以这个概念允许所有的冲突可序列化调度。因此,每一个冲突可序列化的调度也是视图可序列化的。它还允许所谓的盲写操作。因此,它比冲突可序列化的要求更宽松,因为它允许盲写操作的存在。

所以让我们举个例子来理解什么是盲写操作。假设我有这三个事务,T一,T二和T三。好吧。在T一中,你有读A(R a)然后写A(W a),T二只有写A(W a),T三也只有写A(W a)。好吧,再次强调,这只是一个字符串表示。你从左到右读取这个操作序列。

现在我们来看视图可序列化性。所以首先请注意,在冲突可序列化的情况下,这两者之间是有冲突的。因此,我不能交换这两个操作。我们使用可序列化性来说明,这两个调度是等效的,视图上等效的。与另一个调度等效,这个调度基本上把T一的所有操作都放在最前面。

然后是T二的操作和T三的操作。我们能够在视图可序列化的概念中交换这两个操作的原因是,这两个操作基本上是在写入一个A的值,而没有任何事务在读取它。对吧?它们是在写入一个A的值,而没有任何事务在读取它,最终只有T三的写入值才是有效的。

被写入的值。它基本上覆盖了T一和T二写入的值。所以T一和T二的写入顺序并不重要,因为它们都会被T三的写入操作覆盖。好吧,所以最终的写入操作才是决定性的。这样,盲写操作就是那些事务写入了值但没有被任何人读取的操作。

基本上它们可以被翻转。好吧。所以在这个概念中。这个可序列化性的概念基本上比冲突序列化要宽松,但你看,可序列化性的缺点是它。很难执行,你需要非常细致地记录是谁先读取了什么。

谁在阅读每个对象的哪个版本,这很难做到。所以,话虽如此,无论是哪个概念,你都会看到可序列化性不冲突。允许所有调度实际上是可序列化的,因为你不了解操作的意义。或者数据的意义。好吧。所以,可序列化性实际上谈论的是数据的最终状态,并在某种程度上对其进行编码。

操作的语义对于可序列化性和冲突序列化性并不要求你理解操作的意义。所以冲突序列化性是我们实际使用的,而 Alvin 会告诉我们如何使用冲突序列化性,那是因为它可以高效地强制执行。好吧。

有什么问题吗?好,看看,Alvin,接下来由你了。好的。所以在我们进入下一个片段之前,实际上在这之前,我觉得大家在问是不是。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/eae5e6a7bf6abaacd589335a2e1dca71_3.png

是否可以休息一下。所以我们想要休息一分钟,可能是拉伸一下,喝点东西,然后我们可以回到下一部分。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/eae5e6a7bf6abaacd589335a2e1dca71_5.png

所以,嗯,Alvin 也在征求 TikTok 视频的投稿,这样我们就可以播放在休息时间。是的,这其实是我最初发那条消息时的意思。所以,嗯,我其实没有什么正式的公告,你们可以继续,像,你知道的,做一些拉伸,除非像填写期中调查问卷。然后像,你知道的。

就像我说的,对吧?我们实际上是在寻找视频。所以,你知道的,在我去你们今天的讲座之前,我就想。我们实际上并没有收到很多这样的提交。所以如果你还没有做的话,像。你可以随意。你不一定非得自己做,知道吧?你知道的。

只要给我们发一些有趣的东西。如果你愿意的话,当然,如果你不介意与其他同学分享的话。你知道的,如果没人提交任何内容,是吧?那肯定会赢的。而他们什么也没有,是吧?所以,我们会盯着一块空白的屏幕看,像你知道的那样,大概两到三分钟,这会很尴尬。所以,嗯,拜托,意思是。

如果你们有任何东西,意思是,否则,像,你知道的,不然我也可以播放,像,我最喜欢的,像,猫咪视频,知道吧,独角兽,或者像,游泳的金鱼球。意思是,像,知道吧,这些可能都不有趣,是吧?

或者我们就会在Zoom窗口中互相盯着对方看,这很奇怪,对吧?所以,是的。我不会告诉你。所以,正如我所说,你不想知道我的账户是什么。别担心。如果你想分享,没关系,继续吧,我是说,你知道的。我欢迎你们都加我Facebook或TikTok,或者你可以找到它。我是说,你知道的。

这不是秘密。如果你知道的话,没关系。继续吧。你不需要提交视频。来吧,对吧?我的意思是,你刚才说过,如果你有什么想分享的,像是,你知道,否则下次我就开始播放高尔夫球员在游泳的画面,然后,你知道,我们会有一个非常安静和平和的休息时间。我可以播放一些做瑜伽的人。

我想。好吧,艾伦将要展示一些我现在不想点击的东西。我不知道那是什么。我不想做那个小小的横向切换。不是,我的意思是,我信任他。他是我们最优秀的老师之一。是的,反正,在我们继续之前,有没有关于讲座中DTS部分的问题?

所以接下来我要讲的内容,就是如何实现事务及其工作原理。也就是我们所说的项目四。正如你们所猜测的那样,实际上有许多不同的实现可用。它们往往具有不同的性能特征。

它们也面向不同类型的工作负载。然而,针对我们讲座的目的,我们将集中讨论一种叫做基于锁的实现。在我进入整个关于基于锁的实现的内容之前,我先抛出一些你们如果继续研究不同类型并发控制机制时可能会听到的流行术语。

比如说,你可能会听到有人谈论一种叫做多版本并发控制的方法。所以这里的直觉基本上是说,好的,我们就让每个人继续前进,好像他们都有自己的数据库副本一样。然后最终,当他们需要提交或需要中止时。

然后我们会检查是否存在冲突。你们大概已经猜到,在这种情况下冲突意味着什么了吧?就像是,你写了一些东西,另外一个人,另一个事务也在同时覆盖同样的内容,诸如此类。是的。

所以你可以想象基本上只有在你尝试提交事务时才进行检查。可是,除此之外,你就让每个人继续前进,好像什么都没发生。没有检查。没有锁定。例如,有时它会查找一些东西,比如叫做乐观并发控制(OCC)或时间银行的方法。

基于时间戳的并发控制也是如此。所以这些是你如果继续学习不同类型并发控制机制时可能会遇到的内容。但正如我所说,为了简化问题,我们只专注于基于锁的实现。

所以,嗯,如果你没明白我刚才说的内容,不用担心。那些只是为了引起你的兴趣。首先,我们来讨论一下,嗯,我所说的锁是什么意思。其实锁有很多种概念。你知道,这不是161课程。对吧。所以我们不会讨论像,嗯,类似的内容。

比如安全性方面,这里没有对手存在,对吧。所以,嗯,别担心。如果你在担心这个问题,你是安全的。然而,我们所谈论的是锁的概念,或者是独占锁。就是我心中的那种。所以像信号量,或者说基本的独占区域。是的。

代码的独占段,只有一个线程,或者说一个控制流能在某一时刻执行。所以你可能会在61 C中遇到这些。希望这是你可能会记得的内容。基本上,这些都是同步原语。对吧。所以我们基本上会在我们希望独占访问的代码片段周围加锁。

所以这些就是我们在本课程中讨论的锁的类型。大家都很熟悉锁这个概念,或者说我们在本课程中提到的同步原语。希望这能勾起你们一些愉快的记忆,回想起61 C课程的内容。然后马上就有人说,我死定了。好吧,对不起。好吧,继续。

但就本课程而言,你需要记住,就像在61 C课程中一样,这些锁原语实际上并不是免费的。对吧。所以我们需要有,嗯,运行CPU,像这样,它们都会消耗你的一些周期。所以这些并不是像你想象的那样是免费的操作。所以请记住这一点。而在本课程中,对于数据库课程来说。

我们将遵循一个非常简单的约定。所以这就是你需要知道的关于锁的一切。例如。我们假设每个数据元素都有一个唯一的锁。每个事务必须首先获取该锁,才能读或写该特定元素。如果一个锁已经被其他事务占用,那么,嗯,你只能等待。

然后在我们完成操作后,我们总是在某个时刻释放锁。所以这是我们在本课程中遵循的基本原则。接下来,你们会在本讲座中看到不同类型的锁定协议或方案。有时候你会听到人们谈论这些。

它们的不同之处基本上是在什么时候锁定或解锁这些数据元素。它们能够实际锁定的内容以及当某个人在等待锁时发生了什么。我一直在努力解释在这种情况下数据元素是什么。所以,事实证明,它在不同的供应商之间有所不同。例如。

你可以认为这里的数据元素是整个数据库。我的意思是,你可能会觉得这很傻,对吧?因为如果你锁定整个数据库,那么当然,你基本上就保证了没有其他事务能够并行运行。因此,从定义上讲,它保证了可串行化性。事实上,这时它变成了一个严肃的调度问题。

对的。在任何时刻,只能有一个事务正在进行。而这个事务必须完全结束,另一个事务才能开始。所以,当然,我们会像这样保持事务的完整性。你可能会觉得这个例子很傻,没人会真正实现这种方式。但信不信由你,像 SQL 这样的系统在运行事务时有锁定整个数据库的概念。

这是什么意思呢?这意味着,如果你关心并发事务在你的应用程序或首次启动时的执行,举个例子,不要使用类似 SQL 的数据库,因为 SQL 假设每次当某个事务开始时,整个数据库都会被锁定。

所以,祝你好运。还有一些数据库实现,它们只锁定单独的记录。对吧?这就更合理了,正如你所想的那样。所有的商业实现都这么做。稍后我们将看到关于为何选择这种方式而非另一种方式的不同权衡。我们还可以探讨是否能将这两种方式结合使用,例如,好的。

所以在本课程中,正如我之前所说的,对于我们能够获取和释放的每一个锁,我们将使用这个符号表示。下划线部分,指的是事务 I,然后是 L 或者 U。

所以,实际上就是这样。首先,这是一个非串行化的调度。为什么这是一个非串行化的调度呢?提醒一下自己,时间是向下流动的。好了,这里我们有 T1 尝试读取 A,你可以把 A 想象成一个记录。比如说,一个单一的元组,然后它尝试读取 A 并进行写入,接着是 T2 的一些操作,最后再回到 T1。

对的,在这种情况下,我们正在写入 A,然后 T2 基本上会读取由 T1 写入的值。接着,T2 会写入 B,而 B 随后会被 T1 读取。对此,我挑战你一种方法,可以交换操作顺序,使其变成串行调度。

这没有办法,对吧,因为我们陷入了一个循环中,对吧。因为 T1 和 T2 依赖于 T1 对 A 的值,T1 又依赖于 T2 对 B 的值。所以我们有点卡住了。所以这是一个不可串行化的调度。现在,如果我想把这个调度改为可串行化的调度,一种做法是,我之前说过的,对吧。

通过引入锁。所以在这种情况下,我会先获取锁。在读取 A 之前,我会先获取 A 的锁。然后在我读取之后释放它。接着,我会获取 B 的锁,对吧。就像你的案例一样,当你是 T1 时,先进行第一个事务。所以同样的道理。同样适用于 T2。所以 T2 会首先获取 A 的锁,然后再读取它,以此类推。然后,当它试图获取 B 的锁时,它会被阻塞。对吧,因为 T1 当前持有 B 的锁。所以 T2 必须等待。

由于 T2 无法继续执行,我们实际上可以切换回 T1。所以在这种情况下,T1 会完成它对 B 的操作,然后解锁。之后 T2 就可以获取锁并继续它对 B 的操作。Kyle,你有问题吗?是的,为什么 T1 会锁定 B?嗯。

因为 T1 知道它稍后会对 B 进行写操作,对吧。所以我可以强制这个调度变得可串行化,意思是知道 T1 和 T2 将要做的事,基本上就是插入这些锁语句。因此,实际上,这个过程的目的是向你展示,使用锁的方式,对吧。

所以我们现在已经确保了一个冲突可串行化的调度。我是说,你可以在之后做这件事,对吧。因为有了这个特定的调度,你现在可以进行我之前告诉你们的交换操作,使这个调度变成一个冲突可串行化的调度。我明白了。这样说有道理吗?是的,谢谢。好的,太好了。还有其他问题吗?所以,嗯。

我有一个问题。T2 试图获取 B 的锁,但被阻塞了,然后 T1 释放了锁。它会在解锁后自动获取锁吗?还是必须重新请求锁才能获得它?这会被授予吗?你需要读取吗?不,没错,没错。所以你可以这样理解。

是的,不,没错。所以基本上你可以把 T2 想象成,发生了什么呢?

为什么它被锁定了?对吧。所以你可以把它看作是基本上在不断旋转。所以你可以把它看作是一个来自 61C 的调用,对吧。你可以实现这个锁的一种方式是基本上有一个 while 循环,不断地什么都不做,仅仅检查。然而,在你能够实际跳出它之前,它并不那么可用。好的,谢谢。那么在这种情况下呢?

这基本上意味着,我们可以跳出循环,因为我们终于获得了锁。好吧。是的,到目前为止,一切还好,对吧?嗯,关于选择什么时候锁定资源以及如何拆分调度有个问题,因为在当前的设置中,确实有效,像是L B被事务1锁定。

但几乎看起来,如果有一个像T3那样的事务,它首先执行一些操作,可能在T1操作A的时候,它就开始使用B。我猜,像是。看起来你选择开始加锁的时机似乎有些随意。是的,完全正确。所以在这种情况下,我实际上决定让你首先抓取B的锁。

对,实际上,这为下一张幻灯片做了一个非常好的引入,对吧?所以我们到了这儿。我们已经有了它。所以在这种情况下,我的意思是,关于锁和解锁的部分,我并没有改变任何内容。所以我们依然在抓取锁,例如,对吧?所以在这种情况下,注意到我在锁定A之后,紧接着抓取了B的锁,对吧?所以在这里。

如果你看一下,像T1的调度是完全一样的。好吧,我在尝试在解锁A之后抓取B的锁,对吧?唯一的不同是,我现在允许T2先去抓取B的锁,而不是T1。对吧?就像在61C中一样,我们无法判断哪个事务会先抓到锁。

然后如果它们都在同时尝试抓取锁,那么就有些难以预测,哪个会先赢。所以下一张幻灯片中,我说,好的,让T1先赢,然后看看会发生什么。结果发现,我们现在有了一个可串行化的调度。

那么问题是,如果我让你先拿到锁,你知道,B上的锁会发生什么呢?也是同样的事情。其实这是一个问题。可能是修辞性的。不是的,对吧?因为现在我们回到了第一步,在这种情况下,你知道,我们现在。再次有了依赖关系。所以T2将读取T1刚刚为A写入的值。

然后它将写入自己版本的B,之后T1会读取它。所以我们再次陷入了最初的循环中。所以基本上看起来,你使用的锁并没有强制执行冲突可串行化的规则,对吧?当然,在这一点上,我知道该如何测试了。那么现在该怎么办呢?

基本上,这意味着仅仅使用锁本身并不够。正如我之前所说的那样,我们来试着强制实施第一种锁定方案或协议。在这种情况下,它被称为牙膏锁定方案,或者称为两阶段锁定(2PL)协议。它基本上要求每个事务在试图解锁任何东西之前,必须抓取所有的锁定请求。

再次强调,我们仍然使用锁,就像之前一样,唯一不同的是,我们在玩弄的是什么时候应用锁,什么时候释放锁。这就是为什么在本讲座的第一行我会说,所有这些不同的锁定协议。

团队们,他们唯一不同的地方就是抓取哪些锁,以及抓取的时机。对吧,同样,释放锁的时机也如此。让我们看看这个操作。现在我强制要求这两个事务必须先获取锁,才能释放任何东西。所以请注意,在这种情况下,我现在已经抓取了A和B的两个锁。

在我实际上释放任何东西之前。对。所以同样,对于T2来说,T2在这种情况下是在释放任何东西之前先获取所有的锁。好的,按部就班地进行,或者说按照程序执行。现在你可以看到,这个调度现在是冲突可串行化的,基本上回归到了我们之前提到的良好情况。

在这种情况下,我认为无论你知道两个事务尝试获取相同的锁时,如何进行调整并不重要。对吧,举个例子来看。T1在这种情况下是好的。它将首先获取两个锁,作为前两个语句。因此,只有在它确实获得了A和B两个锁后,它才能继续执行接下来的操作。

对。所以在这种情况下,我认为不可能在此之前让你进入并离开。你知道的,要使得这个调度成为无冲突可串行化的。因此,我将证明使用这种两阶段锁定机制将始终确保冲突可串行化。我们如何证明这一点呢?我们将通过反证法来证明。所以假设它没有实现。

好的,正如你从今年的讲座中记得的那样。这基本上意味着我们在依赖图中有一个循环。所以我们所说的依赖图是这样的:如果两个事务之间有依赖关系,依赖关系上会有一条边。你知道的,可能是读取-写入依赖或写入-写入依赖。

所以这是一个循环。你看,在屏幕上看到了这个循环。而且,它肯定不能确保冲突可串行化。对吧?我们来看看为什么这个情况不可能存在,我们试图通过矛盾证明这一点。所以我将通过向你展示时间中存在一个循环来证明这一点。

如果我们跟随这个依赖图。让我们一步一步地进行分析。首先,我要声明,事务1将在事务2能够抓取A上的锁之前释放A上的锁。为什么?因为事务1必须在事务2或其他事务能够抓取该数据元素上的锁之前释放它。对吧?

所以从时间上来说,A上的解锁事件必须发生在事务2锁定A之前。明白了吗?好的,没问题。请注意,我选择A是因为我们在这里有这个依赖关系,这个依赖关系基本上意味着事务1和事务2之间有依赖。

所以在这种情况下,我基本上是在说A上存在一个依赖关系。这意味着事务2不能在事务1释放A上的锁之前抓取A上的锁。所以让我们继续跟踪这个过程。所以现在我说事务2必须在能够释放B上的锁之前抓取A上的锁。为什么会是这种情况呢?我给你个提示,大家可以想想。

你必须确保所有的锁都在你自己锁之前释放。是的。所以根据2PL(两阶段锁协议),我们必须等待,按照2PL的规则,我们不能释放任何锁,直到我们已经抓取了所有锁。A和B是不同的数据元素,没问题。但在这种情况下,由于锁的操作…

锁必须按顺序进行操作,锁的释放也必须按顺序进行。所以在这种情况下,这是我能从一个事务或视图中做的唯一事情。因此,随着时间的推移,我们在这里建立了这种关系。所以我现在可以继续查看这里的边缘。那么,这些边缘集是什么呢?

这些边缘在说什么呢?你知道,跟之前一样。它基本上会说事务2不能释放B上的锁。事务2必须在能够抓取B上的锁之前,先释放B上的锁。出于同样的原因,我认为我们之前已经解释过这个问题。

所以你可以继续推理,不管你信不信,你最终会进入一个循环。结果发现,事务1必须在实际释放A上的锁之前解锁A。这就意味着时间上存在一个循环。所以这基本上是一个矛盾,因为同一个事件不能先于它自己发生。

很酷,对吧。那么现在我们已经通过反证法证明了这一点,这基本上意味着使用2PL确实可以确保冲突可串行化。到目前为止有什么问题吗?L1在哪里,我并没有在这里画出调度图,也没有画出事务内部的各个操作。

但是你可以想象,对吧,因为我们在依赖图中有一个从事务一到事务二的边。所以如果你回想一下DTS的讲座,这基本上意味着T1必须在执行过程中某个时刻获得对A的锁。哦,好吧,所以我们已经做完了,对吧,显然还没有完成,这意味着我们还得继续。现在我们有另一个问题需要解决,这个问题被称为不可恢复调度。

我什么意思呢?好吧,所有在这里这段代码之前的内容,抱歉,所有在这行之前的内容和之前的完全相同。唯一的区别是这个调度和之前的调度之间的差异是否有事务决定提交或者回滚,也就是所谓的“中止”。顺便说一下,这个问题也被称为级联中止。为什么这是个问题?

结果证明事务一最终决定中止。所以这意味着我们需要恢复事务一所做的一切,尤其是写入到不同元素的操作。所以在这种情况下,注意到事务一基本上写入了A和B两个元素。所以我们需要撤销它对A和B的影响。然而。

比如说你的事务二实际上决定提交。而在这种情况下,最糟糕的情况是,它不幸读取了事务一之前写入的B值。那很糟糕,对吧,因为现在事务二读取了一个脏数据,而这个数据本该被回滚。然后它也决定提交了,对吧。所以这不好。

现在事务二再也无法撤销了,因为事务一写入的值已经被事务二读取了,所以这不好。而且问题又出现了。到目前为止有问题吗?

这被称为级联中止,因为我们基本上试图弄清楚如何同时回滚事务一和事务二。在这种情况下,我们需要回滚这两个事务,因为这是保持数据库状态一致性的唯一合理方式。

好吧,因为T1决定回滚自己,所以T2已经读取了T1的值,因此我们唯一能做的事情就是禁止T2提交。所以解决这个问题的一种方法是,现在我们需要完善我们的2PL规则。所以不仅要像之前一样,只能在获得所有锁之后才能释放任何锁。

我们现在还必须确保所有的解锁操作发生在我们决定提交或中止之后。所以在我们决定提交或中止之前不能释放任何资源。否则,其他的一切都和之前一样。然后我声称,通过严格的调度,我们现在得到的调度既是冲突可串行化的,又是可恢复的。

你可以看到这个操作了,对吧?所以现在你可以看到,在我决定回滚或提交之前,我不会释放任何锁。因此,现在在这种情况下,由于我们已经处理了 T1 的中止,当我们实际授予 T2 对 a 的锁时,T1 的回滚已经完成。

所以这样我们就安全了。因此,T2 不会读取 T1 之前的脏数据。我会这么做。明白了吗?那么这是否确保了一个事务的所有操作在下一个事务之前完成?当你锁定一个事务时,就像这样,因为事务 1 持有 a 和 b 的锁,所以事务 1 的所有操作都会在事务 2 之前完成。是的。好的,这是个好问题。是的。也是“概念 1”想问的问题。

正是如此。所以现在我们正在逐渐减少我们可以并发执行的数量,因为现在看来,这实际上就像是一个串行调度了。好吧,事情不必看起来这么糟糕。假设如果你有另一个事务,它既不触及 a 也不触及 b,那它可以像其他任何并发事务一样继续执行。

所以我们并没有完全消除所有并发性,但由于我们希望事务是可恢复的,我们在某种程度上是退后了一步。好的。好的,实际上,大多数基于锁的数据库系统只使用严格的调度,因为它们希望确保事务是可恢复的,同时也是冲突可串行化的。

而且实际上它相当容易实现,方法是我们只需在尝试读取任何单一元素之前插入所有的锁定语句。例如,然后我们只需要确保在事务决定提交或中止之前,我们不会释放任何锁。

然后通过这样做,我们可以确认这两个问题已经解决了。我们还完成了吗?当然,答案是否定的。我是说,我们还有 25 分钟,所以肯定还有其他问题。那么,是的,当然还有其他问题。我不知道是否有人告诉过你,我是说,事务或者并发控制总是一个需要用血与泪书写的故事,或者我不知道,可能是血与汗,或者其他任何这些糟糕的事。

所以不幸的是,这个故事是相当悲观的。哦,好吧,接下来是什么问题?看一下这个调度表,我们有一个事务试图读取 a 然后写入 b,然后我们有第二个事务试图读取 b 然后写入 a。

好的,所以我们直接讲到死锁问题,T1 会先获取 a 的锁,然后它会等待获取 b 的锁。例如,它正在尝试获取 b 的锁。然后 T2 会首先尝试获取 b 的锁,然后它现在必须等待,因为 T1 持有着 a 的锁。好的,这就是所谓的死锁,对吧?所以你们可能都记得在 61C 课程中我们讨论过这个问题,嗯,我们需要避免死锁,当然,现在猫又出现了。

你们在这节课上会看到很多猫,正如我告诉你们的,这是一个非常悲观的画面。好的,现在回想一下,如果你们正在上 162 这门课的话,这其实并不是一个新问题,甚至在操作系统中也会遇到同样的问题,基本上就是一样的。

或者是并发控制机制。操作系统中的标准技术,例如在 162 课程中,是进行排序。强制执行一个排序,规定比如屏幕总是先获取锁,其次是其他设备。例如,为什么这对事务或数据库来说是一个问题呢?嗯,

我们要强制什么样的顺序呢?我的意思是,我们有两个池,两个表,这并不是像唯一的设备一样存在,那么我们该如何强制顺序呢?希望你们还记得,比如在 61C 中,你们是如何在软性问题中强制顺序的。

因为如果我们有顺序,那就意味着我们总是能在发生冲突时解决优先级问题,对吧?例如,如果两个事务都尝试同时获取同一个锁,我们就可以通过顺序来打破平局。所以,如果我们能够强制为事务设定顺序,那就没有问题了。但很难想象我们要强制执行什么样的顺序,因为人们不断提出新的工具,而我们无法预见人们会尝试创建什么样的表。所以,在处理死锁时,我们可以采取几种方法。

第一个方法就是避免死锁。如果我们能完全避免死锁,那我们就不会从一开始就遇到这个问题。如果我们不能避免,那么接下来我们要讨论的就是如何检测死锁。然后,如何在检测到死锁后进行处理,对吧?所以接下来我将讨论的就是避免死锁的问题。

我们可以避免锁的方式之一是完全避免锁的使用,就像在操作系统中的设备案例一样。所以我可以基于事务的“年龄”强制规定一个顺序。显然,我在这里说的“年龄”是什么意思,基本上就是说你知道事务执行了多长时间。所以现在拿时间,然后减去事务第一次尝试执行的时间。

然后,我就把那个称作事务的“年龄”。一种策略,或称为“等待策略”,基本上就是说,如果我有更高的优先级,这里是基于年龄的对吧。那么它就会等另一个事务完成。然后如果我实际上拥有的优先级比T或者J低,它就会中止。

另一种策略叫做“单一等待”,基本上是按另一种方式来做。如果我有更高的优先级,那么另一个事务J会自我中止,然后让I继续。否则,它就会等一下。所以你可能觉得这有点抽象,那么我们就用一个例子来说明一下,然后我会做一些可能有点臭的事情。

我们习惯了使用派对的例子。假设我们有两个人对吧,年龄,让我们用人类来举例。你有一个年长者在等着使用卫生间,而另外一个年轻人正好在卫生间里。

所以现在第一个策略说,如果年长者试图在其他人已经在卫生间时使用卫生间,对吧,那么我们可以等一下。对,这就是第一个策略的意思,等一下,死掉对吧。所以如果我有更高的优先级,它就会等一下。基本上这意味着年长者会等年轻者。

如果没有的话,它就会自杀,对吧,所以这就是“死”的部分,你看这里像是在等待环路中。然后是另外一种策略,对吧,所以一个权重基本上会说,如果我是年长者,我会基本上杀死年轻者,对吧,现在在卫生间里,然后你知道,我就可以接管。这就是其中一部分。如果是反过来的话,那我就等一下,好吗?

所以你可能会问,为什么这是避免那个死锁的?为什么这是防止死锁发生?要理解为什么,你可以问一个问题,之前的图像有什么共同点。我会把它放回到这个侧面,对,这一页。所以注意到,在第一张图片的前面,对吧。

所以注意到年长的事务会等年轻的事务,但反过来就不是。这样我们就避免了这种情况,或者说就是所谓的“年长者总是胜利”。所以年轻的事务会自杀,而不是像等待年长事务那样。所以我们这里不会发生死锁,因为永远只有年长的事务会等。

不是相反的情况。所以我们打破了平局。因此,你知道的,这样就不会形成循环。请注意锁定。然后我们遵循第二个规则。情况是相反的,永远是较新的交易会等待。较老的交易不会等待,因为它会立即通过胜利终止较新的交易。

所以因此我们也要避免那个情况。这样说有道理吗?换句话说,我们只是会使用这个年龄机制对交易进行排序,通过这样做来避免我们想要防止的循环。

所以,重要的是我们要知道,如果一个交易被其中一个机制终止了,它必须获得原始的时间戳。如果没有,那么就不公平了。举个例子,你可以回顾一下之前的某个方案。

是的,所以我们假设使用第一个规则的协议,然后如果一个交易失败,那么它基本上会重新排队并尝试再次执行。如果它获得了新的时间戳,那么它仍然会在外面等待,等待一个更老的交易完成,对吧。

然后这就是因为它比旧交易年轻,最终会自己终止。所以基本上这意味着新的交易永远不会被执行,而这是不好的,对吧。所以我们不仅要避免那个情况,我们还要确保有进展,对吧,这是你现在经常听到的,尤其是在我们处于选举阶段的时候,对吧。

所以我们想要确保有进展。我们要确保这些交易最终会被执行。但是,如果我们一直得到新的时间戳,那么按照第一个规则,新交易永远不会执行,这样就不好了。

所以唯一的解决办法就是确保我们得到原始的时间戳。所以,当旧交易最终完成时,就像你知道的那样,我们会在那时执行它。同时请注意,我只是使用这个“年龄”机制作为强制排序的一种方式,这并不是唯一的方法,你也可以使用其他机制。例如,已经消耗了多少资源,比如内存,已经获取了多少锁等等,这些都可以作为实现排序的机制。

我的意思是,我们所要做的就是对所有这些不同的交易强加一个任意的顺序,并确保这个顺序中没有循环。这样我们就可以用它来打破死锁的情况。目前对此有任何问题吗?

我为这个有点臭的例子道歉,但我认为这可能是一些人容易理解的例子。好的,如果没有的话,我接下来想谈谈死锁检测。好的。所以,如果我们不能一开始就避免死锁,或者如果你不想应用我们之前讨论的任何机制。我们可以尝试让每个事务都执行,然后像你知道的那样,当死锁真的发生时去检测它。

那么,怎么做呢?结果我们可以创建一个等待图。类似于我之前提到的依赖图,然后我们基本上检查这个图中是否有循环。如果有循环,这基本上意味着我们遇到了死锁。这个是怎么工作的呢?

所以这里涉及到事务。你知道的,正如之前提到的,时间是横向流动的。首先事务一尝试读取 A,然后它尝试读取这些不同的数据元素。到目前为止一切顺利。我们没有遇到任何死锁。

然后事务二尝试写入 B,接着事务一尝试读取它。就像在依赖图中一样,我们基本上会在 T 一和 T 二之间插入一条箭头。接下来发现 T 一现在正在等待 T 二的锁,因为 T 二正在写入 B。然后继续进行,现在 T 三尝试读取 D。这个是可以的。

因为像你知道的,没有人曾经写入 B。即使 T 一早些时候读取过它,这也不是问题。而且,像你知道的,C 也没有问题,但现在 T 二在 T 三读取 C 后尝试写入 C。所以在这一点上,我们也插入一条箭头,连接 T 二和 T 三。

因为 T 二现在正在等待 T 三释放它所持有的锁。然后,你知道的,接下来 T 四试图在 T 一读取后以及 T 二也已写入的情况下写入 B。现在 T 四也有一个依赖关系到 T 二,因为它也在等待 T 二当前持有的锁,只有等 T 二释放后才能继续。所以现在,如果 T 三尝试写入 A,我们就会遇到一个死锁循环。

因为 T 三正在等待 T 一持有的锁,即 A 上的锁。然后,递归地,T 三等待 T 一,T 一等待 T 二,T 二等待 T 三,T 三也在等待 T 一。

所以因为存在一个循环,现在就有了死锁。明白吗?基本上意味着它们都无法继续进展,每个人都在持有其他人的锁。没有人能够取得进展,即使 T 三依然在进行,T 四也仍然在执行。

T4仍然可以像之前一样继续执行,对吧?没有什么实际的变化。那么现在,由于T1到T3之间发生了死锁,我们基本上需要弄清楚如何打破这个锁,如何打破死锁。解决这个问题的方法就是周期性地运行死锁检测机制,通过检测循环来识别死锁。

然后,当检测到一个循环时,就像你知道的那样,选择其中一个事务来终止它。然后你实际上能检测到这一点。是的,所以基本上我们需要终止循环中的一个事务来打破它。然后我们让所有人继续执行,就像你知道的,我们终止并尝试重新执行。作为一个经验事实,大多数这些死锁通常是比较小的。

所以这并不像你知道的那样涉及到一百万个事务,通常只涉及到几个事务。而且即使我们所有的操作都做得很完美,意味着我们杀死所有相关的事务,情况也不会太糟,对吧?因为我们不会把大量事务重新添加回队列。到目前为止有关于这一点的任何问题吗?很好,所以我们现在已经讨论了如何避免死锁。

如果我们无法避免死锁,无论出于什么原因,我们现在有一种方法可以检测到它。而且我们也有方法来处理它,对吧?如果我们确实检测到死锁。现在你可能会问,是否能做得更好?比如说,我们是否可以避免进入死锁的情况呢?

如果每个人都只是试图读取数据,那么即使它形成了一个循环也是可以的。对吧?即使每个人可能实际上都在等待其他人的锁,实际上也是可以的,因为当每个人只是试图读取数据时,并没有真正的冲突,对吧?

那么我们如何实际实现这个机制呢?有没有一种方法可以实现这个直觉呢?结果证明是有的,我们可以使用不同的锁模式。到目前为止,我们一直在讨论一个单一的锁,对吧?比如你要么获取它,然后没有其他人可以访问它,这是我们在61C中学到的原则。那么这里有另一种实现锁的方法,我们仍然有这种排他性访问的概念,你想要获取某个资源。

但是我们有不同的级别。我们有一个级别可以让多个事务共享锁。例如,如果它们只是读取数据元素。那么我们还有另一个级别,叫做排他性锁,意味着在任何时候,只有一个事务可以持有该特定锁。为什么我们要这么做?嗯,我们这么做是因为我们希望允许那些只做读取操作的事务继续执行,而不需要处理所有这些死锁、检测或唤醒之类的问题。

如果每个人都在读取,那么你仍然可以继续进行严格的“出局”操作,我们仍然能够正确地实现并发。我们不需要终止事务,因为我们不会遇到锁的问题。这会非常好,因为我们仍然可以在并发控制下运行,并获得并发带来的好处。

即使我们仍然在玩严格的“出局”游戏。但是现在因为我们有不同类型的锁,所以我们需要确保有一种方式来区分什么时候事务能够获取每种类型的锁。所以为此,人们使用了一个被称为“锁兼容性矩阵”的工具。你可以看到这里有一个例子,所以我们有这个矩阵。

然后这里的每个条目基本上会有一个勾号或者叉号。阅读的方法是基本上查看左侧和顶部的两个条目。所以如果事务一没有在某个特定数据元素上持有任何锁,另一个事务出现并且可以照常继续进行。什么都不会发生。

如果一个事务已经在某个特定数据元素上获得了共享锁,且一个新的事务出现并且也试图获取共享锁,那是可以的。因为在这种情况下,共享锁意味着只是尝试读取对吧?因此,这符合我们的直觉,我们希望读取操作像以前一样继续进行。

它们可以像其他事务一样并行进行,就像其他事务一样,没有阻塞。不需要检查很多其他内容。然而,如果一个新的事务出现并试图写入一个正在被之前事务读取的数据元素,那么我们不会允许这种情况发生。换句话说。

如果一个事务已经持有一个共享锁,那么如果一个新的事务出现并尝试在同一个数据元素上获取一个独占锁,它们将不会被允许。对吧?因为否则我们就违反了我们之前的承诺,最终会进入我们之前尽力避免的那些糟糕情况。

你可以推理出其他情况,当然,这也意味着如果一个事务已经拥有独占锁,我们不能允许另一个事务也拥有相同的独占锁。好的,所以这是一个问题。我想在聊天中有一个问题,能不能有人重复一下我没听明白?是的,基本上问题是为什么我们不总是让读取操作先于写入操作?因为像在B+树中我们曾经有过这种设计。

我们必须先读取页面的修改内容,然后再写回去。对。所以,你总是可以保证先读取再写入吗?如果你能将事务操作的顺序安排得恰如其分,那当然很好。事实上,有些数据库系统要求必须在读取完所需的所有数据之后,才能写入任何内容。

但你总是可以保证这种情况对吧?例如,我可能需要先读取某些内容,然后才能决定写入什么内容。之后,在写入后,我可能需要再次读取从它派生出来的其他内容。所以这是一个,我需要先读取总学生人数,然后写入一个记录,表示总人数。然后,我可能还需要再读取它作为同一事务的一部分,并做其他事情。

所以你总是可以保证这种情况吗?如果这是事实,那当然很好,但并非总是如此。所以阿尔文,我觉得大家对于读取和写入的理解,以及事务在逻辑层面的作用,还有缓冲区管理器层面上读取和写入的含义,可能也存在一些混淆。

哦,明白了。所以,记住我们在讨论的这个例子是数据元素。所以我故意在这里保持模糊,因为稍后我们会谈到不同级别的日志。现在,作为本节讲座的目的,你可以将每个锁视为单个数据元素。

如果有一页我们已经读取到主存中,并且它包含了比如说100个不同的条目,那么这就意味着我们有100个不同的锁,每个锁分别用于两个相关的条目。这是问题还是困惑所在吗?我更困惑的是……

我想应该是在几张幻灯片之前,当时你在幻灯片上列出了四个事务,其中事务一是读取D和B,但事务二在没有读取任何数据的情况下就写入了B,再写另一个。我只是想知道这样做是否可能,因为我原本以为读取是访问数据的方式,而写入是修改数据的方式。

所以,我不确定这是不是你故意没有提到的,或者是否实际上可以做到没有数据的情况下就写入。嗯,你可以在写入之前就进行操作。例如,我想把每个人的名字都改掉。对吧?在这种情况下,我们不需要等待什么,只要直接写入所有记录就行了。

所以,没问题。对,好的,这是戴夫解释的内容,然后是的,明白了,谢谢。好,现在让我们讨论一下数据库系统内部是如何实际工作的。我们已经提到过锁的概念,对吧?那么,谁来管理这些锁呢?我们需要有人来管理它们。

结果证明,在数据库系统中有一个叫做锁管理器的东西来处理这些问题。正如你所猜到的,它的工作基本上就是处理所有的锁获取和释放。好的,在内部,它可能会维护一个哈希表,然后持续跟踪正在被锁定的对象。所以,如果我们谈论的是Google级别的锁,那基本上每个锁在每个级别上都会有一个条目。

然后,它会跟踪谁当前持有这些锁。它有一个授予集,基本上是指哪些事务当前持有这些锁。它还会记录锁模式,以防有多级锁的情况,就像我之前提到的那样。并且它还会维护一个等待队列,列出不同的事务正在等待该锁。这里是一个示例。

所以我们有两个不同的锁,A和B。它们可以是单一的,也可以是独立的。例如,我们可以看到T1和T2已经对A加了锁,因为它是共享锁。然后我们有一条其他事务的队列在等待它,分别是T3和T4。它们正在尝试获取A的独占锁。

这肯定只是其中的一种设计方式,像你可以想象的不同设计方式一样。但它的工作原理是,当锁请求到达时,我们基本上会查阅这个表格,然后决定是否授予它。

基本上,这取决于之前我跟你们讲的兼容性原则。所以如果我们可以授予这个锁,我们就会将这个事务加入到授予集。如果不能,我们就会把它放入队列中。如果一个事务尝试升级锁类型,例如:

如果它已经有了共享锁,现在决定获取独占锁,因为它将写入相同的数据元素。那么我们就会使用相同的机制,查阅这个表格,看看是否可以授予这个锁,如果可以,就把锁模式升级为独占锁。我的时间只剩两分钟了,我想简要谈一下锁粒度的概念,下次我们会继续讲这个内容。

早些时候我提到过关于不同类型的锁,比如三重锁的概念。但是你可以想象,这样做有点浪费,对吧?比如说,如果我实际上在执行一个查询,查询内容是“下一个停靠点”。假设我有一百万个人。如果我需要为这其中的每一个人获取锁,那将会非常昂贵,对吧?正如我之前所说,这些锁的获取和释放并不是免费的。

它们会产生 CPU 开销。所以如果我必须获取 100 万个独立的锁,然后再释放所有的锁,这将是非常昂贵的。那么,实际上有没有一种方法可以实现不同层级的锁呢?对吧?比如说,如果我能在整个表上加一个锁,那就很好,因为如果我实际上是在读取所有数据,正是我想要做的。所以我之前提到过这一点,一方面我们可以在三元组级别进行非常精细的锁定。

这样做的好处是允许高并发,因为现在你知道两个帖子实际上在读取,抱歉,是两个事务在读取,不同的人可以从同一张表中读取,现在实际上可以并行进行。所以它们之间不会互相干扰,它们有不同的锁,可以获取这些不同的锁,然后继续执行。所以这对并发性有好处,但在高开销方面会有问题。

正如我所说,想象一下获取 100 万个这样的锁,仅仅是为了读取,假设是执行一个选择查询。另一方面,你知道我们可以采用 SQL 写锁的方式,即为整个数据库加一个单一的锁。

这很好,因为没有开销,只有一个锁。但是,到那时我们基本上已经杀死了并发性。对吧?没有其他人能够继续进行。那么解决方案是什么呢?解决方案是基本上拥有不同级别的锁定,然后根据需要进行等待。随着我们向上走,我们决定实际需要做什么。

它将类似于我之前提到的不同锁定级别,比如共享锁和独占锁。你现在可以想象在表级别甚至数据库级别加锁。问题当然是,你必须决定在哪个级别获取锁。

比如说,你可以在表级、页级等不同层级获取锁。那么这里的权衡是什么呢?因为你知道我们可以为两个操作加一个单独的锁,这很好,正如我所说的,它可以允许我们有很高的并发。

我们也知道,这样做很好,因为较少的锁意味着管理起来也更简单。因此,它使得锁管理者的工作变得轻松。那么问题是,当然,我们能同时拥有两者吗?对吧?因为我们希望在不同的事务中同时发生两种级别的锁,且它们获取不同类型的锁。

然而,我们不希望它们相互干扰,因为那样会很糟糕,就像你知道的,我们最终可能会打破并发控制,让两个事务,比如说,写入同一张表的不同地方。所以,我实际上会停在这里,不告诉大家答案,下周再讲。

所以如果你快要死了,你知道你可以尝试快速浏览剩下的幻灯片,我尽力做了税务书。但是我会留给你们这个问题,我是说,我们如何才能让这两种锁都能在同一个系统中共存呢?

但仍然保持我们在数据库中所需要的一致性。好吧,如果你有任何问题,请留下来提问,否则我们周二见,祝大家周末愉快。[沉默]

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/eae5e6a7bf6abaacd589335a2e1dca71_7.png

P16:第16讲 恢复 I - main - BV1cL411t7Fz

好的,大家好,欢迎来到186的周二讲座。这个星期的计划是。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_1.png

我们首先要完成上周剩下的部分,特别是上周四讲的关于事务的内容,整理好整个部分,然后我们将实际开始讨论恢复(recovery)。然后我们会在其中做一些公告。好的,首先我想回顾一下我们上周讨论的内容。我知道已经过去几天了,所以。

你们中的一些人可能已经忘记了我们之前讨论的内容。所以我们讨论的主题是事务,对吧?好的,它是关于并发控制的,也就是我们如何确保在同一个数据库系统中允许多个操作同时进行,以便能够取得进展。所以我已经做了TS部分的。

这堂课已经讨论了许多不同的概念,比如运行事务是什么意思,什么是可串行化的,冲突可串行化等所有其他相关内容。然后在周四,我们开始讨论如何实际实现这些概念,对吧?所以这些概念是非常有用的,它们有很多实际应用。

许多事情都可以以可串行化的方式来进行,对吧?如果一个调度可以做到冲突可串行化,那么它就有很多非常好的特性。但是我们如何在实际生活中实现这一点呢?我们如何进行实现?因此,在这堂课以及你们的项目中,我们基本上将使用。

基于锁的实现方式,正如你们可能记得的那样,在这里,锁意味着对一段代码的独占访问,对吧?这段代码不能被其他人运行。所以这些就是我们在周四讲过的一些幻灯片内容。我们基本上在这堂课上讨论的是基于锁的实现方式,尽管实际上有许多不同的实现方式。

你们有很多不同的实现方法可以探索,如果你们感兴趣的话。而且,特别地,我们在谈论这个叫做theta元素的概念,因为我们正在讨论什么是被锁住的,对吧?接下来你们会看到,这也有很多不同的方式可以实现,对吧?所以我们可以锁定整个数据库。

这当然能保证可串行化性,因为这基本上意味着在任何时候只有一条指令可以取得进展。如果那条指令还没有完成,那么其他人就不能在同一个数据库上做任何事。所以,当然,它会是串行化的。但是不幸的是,这样做并不高效,对吧?因为我们基本上。

现在,我们也可以锁定单独的记录,对吧?一直到只锁定我们需要处理的两个记录的字段。所有这些,你可以想象成一个范围,对吧?我们可以锁定一个页面,可以锁定一个单一表格,可以锁定一组两个记录,依此类推。然后我们将看到后面选择不同选项时的差异。

所以关于锁定,我们需要讨论我们能对锁做什么,对吧?你们可能听说过或者还记得61C课程中,你可以抓取一个锁,然后进入临界区,接着在这个临界区中运行代码。但是为了抽象化这一切,对吧?

所以为了186的目的,我们将所有锁操作抽象为仅有两种操作,即锁定和解锁。就是这样。在我们锁定某些内容之后,我们就拥有对这些特定数据元素的独占访问权。无论这些数据元素意味着什么,可能是整个数据库,可能是一个表格。

可能是一个记录,依此类推。然后,解锁的操作则是相反的。我们通过了许多不同的调度,如你从周四的课上可能记得的那样。所以这是最初的调度,我们希望它是冲突可串行化的。然后,通过插入你在这个调度中看到的所有不同的锁,并且相应地执行。

如果我们首先解锁语句,那么我们基本上可以保证这个调度是冲突可串行化的。所以我不再重复细节了,但你也可以看到,仍然可能会出现问题,因为根据事务解锁的方式以及哪个锁被分配给哪个事务,我们仍然可能会陷入问题情况。

就像你在屏幕上看到的情况一样。当然,测试也会出现。所以我们接下来讨论的是所谓的2PL或者两阶段锁定规则,它规定对于每个事务,所有的锁请求必须在所有解锁请求之前。因此你可以理解为什么前面的调度会存在问题。

违反了这个特定的约束,因为我们在最后一个锁语句之前就开始了解锁了。所以根据2PL规则,这是不允许的,对吧?所以这是相同调度的重写版本,唯一的不同是,现在你可以看到我们在实际上解锁任何内容之前,先画出了所有的锁。对吧?所以这样是可以的,2PL是可行的,除了现在我们遇到了一个新问题。

对吧?这个问题也被称为不可恢复调度。在这种情况下,问题在于,如果一个事务回滚或中止,而另一个事务决定提交,即使我们坚持使用2PL锁定机制,仍然会有问题。这里屏幕右侧的第二个事务可能会阻塞其他所有事务。

去读取一些已经被事务一方修改过的内容。现在事务一方决定中止,所以我们需要以某种方式回滚或撤销事务一方所做的更改。但不幸的是,这样做也许还需要我们回滚,或者中止事务二,因为事务。

事务二看到了它不该看到的内容,对吧?这说得通吗?

但不幸的是,事务二已经提交了,对吧?所以我们现在处于一个奇怪的局面,对吧?因为事务一想要中止,但我们也需要回滚或中止事务二,你知道的,因为什么原因。但是事务二已经提交,我们就不能再撤销了。所以这就是问题。我们该如何处理这个问题呢?好吧,我们制定了另一条规则。

这就是所谓的严格2PL规则。所以跟之前一样,除了在这种情况下我们要求所有的解锁操作必须与提交或中止一起进行,也就是事务的结束,对吧?你可以从我们上一页的例子中看到,对吧?现在执行严格2PL规则,在这种情况下。

我们保证不会遇到不可恢复的调度问题。因为现在我们只有在回滚或提交之后才会解锁。这样说得通吗?所以这实际上也变得相当容易实现,因为,像你知道的那样,我们基本上只需要在合适的位置插入锁定和解锁语句。

事务,然后就让锁定进行下去,对吧?在谁先获得锁的方面已经不再重要,然后我们就能做出正确的决定,可以这么说,对吧?通过确保冲突、可串行化性以及恢复性。但这是否意味着问题就解决了?不幸的是,情况并非如此。

对吧?你还记得我上周说的是什么吧?

我的意思是,事务或并发控制永远是一个写满血泪与眼泪的故事,对吧?

或者像你知道的那样,眼泪或者其他一些糟糕的事情。嗯,我想这取决于你站在哪一边,对吧?因为如果你是银行交易的一方,对吧,那么你最终会得到免费的钱。我是说,我认为你不会哭泣。也许只有在你对这件事感到过于高兴时,你才会哭泣,对吧?或者,什么的。

但其他人会遇到麻烦,好吗?所以我们把这个问题放在那儿,对吧?对吧?那么现在的问题是什么呢?对吧?现在的问题是我们可以坚持严格的2PL规则,但现在我们遇到了死锁问题。死锁的意思是没有任何一个事务能够继续进行,对吧?所以这里我们看到事务一持有着锁A,然后。

等待B,然后T2等待B,持有B上的锁再等待A。所以需要有一个事务能够进展。我们遇到问题了。所以就是这样,我们停在这里,对吧?所以,我们在讨论,好的,现在我们有了死锁。基本上,我们需要了解三件事。

首先是如何彻底防止它发生。如果能做到,那就太好了,对吧?

第二种方法是如果我们无法避免死锁,那我们该如何处理它?

我们如何检测它?如何消除它?所以基本上这就是我们讨论的三件事。第一种方法预防是简单的。我们可以通过设置一个任意的顺序来做,规定事务如何获取锁,对吧?所以我可以随便说。

比如在这个例子中,162,我可以随便说屏幕优先于网络卡和打印机。所以如果屏幕尝试获取与网络卡相同的锁时,屏幕总是会赢。完全是随便的,对吧?所以没有。

我没有理由认为打印机必须排在最后,优先级最低。但至少这样能解决问题,因为我们将不再进入死锁状态。然而,1986年的问题是,如何在一堆表格和帖子上施加一个任意的顺序?对吧?我们没有固定的设备来处理这些,对吧?我们处理的是各种各样的。

人们可以创建表格。所以在不知道会涉及哪些表格或哪些帖子会参与查询的情况下,我们很难随意施加顺序。好的,如果我们无法完全避免它,那我们就尽量通过能够检测并在情况发生时进行处理来避免它。我们该怎么做呢?

好的,一种方法是使用事务的年龄这个概念。基本上,就是在特定事务上花费的时间。然后每当我们检测到死锁时(我会在下一张幻灯片中讲解),我们有几个选项。第一个你可以看到的就在上面,右边,对吧?所以,再次。

这是上周使用的臭例子,关于人们去洗手间的例子,基本上是让较老的事务(在这里指处理时间较长的事务)等待。如果最后发现较新的事务需要等待,那就让它自杀。自杀时。

这种情况意味着你知道,返回队列的末尾,再次开始等待,对吧?为什么这样可以防止死锁?

这样就能避免死锁,因为我们不再有循环等待的情况了。所以不是那种情况。你知道,旧事务需要等待年轻事务。而且年轻事务也会遇到需要等待旧事务的情况。对吧。因为在这种情况下,由于这个严重情况,每当年轻事务。

当年轻事务需要等待旧事务时,它总是会自我终止,然后你知道,去到队列的最后。这样我们就防止了循环的发生。旧事务总是会获胜,对吧,在这种情况下。除非,像你知道的,有人已经在使用资源。那么在这种情况下,旧事务也需要等待。

然后另一个方式,对吧,基本上是相反的方式。所以,你知道,或者实际上不是完全相反的方式,而是处理死锁情况的另一种方法,对吧。我们可以让年轻事务等待。就像这个例子一样,然后如果旧事务实际上需要使用资源,就终止在使用中的任何事务。对吧。作为一个不太清楚的示例。

我知道,对吧,但是至少这样就打破了死锁循环。一样的思路。所以我们现在有了,年轻的事务总是等待。但旧事务永远不需要等待,对吧。因为一旦它需要使用资源或获取锁,它总是能抢先成功,对吧。

所以,理应这不再是问题。所以我们再次避免了问题。即不再需要旧事务等待新事务,而新事务也可能需要等待旧事务,以及其他情况。这样说有道理吗?

到目前为止,有没有什么问题?所以,尼古拉斯在问,是否会因为等待而死锁,可能会。事务中重复发生阻塞,导致自我消耗的循环。是的。所以在这种情况下,我的意思是。我们只保证阻塞避免,对吧,我们并没有保证进展。所以,确实可能会发生这种情况,可能会是这样。

年轻事务会不断地回到队列的最后,回到队伍中,调整自己,看到旧事务仍在处理当中。所以,我就再次自杀,然后回到队列的最后。对吧。这完全可能发生,但除非,像你知道的。

旧事务会一直占用资源,直到完成其任务,对吧。这样说。否则,最终年轻事务将能够不断获取锁,或者在这种情况下,能够使用资源。因此,像你知道的,它最终会这样取得进展。但你说得对,在这期间。

你知道的,可能,我们可能花很多时间在排队,来回等待,然后,又要终止自己,然后再回去排队,就像是。只为了再次做同样的事情。好吧,现实生活中不太有趣,对吧,但在事务处理中,我猜我们可以容忍一些这种反复的过程,好吧。

很好。那么现在我们也讨论了,比如,如何从一开始就检测死锁。一个方法是使用一种被称为“等待图”(wait-for graph)的方法。所以我不会在这里重复整个概念,但基本上它是一个图,表示哪个事务正在持有。

当我们进入另一个事务正在尝试获取的相同区块时。比如,在这种情况下,我们在T1和T2之间有一条箭头,因为T1正在尝试读取数据元素B,而T2当前持有该区块,对吧。所以T1和T2之间有一条箭头。这与依赖图的概念非常相似,你可能记得吧。

来自DTS讲座。但在这种情况下,我们基本上是用箭头标注图表,每次两个事务之间存在锁定情况时。然后如果图中有循环,我们就基本上检测到了死锁。然后我已经在前面的幻灯片中讲过,你应该怎么处理它了,对吧。

当这种情况发生时,好吧。然后,就作为一个经验事实,这些死锁往往是小规模的。因此,通常不会涉及大量不同的事务。所以,即使我们必须终止这些事务,它们是新的事务,可能我们不需要终止很多它们。

从经验来看,尽管当然你可以构造一个反常的例子,假设我们有一个由数十个甚至上千个事务组成的大循环。但我想说,这在实践中相对较为罕见。好吧,死锁是坏的,我们想避免它,但另一种尝试避免死锁的方式是实际观察事务之间的情况。

需要处理死锁。举个例子,如果所有事务实际上都在尝试读取相同的数据元素,比如一个元组。如果它们都在读取,那么即使它们在读取相同的东西,只要没有事务实际上写入相同的元组,我们实际上可以让所有这些事务继续进行,对吧。

它们仍然可以继续读取,没问题。我的意思是,只要没有人进行写操作就行。但在前面的幻灯片中,我们基本上是在尝试获取区块,然后保持等待。我们基本上必须一直等待它们,因为同一时间只有一个事务可以读取该元素。所以要实现这个想法,一种思路是基本上有不同类型的日志。

所以现在我们变得更复杂了,对吧?我们超越了像是严格事务顺序的范畴。我们也超越了像死锁检测这种东西。所以这些东西基本上已经被我们处理好了。现在我们在讨论如何提高效率。如果。

我们一开始就有不同类型的锁。那么,假设我们可以为仅仅读取数据元素的事务获取一个锁,而对于写入或修改相同数据元素的事务,则使用排他锁。我们将按照严格的事务顺序来进行操作,正如我之前所说的那样。所以,大家知道的,我们必须遵循锁定和解锁机制。

但我认为,在使用共享锁的情况下,多个事务现在可以继续进行,而不需要相互等待。在这种情况下,假设它们只是尝试等待,但并没有对数据元素进行操作。那么现在的问题是,我们有了不同类型的锁。那么,它们是如何兼容的呢?为了解决这个问题,我们发明了一个叫做锁兼容矩阵的东西。

这种情况的意思基本上是,假设一个事务已经在数据元素上获取了共享锁,然后一个新的事务出现并尝试在该元素上获取相同的共享锁。根据这个矩阵,有勾选符号基本上意味着我们会批准这个请求。如果你仔细想想,这在现实生活中是有道理的,对吧?

因为在这种情况下,我们有两个事务试图读取相同的数据元素。所以它们都应该能够继续进行,对吧?这样说有道理吗?当然,接下来的问题是,像你知道的,为什么这里会有一个交叉呢?嗯,想一想这个,实际上是什么情况呢?它对应的是一个这样的情况,其中一个事务。

如果一个事务试图写入另一个事务正在尝试读取的数据元素,那么我们不能允许这样做,因为如果允许,我们就会破坏可序列化性。因此,我们不会批准这种情况下的锁请求。同样的情况也适用于反过来的情况,对吧?所以不管谁已经拥有锁,情况都一样。

无论是,像你知道的,第一个事务拥有排他锁,还是第一个事务拥有共享锁,这并不重要。所以我们不会批准第二个锁请求。很酷吧?那么你可能会问,实际上这些锁是如何实现的呢?对吧?谁在跟踪这些不同类型的锁呢?

结果发现,在数据库系统中,我们有一个叫做锁管理器的东西,基本上就是做这些事情。在锁管理器内部,基本上是基于被锁对象名称的哈希表。如果这里的对象是表,那么我们就为每个表有一行。如果我们讨论的是两个帖子,那么我们就有一行…

拉取。然后锁管理器会跟踪当前谁拥有锁,以及当前有哪些事务在等待它。所以你看到一个例子,这里有两个数据元素,A和B。然后这里的授予集合基本上是在讨论哪些事务当前已经获得了对该数据元素的访问权限。所以你可以看到,在…

在第一行中,我们有两个事务获得了共享锁,而在第二行中,我们有一个事务获得了独占锁。等待队列基本上是在讨论哪些其他事务在等待它。所以你可以看到,事务三正在尝试获取A的独占锁。但由于,像你知道的,另外两个事务…

目前,由于共享锁的兼容性矩阵,我们无法允许这种情况发生。因此,我们必须等待,这里就有等待队列,基本上是让其他所有事务排队。所以当一个请求到达锁管理器时,它会检查这个哈希表来判断是否能够允许该请求通过。

如果没有人持有任何该数据元素的锁,那么我们可以直接将其插入到授予者集合中,然后让它继续处理这个业务。如果不是的话,我们就像,你知道的,将它放入队列中。然后你也可以想象一下升级锁,对吧?如果我已经拥有一个共享锁,那么现在,如果我…

如果我想写入相同的数据元素,那么我需要基本上将我的锁升级。我的锁,对吧,升级为独占锁。而这正是相同的机制。所以你可以,像你知道的,想象一下你如何解决这个问题。基本上看一下等待队列,看看是否有任何人正在尝试获取独占锁,或者已经持有该数据元素的独占锁。

基于此,我们可以判断是否要授予请求。到目前为止,有什么问题吗?是的,尼古拉斯。哦,对不起,我只是有个问题。那些像等待死锁(wait-die)和不等待(wound-wait)的策略,当你有多个锁模式时,它们是否仍然适用?好问题,嗯。

如果你有不同的锁模式,你仍然可以应用这些策略。当然,就像你知道的,在这种情况下,我们只有在其中一个事务尝试获取独占锁时才会应用这些策略,对吧?因为如果所有的事务都只是尝试获取共享锁,那么我们就让它们全部获取。

那也没问题。所以我们需要处理的唯一情况是,如果某人在试图对一个数据元素获取排他锁,而这个数据元素上已经有另一个交易持有排他锁或共享锁。谢谢。问题。很好。那么你可能会觉得我们所有的问题都解决了,对吧?但是事实证明,仍然有另一个问题。

所以现在我们讨论的不是正确性,对吧?我们讨论的是效率。所以这是一个典型的图表,当人们尝试绘制交易数量与吞吐量之间的关系时,吞吐量是每秒处理多少交易的一个衡量标准。所以显然,越高越好,对吧?但是你基本上会看到,在某一点,你知道。

性能开始下降。而之所以会这样,是因为我们有更多的交易时,它们彼此在这些锁上等待的概率就更高,对吧?

不管它是排他锁、共享锁还是其他什么的,问题是锁的数量,这些锁的数量或者说被引发的锁,等待的锁,会随着交易数量的增加而增加。所以你会陷入一个阈值问题,对吧?我的意思是,像这样。

你开始听到你的笔记本风扇开始转动,停止工作了,对吧?例如说。这是坏的。那么我们如何缓解这个问题呢?对吧?一种方法是,基本上做一些被称为细粒度锁的事情。所以这实际上回到我们之前讨论的,像是,知道在什么级别下这些数据元素应该是什么?对吧?我们可能会讨论锁定一个。

数据元素。它们应该是单个元组级别的锁吗?

或者它们应该是整个数据库级别的锁吗?所以如果整个数据库只有一个锁,那么你知道,我们就不会有阈值问题,因为只有一个锁。但这样的话,我们基本上就会杀掉效率。所以那样的话,我们一次只能处理一个交易,对吧?然后如果我们有很多不同的锁。

对吧?所以假设我每个元组都有一个锁,而且我有一百万个要在表格中对抗的锁。我们说的可能是有一百万个锁在使用。然后等待,再把它杀掉,死掉,还有其他所有事情,对吧?所以这一部分会导致我们在上一张幻灯片上讨论的阈值问题。

所以现在的问题是,我们是否可以尝试改变这种粒度呢?对吧?因为如果在不同类型的锁之间存在一个广泛的选择范围,那么这可能是解决这个问题的一个好方法。但其实很难决定锁定什么的优先级,对吧?因为我们可以在不同的级别锁定东西,但接着。

我们已经理解了,在是否我们想要产生资源争用,或者我们可能会经历资源争用之间存在一种权衡,另一方面,在任何时刻只有一个事务在进展,对吧?任何时刻。问题是,能不能实现两者兼得?

不知怎么地,我们希望能够在不同层次上获取锁,然后仍然能够避免通过动态选择我们要在哪个层次上获取所有这些不同锁的问题。问题就是这样设置的。解决这个问题的方法,正如我所说,是基本上在不同层次上拥有不同类型的锁。我们来定义一个。

不同粒度的层次结构。我们有两个帖子嵌套在一个表中,并且有表嵌套在数据库中,例如。如果我们有一个这些不同数据元素的层次结构,那么我们可以将其表示为一棵树,然后我们可以从最上层开始,按需一直向下获取锁直到单个元组,对吧?所以现在,当我们尝试在这个世界中。

对,关于锁的层次结构,我们需要有一个规范,明确当我们尝试在这个世界中获取一个锁时,它意味着什么。所以我将随便做个决定,如果我们锁定了我在这里展示的这个树中的一个节点,那么我们实际上就是隐式地锁定了它所有的后代。为了检查我们的直觉。如果我获取了这个锁。

整个数据库,基本上是根节点,这基本上意味着我正在锁定树中的所有内容,所有的表和所有的元组。然后,如果我在表中的某个页面上获取锁,那么我隐式地也锁定了每个在这里作为后代的单个元组。我实际上并没有显式地获取。

锁定这些元组的锁,对吧?因为如果我们最终这样做,那么我们不如直接为每个元组设置锁。但我们基本上是在构建这个层次结构,以便当我们在高层次上获取锁时,这就意味着我们也会隐式地获取低层次的锁,尽管在代码中我们没有显式地这么做。

这有意义吗?对,所以现在我们有了不同类型的二进制,对吧?当我们尝试锁定街道中的任何东西时。比如说,如果我们尝试在最低层次上获取锁,比如元组层次。那么我们可能会有很大的开销,甚至可能遇到一个阈值,对吧?

和之前相同的问题。如果我们在更高的层次上获取锁,那么我们就可能通过这样做避免其他事务的进展,对吧。所以我们基本上是在失去并发性。在现实世界中,你会发现,实际上有很多不同级别的锁。所以在上一张幻灯片中,我只展示了。

你知道,像是四个不同级别的层次结构。实际上,你可以获取许多不同级别的锁。然后,这一切都取决于不同的实现。但现在有了这个新概念,我们仍然需要讨论,如何在这种情况下获取锁,对吧?我们有许多不同的级别。然后。

理解是,如果我在更高层次获取一个锁,那么在下层的所有锁也会被获取,没错,这是理解方式。但是,我们如何告诉其他事务,我们要获取哪个锁呢?解决这个问题的方法是发明另一种锁类型。

通常称为意图锁,基本上理解是,在获取共享锁或独占锁之前,事务必须先在该锁层级结构中的所有祖先上获取意图锁。例如,如果我想在页面级别获取锁。那么请注意,在这种情况下,我正在页面级别获取一个共享锁。

我首先需要在表级别和数据库级别获取一个意图锁。再次强调,理解是,如果我有一个意图锁,它基本上是在告诉其他事务,我将在树形结构的某个更低层次获取锁,对吧?所以如果你看我这里获得的第一个锁,没错,是在数据库层级上的意图锁。

它基本上是在告诉一个事务,“嘿,看看这里有人在工作。”而且有某人在层级结构的较低层次上工作。所以只是为了确保你知道,像是有事情发生了。对,基本上,基本上,它们现在将有三种不同的锁。

这是对我们之前讨论过的共享锁和独占锁的补充。在树的更低层次,还有获取共享锁的意图,通常称为最终粒度(final granularity)。还有获取独占锁的意图。并且,还有同时获取共享锁和获取独占锁意图的情况。

所以我们应该理解,SIX基本上是获取一个共享锁并加上获取独占锁的意图。那么你认为这有什么用处呢?你能猜到为什么我既想获取一个共享锁,又想在层级结构的某一特定级别上获取独占锁的意图吗?

你可能在做读写操作。所以,你想在读取时使用共享锁,然后写入时,比如使用IX(意图排他锁)、X锁。完全正确。所以也有很多人提到过这个问题,对吧?是的。所以你可能正在读取某些内容,然后稍后决定实际进行写入。

获取排它锁,对吧。一个好的例子就是,比如说,我想读取所有名为Alvin的学生,然后给他们在这门课上打个A。对吧。所以你首先想要读取学生数据,对吧,但学生的名字。然后你可能最终会像你知道的那样,改变那个层级结构中的一个记录。

好的,是的,人们现在在更改名字。好的,酷,对吧?所以现在我们有点更复杂了,因为现在我们有不同的锁模式。我们不仅有共享锁和排它锁,还有这些不同的意图锁。那么问题就变成了,我们怎么推理什么锁与什么锁是兼容的?

所以我在这种情况下做的基本事情是,考虑它们在没有意图锁的情况下如何交互。然后你应该能够推理出,像你知道的那样,如何填充我在这张幻灯片上展示的兼容性矩阵。所以我将提出以下声明。

如果我想在某个特定节点上获取共享锁,或者是意图共享锁,或者是意图,嗯,去获取一个共享锁,那么我必须在父节点上持有IS或IX节点锁。然后如果我想要获取一个排它锁,那么我必须持有排它锁的意图锁,或者持有父节点上的SIX节点锁,作为SIX锁。然后我会按照从底向上的顺序释放锁。然后,再次这样做。

对吧,我们将像以前一样进行2PL或直接2PL。我宣称这个协议是正确的。如果我们基本上遵循这种特定机制,知道何时行动,抓取什么类型的锁,什么时候实际上释放它们。看我们。你有问题吗?是的,我仍然对意图锁的目的感到困惑,为什么我们要使用它们。

是的,基本上目的是让我们能够允许多个事务同时进行进展。我们希望多个互不干扰的事务能够同时取得进展。对吧?所以如果我们回到之前的层级示例,比如说,如果我们在数据库层级只有一个锁。

那么基本上,我在这种情况下就是杀死所有的并发性,对吧?我不允许任何其他事务。任何时候只有一个事务能进展。对吧?但是如果我只在最低级别进行锁定,在元组级别,那么,你知道的。我可能会最终陷入一种死锁或冲突状态,因为你知道。

所有的事务都在试图获取锁,然后没有人能够进展。这就是情况。所以如果你只在最低级别锁定,为什么会发生这种死锁?

因为如果我有很多两个帖子,那么已经有很多锁对象了,对吧?

记住,每个锁都是唯一的,对吧?它是一个独立的变量。所以我们会有很多事务,跟踪并检查所有这些不同的锁,对吧,这些锁可能已经被占用,也可能没有。而且,仅仅是检查,运行所有的检查,对吧,已经消耗了 CPU 资源,而不是实际在数据上执行工作。谢谢。是的。

所以,想法是,如果我们允许事务在不同级别上根据安全需要获取锁,那不是很好吗?对吧?这就是我做的相似性问题。是的。是的,我只是补充一下。所以,Lucas,如果你想象一下,假设你有一个事务,假设它没有在层级上获取意图锁,而是直接去锁定。

leaf 元素,对吧?现在你有另一个事务,想要对整个数据库做一些操作,对吧?假设你想改变数据库的模式。现在这个事务需要知道,有人在 leaf 处对一个特定的元组做了一些改变,这样他们才能协调各自的访问,对吧?

所以数据库系统可以协调它们的访问。拥有在层级的不同级别上的意图锁,可以让这种协调更加顺畅地进行。这样说得通吗?是的。谢谢。好的。太好了。正如我之前说的,对吧?

所以我们现在需要弄清楚,什么时候锁定,应该获取什么样的锁,以及何时获取。然后我将遵循这个协议。如果我想在层级中的任何一个级别上获取锁,我必须在更高层级上有意图锁。然后,关于共享锁和独占锁之间的差异,也是同样的道理。

锁。不过,问题是,这个锁的兼容性矩阵应该是什么样子的呢?

比如,如果某个事务有意图,持有 IS 锁,而另一个事务尝试获取 Ix 锁,假设你知道,在树中的某个特定节点,我们应该允许这个操作继续执行吗?关于这个问题,正如我之前说的,想一想,应该发生什么情况,比如我们有两个元组,像你知道的那样。

例如在单页上。好的。假设在这种情况下,我们想要推理 Ix 锁是否与页面级别的 Ix 锁兼容。例如,这是什么意思?对吧。所以如果你跟随前一张幻灯片,某人获取了一个 IS 锁,对吧,意味着我将在两个页面之一上获取共享锁。

在该页面内的项,对吧?根据我们的协议。因为如果你需要对任意一项,两个项中的一项加共享锁,那么你必须已经在更高层次上拥有IS锁,对吧?举个例子。包含元组的页面。然后类似地,如果我想对其中一个项加独占锁,那么,你知道,根据上一页的约定,我必须已经在该页面上,拥有Ix锁,对吧?那么在这种情况下。我们应该允许这种情况发生吗?还是不应该允许?应该允许一个事务对

元组,然后再对与第一个元组位于同一页面的另一个元组加上独占锁。这可以吗?这允许吗?你们怎么看?是的,对吧?这不是个陷阱问题,顺便说一下。对吧?我的意思是,基本上是说,两个事务正在尝试操作两个不同的项,并且它们正在尝试获取锁,对吧?一个有共享锁,因为它是。

读取它。另一个有独占锁,因为它正在写入。嗯,我是说,这两个项本来就是不同的,所以不管它们是读还是写,都没关系,对吧?

但这基本上意味着我们可以在页面级别允许IS锁和Ix锁是兼容的,对吧?所以记住,这个IS和这个Ix锁是在页面级别,而不是在元组级别,对吧?

所以这基本上意味着我们可以在同一页面上同时授予IS锁和Ix锁,对吧?同样,当然,在同一个表中也是如此,对吧?同样,当然,在同一个数据库中也是如此。这样说清楚了吗?如果你不明白我意思的话,对吧?那么,正如我所说,推理一下,按元组,单个元组级别来分析。

然后尝试想出一个场景,看看你知道吗?你能看到哪个请求被允许还是不被允许。然后推理的根本方法是,思考我们是否可以允许像这样的事情发生,对不起。我们是否可以允许这样的情况发生,一个元组上有共享锁,而另一个元组上有独占锁。

在另一个元组上加锁,对吧?现在假设我想推理两个Ix锁是否兼容。想一想,这种情况会是什么呢?这意味着一个事务对T1加上了独占锁,而另一个事务对T2加上了独占锁,对吧?而且由于这两个元组完全独立,这种情况。

应该被允许,对吧?所以这基本上意味着Ix锁应该与同一页面上的另一个Ix锁请求兼容。不是同一个元组,对吧,而是同一页面上的。你需要记住这一点。有关于这方面的问题吗?好吧。所以一个父锁可以有多个意图锁,对吧?是的。假设你。

如果你在一个页面中尝试读取两个不同的元组,你只需要一个Ix锁,还是需要为每个元组分别加两个Ix锁?一个,等等,抱歉,是页面还是元组?嗯,是的,你尝试读取同一页面中的两个元组。那么对于那个页面本身,你只需要一个Ix锁,因为你只在读取这两个元组?我说的是页面上的锁。

两个元组。是的,所以页面级别只有一个Ix锁,就像元组级别只有一个锁一样。所以每个锁都有它自己的锁,抱歉,每个元组有它自己的锁,每个页面有它自己的锁,每个表有它自己的锁,每个数据库有它自己的锁。好的,谢谢。那么我们现在讨论的是是否应该批准同一页面的Ix锁请求。

而其他事务已经持有相同页面的Ix锁。所以这是不是意味着一个父节点最多可以持有三种锁?比如IS锁、Ix锁和SIX锁?

好的,这基本上意味着它是一样的,对吧?所以每个页面目前都会有一个锁表,就像我在上一张幻灯片上展示的那样,然后它基本上会显示当前哪个事务对这个特定页面持有什么类型的锁。一些事务会对该页面持有IS锁,另一些事务会持有Ix锁。

例如,对于该页面的锁。但是你能否叠加不同的锁?就像你在屏幕右侧写的,页面同时有IS锁和Ix锁?

是否有可能同时拥有它们,再加上SIX锁?哦,你告诉我吧,对吗?

所以你现在基本上是在问,在这种情况下应该发生什么,对吧?

然后我尝试推理一下,对吧?假设我有一个事务已经对页面P持有IS锁,另一个事务则持有Ix锁,然后第三个事务过来,假设说:“嘿,我想要在这个页面上获得SIX锁。”我们应该批准吗,还是不应该批准?对吧?我声称应该批准,因为你可以看到,已经有一些事务持有该页面的Ix锁。

这个框里的值是“真”。然后这个框里的值是“假”,基本上意味着如果某个事务已经对该页面持有Ix锁,那么我们就不能允许通过SIX锁请求。好的。所以这个表格展示了,如果你在父节点中持有这些类型的锁,那么还可以存在其他哪些锁。嗯,好的,谢谢。是的,这就是为什么这里有一个。

锁的兼容性矩阵。哦,所以你可能会想,好吧,我们有五种不同的锁,这已经让我有点害怕了,对吧?你知道,我得推理出25种不同的组合。你说,认真吗?我的意思是,试着推理一下,这有点像现实世界中的情况,对吧?所以你知道。

你可以获得的锁的类型肯定多得多。而且当然,矩阵就像你知道的那样,不断扩展。因此,就像你知道的那样,我们也需要能够推理这些内容,对吧?但幸运的是,如果你做了功课,没错,那么基本上你只需要推理每个框一次。然后它就行了。

然后,这个矩阵在对角线两侧也是对称的。所以它基本上是彼此的镜像。所以你只需要推理这个巨大表格的一半,而不是整个表格,如果这样能让你更开心的话。好的,像你知道的那样,这个问题就到此为止,除了我们仍然有……

还有另一个问题,叫做幻读问题,对吧?我认为这是个好时机,因为在10天后我们将迎来万圣节。遗憾的是,这与那种幻读问题无关。它从某种意义上来说更无聊。问题如下:到目前为止,我们假设数据库基本上是两本静态书的集合。没有任何变化。

对吧?我们没有在主动插入或删除东西。所以如果我们开始在事务中插入和删除东西,那么这个问题就会出现。这是什么意思?这里有一个例子。所以我有两个事务。第一个事务试图选择所有的蓝色产品,第二个事务……

尝试插入一个新的蓝色产品。然后第一个事务出于某种原因决定再次发出相同的查询,对吧?所以我们先问一个问题,这个调度是可串行化的吗?

答案是否定的,因为T1实际上看到一个正在插入的新产品,称为A3,对吧?所以这与任何你能想到的T1完全执行在T2之前或反之的排序方式都不等价。因此,这不是一个可串行化的调度。现在,问题是,这实际上是一个冲突可串行化的调度吗?如果你还记得……

在DTS讲座中,进行这个测试的方式基本上是尝试重新排序这两个事务中的操作,然后看看我们能否重新排序到一个点,在这个点上T1的所有操作都在T2的所有操作之前,或反之亦然。所以在这里,我假设数据库中已经有两个蓝色产品。

也就是A1和A2,然后A3是T2插入的新产品。所以到目前为止,这不是一个冲突可串行化的调度,对吧?

因为我们有操作在T1和T2之间交织。但是,由于A3与A2或A1无关,根据DTS在讲座中讲解的规则,我们实际上是允许这样做的,对吧?

要将A3的操作,具体来说,是在对A1和A2的读取操作之前。因此,所有T2的操作都需要移动到T1之前。因此,正如我们之前讲座中提到的那样,这实际上是冲突可串行化的。但是我们仍然知道这里有一个问题,对吧?所以这不好。

所以这要么意味着冲突可串行化性有问题,要么意味着DTS有问题,对吧?所以我们不能使用这个方法,不能用这个测试,对吧?当然,像你知道的,他是对的。所以,像你知道的,这个测试实际上允许我们进行冲突可串行化性检查。基本上就是这样。

就是当我们现在谈论的是在一个事务中插入或删除时,就会发生幻影问题,且冲突可串行化性不再成立。这就是一个大问题,对吧?因为我们原本希望它能帮助我们,但结果却像是全盘皆输。

当我们处理这些动态事务时,这就成了一个问题。它们不仅仅是读取或写入单个元素或现有元素。我们讨论的是事务插入和删除数据的情况。所以这很糟糕。好的,那么我们如何解决这些幻影问题?快速解决方法:锁住整个表。

锁住整个表,对吧?这就太棒了。所以如果我们锁住整个表,没人可以继续操作,那也意味着,对吧?你可以随便插入和删除,幻影问题就没有了。但我们已经看到,这样做是有问题的,对吧?

所以我们尽量不这么做。另一种做法是,如果有索引,我们可以锁住索引,对吧?因为如果有索引,我们试图插入数据。那基本上意味着索引会变得过时。所以如果有人试图通过索引来读取数据,像你知道的,那时候过程就会有问题。

该操作将被阻塞,对吧?所以我们将能够防止之前提到的问题。或者你可以做一些称为“等待块”的操作,基本上就是先进行一次预演,确定我要读取的是哪两个帖子?

然后,一旦我确定了要读取的两个帖子,我会在实际读取之前先对这两个帖子加锁。所以我不会在这里详细讲解,但你可以想象,处理这些幻影问题非常昂贵,对吧?因为这些方法都不便宜。

解决方案可能并不像我们在课堂上之前讨论过的那样简单或干净。如果你正处理的情况涉及到可以插入或删除数据库中的事务,那么这就是你可能需要解决的问题。或者你可能会说,随便了,我不在乎。我不保证你能做到。

你可能看不到幻影。我是说,现在我正在实现的这个数据库中,你可能会看到幻影。所以如果是这种情况,那就挺难受的,对吧?你也可以选择忍受它。所以这由你来决定。好的,这就是我想说的全部内容。总结一下,我们基本上讨论了这些讲座中不同类型的可串行化性问题。

然后我们讨论了几种不同的方式,实际上如何处理或确保我们具有冲突串行化。确保你理解它们之间的差异。最后,我们讨论了“幻像问题”或动态数据库的概念,其中冲突串行化不再成立。

总的来说,我们讨论了什么是可串行化的正确性概念。这是许多系统提供的功能,确保事务的资产(ACID)特性得以保留。我们谈到了使用锁来确保可串行化,并且通过不同的方案或协议来确定何时获取锁以及应该获取什么类型的锁。

我们可以通过这种方式确保可串行化。然后我们还讨论了效率方面的问题,以及如何缓解出现冲突时可能发生的抖动(thrashing)。在这门课中,我们谈到了通过在多个粒度级别上使用锁来实现这一点。正如我之前所说的,对吧?

还有许多其他种类的并发控制机制,并不使用锁。所以,如果你感兴趣,你可能也想了解这些内容。如果你想了解更多这方面的信息,我们也可以私下讨论。这就是我们目前关于事务的所有内容。稍后在课程的最后部分。

在这门课程中,我们实际上想要重新审视我们一直在讨论的所有事务,特别是在并行数据库的背景下。也就是说,当我们有多个数据库同时运行时,事务该如何进行?不过现在,我先停一下,让你们提问,我将准备下一部分内容。Felix。是的,之前有一个陈述。

他们说一个事务必须在获取它所需的锁之前,先获得所有祖先上的所有意图锁。似乎就是这样。然后在我们讨论锁兼容性矩阵时,记得我们举了一个例子,两个事务分别在页面级别上拥有意图锁,并且它们在处理…

他们在不同的元组上拥有各自的 SNX 锁。在这种情况下,像是什么先发生呢?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_3.png

当一个事务想要获取 S 锁或 X 锁时,事务是首先尝试获取所有的意图锁吗?还是它会先获取到自己想要的对象,然后再进行处理?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_5.png

然后,事务获取 S 和 X 锁时,是否可能出现事务首先获取了意图锁,但最终无法获得它所需要的 S 或 X 锁的情况?是的,太好了。我现在没有屏幕,但我想有一张清晰的幻灯片可以讲解这些锁的获取顺序。如果你回顾一下那张幻灯片,或者基本上…

告诉你在可以继续向下层移动之前,你需要先从层级的最高层获取锁。这个顺序不是随意的,正是因为他们提到的问题。对吧?所以你可以抓取所有你想要的意图锁,直到你到达最终点,而你可能会意识到哦,有人已经在同一个元组上持有独占锁了。

尝试获取独占锁。到那时,我们基本上就停止了,对吧?

或者我们只是等待。所以获取锁的顺序确实很重要,你基本上需要在层级的最上层进行操作,然后按相反的顺序释放锁。明白了。所以即使在这个例子中,我们也在构建一个例子,像是证明或者展示你可以有一个Ix锁和一个Ix锁。

仍然有可能发生这样的情况:一个事务已经有了Ix锁,但它并没有实际拥有该表的S锁或X锁。对吧?好的,谢谢。是的,当然。还有其他问题吗?是的,我在聊天中问过这个问题,但由于哈希表和树也被不同的线程访问,当你知道它们发生并发时,数据结构也必须被锁定。

是的,通常这样做的方法是让锁管理器控制所有这些不同的请求。所以有一个组件接收所有这些请求,然后你可以把它想象成一个接收不同请求的队列,这些请求需要由锁管理器进行验证,锁管理器基本上对哈希表有独占访问权限。

不同的锁。所以锁管理器基本上必须是串行化的,或者像单线程一样。单线程。对。好的,谢谢。你可以有多个线程锁管理器,但你仍然需要为表中的每一行设置锁机制,对吧?所以没有两个线程会在同一时间尝试操作同一把锁。

很酷,谢谢。所以我想我们答应了一个小休息。我不知道我们是否还应该继续,或者你知道的,我们不如休息几分钟。我觉得我们还在找TikTok或者其他的猫咪、表情包。嗯。所以我想我们确实有一些答案,我想我们现在就播放它们,然后,好吧。

我们做吧。好的,那你应该……你应该像这样,好吧。结束我的任务。对吧?所以你们可以判断,像是你知道,这些有什么问题。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_7.png

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_8.png

很有趣,而且它们相对较短,不幸的是。然后我不确定。所以这是其中之一。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_10.png

我们收到的东西。就只是这些,对吧?好的,很好。然后让我们看看。我们这里还有一个条目是一个表情包。我猜是在讨论效率问题。太好了。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_12.png

这个,我猜是比较有趣的。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_14.png

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_15.png

我可以关闭笼子驾驶吗?哎呀。然后其实是有声音的。我不确定你们是否真的能听到。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_17.png

好的,让我再放一次。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_19.png

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_20.png

是的。好吧,我希望你们现在的感觉不是这样。我不知道。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_22.png

然后最后一个问题是关于期中考试2的,它是如何与我们课程相关的?

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_24.png

不确定那是什么意思。好的,差不多就是这些了。就像我之前说的那样,对吧?我意思是,我们还在征集新的视频。如果你们想休息一下,我是说,你们最好提交一些好的内容,明白吗?否则我们就会继续推进,这对任何人来说都不有趣。

所以我查看了我们收到的提交,基本上,Alvin已经播放了所有的提交。所以我们真的是在寻找更多的提交。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_26.png

是的,好吧,我们开始一些公告。我想我们仍然在努力修改一些部分,但我不记得那方面的更新是什么了。Alvin,你记得吗?

我记得我们决定可能合并其中一到两个部分。我忘记了。Jerry,你在吗?是的。Amy的部分取消了,和Jennifer的部分合并了,这也是他们开启法律部分的原因之一。是的。太棒了,谢谢,Teddy。是的。所以Amy和可能是Jennifer在主持法律部分。

所以他们基本上会更详细、慢慢地讲解事务的概念和并发控制。所以如果你们希望在这些概念上得到更多的帮助,或者更有指导性,我鼓励你们去参加这些部分。这些是非常难的概念。我鼓励你们去参加法律部分。我觉得这应该是一个……

我相信这个部分大约有R和一个半小时。所以Jerry发布了关于期中考试2的信息。基本上,它和期中考试1的设置相同。所以,是的,我希望你们有一些基于期中考试1的练习,至少对考试的设置有所预期。唯一的注意事项是期中考试2的题目范围。

和以往几年期中考试2的题目范围稍有不同。所以可能会有一些问题,你们可能会想,“嗯,我完全不知道怎么回答。”嗯,这可能只是因为你们还没有覆盖到这个话题,它不在我们的期中考试2大纲中。好的。所以我还想提到的是,本周没有办公时间。

哦,对了,我仍然在找,Alvin,我觉得今天Alvin进行了一个概念性的幻灯片讲解办公时间。这基本上是一个小型讲座,你专注于…我不记得Alvin讲了什么。是讲联接算法吗?

今天的联接算法和活动。好的,联接算法和选择性。我注意到Piazza上有几篇关于查询优化的帖子,讨论了查询计划和计算IO的内容。我考虑在办公时间讲解这些,如果有其他请求的话。所以基本上,我原计划要做的,我不知道Alvin做了什么,但这是我原本的计划。

目的是根据你提出的问题来浏览幻灯片,并集中关注可能的误解。就像是想,“嗯,这个值为什么是这样?”我可以坐下来,尽量详细解释。这就是我们的目标。所以我们要确保大家能够感受到一些同学遇到困惑,没能理解某些内容。

所以我们希望帮助你们解困,尽快赶上进度,恢复正常节奏。这也是我们在使用办公时间时的目标。接下来,我想提到的是期中考试一的一些问题。我们发现期中一有一些后勤问题。所以基本上,我们提供了。

这里有一堆指示说明,实际上有不少同学没有严格按照说明操作,虽然这些问题完全可以理解,因为这是你们第一次进行这样的操作。但问题的种类从小的,比如没有展示SID或面部信息,去上网,录制中途掉线,直到一些更大的问题。

有些更大的问题,比如完全没有录音,额外的屏幕,额外的设备。这些都是后勤问题,希望你们在期中二时能够避免。另一个让我们感到遗憾的发现是,可能存在学术诚信的问题。我们目前没有确凿证据,但还是有潜在的学术诚信问题。

我们仍在调查此事。我知道你们中的大多数同学是以学习内容为目标来上这门课的,所以这件事我们会调查,但我们也在寻找评估和验证这些情况的方式。我们不是想让大家感到压力,我们只是想确保公平。

对于那些有后勤问题的同学,为了避免出现这种验证问题,请尽量找出解决方法。如果你在找解决方法上有困难,我们随时可以提供帮助,对吧?如果在期中考试安排上有什么让大家困扰的地方,我们也会处理。

我们很高兴改变这个。但我们希望知道。如果有具体的事情导致了我们讨论的后勤问题,请随时告诉我们。如果你感到困惑,或者在理解内容时遇到困难,告诉我们,并告诉我们怎么帮你。有一件事。

我们还没有做的是查看你们的期中调查。所以可能很多人已经给了我们有用的反馈,我们可以利用这些反馈来改进我们内容的呈现以及项目和各个部分。我好像看到有举手。是举手吗?是我。但我觉得我的问题已经得到了回答。

我们原本会在这里收集反馈,以防我们不小心违反了某些规则而没有意识到。然后,我猜答案是否定的。所以我认为我们可能做的事情是,只是简单地列出常见问题。我认为我们会通过视频进行抽查,尝试识别可能的问题。但当然,我们无法做到面面俱到,对吧?

我们不能坐在那里,盯着你们每一个人完成考试,像是花两个小时或其他时间,整个考试。对吧?我们无法做到这一点。所以我们不可能对这些问题做到面面俱到。所以我不想承诺这一点。我们能做的,当然是列出我们在视频中发现的常见问题,并将其分享给你,以便。

这可以是一个便捷的提醒,比如“嘿,显示你的SID,展示你的脸,确保录制是这样的,确保你的手机已连接充电器,等等。”对吧?所以我们当然可以在之前发出这样的提醒。Jerry,这听起来怎么样?

有什么要补充的吗?Jerry负责考试相关的事宜。是的,我认为没问题。聊天室里有个问题,我们希望大家查看视频。可以,直接来我们的任何办公时间。我想,和之前一样,对吧?所以我认为我们之前也提供过,甚至是在第一次期中考试之前就有提供,基本上是检查你的,知道了,提供帮助。

你能检查你的设置,等等。所以同样的,你可以选择直接参加任何一场办公时间。我想还有一个问题是关于我们如何处理你的视频。你的这些视频,第二个承诺是我们使用它们的唯一方式,就是进行抽查,仅此而已,对吧?

一旦我们完成了抽查,一旦我们弄清楚了哪些人可能需要进一步审查,我们就会将他们移除。我们不再需要访问他们的信息。所以当我们不再需要访问时,我们会通知你,你可以直接撤销访问权限,如果你愿意的话。我认为系统也设置了,可能在30天后自动过期。

或者其他情况。是的,至少Zoom,Zoom帐户基本上会在30天后删除视频。好吧。好了,我们开始讨论恢复。我没有太多时间来讲解恢复,因此我们将尽力而为。好的,退后一步,Alvin已经讲过并发控制,基本上,回顾一下剩余的事务。

这些保证,即事务需要满足的所谓ACID属性。首先是原子性,如果你还记得的话,它基本上意味着事务中的所有操作要么全部发生,要么一个都不发生。一致性则意味着数据库状态是一致的,遵循某些约束条件,例如,事务结束后仍然保持一致性。

隔离性是这些锁定方案所保证的,它确保一个事务的执行与其他事务的执行相互隔离。持久性基本上是说,如果一个事务提交了,那么它的效果将会持久化,超越该事务的生命周期。所以今天,我们将开始讨论恢复管理器。恢复管理器负责确保。

原子性和持久性。另外,回滚那些违反一致性的事务。好的。所以原子性的动机是事务可能会中止。无论是请求中止,还是系统中止它们,我们都希望确保它们的效果不会被持久化到数据库中。因此,我们真的希望要么事务的效果完全没有存在。

所有的效果都会被保留。持久性基本上是确保如果数据库停止运行,已提交的事务效果仍然存在,永久保存。所以在这个例子中,你有几个事务已提交和中止,然后发生了系统崩溃。那么我们在这种情况下希望什么?崩溃后。

系统重启时,我们希望这些已提交的事务T1和T3的效果是持久化的。而T2、T4和T5应该被中止。因此,要么T2已经明确在崩溃前中止,要么T4和T5在崩溃时是未完成的事务。

我们希望它们的效果被回滚。所以我们也希望它们在崩溃后被中止。因此,当系统崩溃并恢复时,我们希望任何在崩溃时未提交的事务,其效果不应在恢复后显现。那么我们来谈谈两个问题,关于为什么。

数据库系统为什么会中止运行?接下来我们将讨论确保恢复的机制。那么为什么事务会中止呢?首先是用户应用程序明确表示,我不再继续执行这个事务。另一个事务可能中止的原因是在事务执行过程中,如果数据库确定有完整性约束。

如果违反了某些约束,它可以中止一个事务。就像是,嘿,你违反了这个主键约束,外键约束,等等,数据库系统可以说,看看,我要中止这个事务,那个事务的任何效果都不应该被保留。它也可能由于死锁而发生。阿尔文提到的那些方案,你当然可以查看。

你可以尝试将一个事务作为循环的一部分,然后导致它被中止。你也可以因为系统故障而中止一个事务,前提是该事务在成功提交之前未完成。因此,如果系统本身失败,那么这个事务还没有到达提交状态。我们将在系统恢复后中止该事务。

所以我相信我们之前也讨论过这个。关于 SQL 中的事务,你应该使用 begin transaction 命令来开始一个事务,然后你使用 commit 来提交该事务的效果,如果你想回滚该事务的效果,就使用 rollback。所以这就是你如何通过 SQL 与数据库进行事务交互的方式。

你还有这个方便的保存点概念,它允许你在事务中保存状态。所以你可以通过 save point 并给它一个名称来声明一个保存点。然后你可以释放一个保存点,基本上是说删除一个保存点,就像保存点从未存在过一样,使用这个 release save point name 命令。

然后你也可以使用这个 rollback to a save point 命令回滚到之前的保存点。这意味着你在这个事务中采取的所有操作,回滚到并包括该保存点,都将被撤销。所以这些保存点是一个方便的工具,可以将一个较大的事务拆分为多个组件。

这些是单独保存并且可以回滚的。所以在这个特定的例子中,你有一个大的事务和一个提交,并且在其中有一个插入操作,然后声明了一个保存点,基本上允许你回到这个点,再进行另一个插入操作,然后在这里我释放了这个保存点,这意味着几乎就像这个保存点从未存在过。好的,然后我有。

另一个保存点。我声明了另一个保存点,添加了某些内容到这个表中。然后我说,所以我向这个表添加了一个空值。现在我说回滚到保存点,这意味着所有这些都会被删除,对吗?这些效果会被删除。最后,我还有另一个插入语句。所以我基本上有了是的,一个,两个。

并且作为该表的一部分,三。好的。因此,释放和回滚只是一些方便的操作,允许我回到事务中的特定点,并撤销事务的部分效果,而不撤销整个事务。好的。那么让我们讨论一下其他可能导致事务中止的机制,比如违反完整性约束。

所以在这里,我展示了一个完整性约束导致事务回滚或事务被中止的例子。这里我声明了一个sailor表和一个reserve表,作为我的典型例子。我还从reserve表到sailor表有一个外键约束。我已经将pop I插入到sailor表,并且在reserves表中也插入了一个指向同一条目的元素。

sailor ID。好的,然后我决定开始我的事务。我执行begin,然后我决定。我想从sailors表中删除pop I。当我尝试这样做时,这将导致一个完整性约束违规。对吧?因为通过删除pop I,从reserves表中删除指向这个sid值(123)的指针。

所以我不能删除具有该sid的sailor。这样就会立即导致这个事务被中止。好的,所以这个事务基本上就是被中止了。数据库给我返回了一个错误。这是postgres,并且它说:更新或删除blah blah。外键约束违反,表格reserves。现在,在我的任何其他语句中,

之后的事务也不会被执行,基本上会被避免。因此,插入到sailors表的值。blah blah。橄榄油会被忽略。这是因为数据库系统再次告诉我,当前事务已被中止。所以事务块结束之前的所有命令都会被忽略。所以,如果我执行select star from sailors,我发现并没有成功删除pop I,也没有插入。

这个特定案例中的橄榄油问题。好的,这是一个事务回滚的例子。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_28.png

好的,我们来谈谈持久性。为什么数据库会崩溃?对吧?在今天这个时代。我们可以想出很多原因为什么数据库会崩溃。可能是火灾,可能是疫情,也可能是其他形式的弹窗。但更严肃的来说,还有一些更加普通无聊的原因,为什么数据库可能会崩溃。比如,

也可能是操作员错误。比如你可能会不小心踩到电源线。这可能导致服务器断电,系统崩溃。对吧?你可能会输入错误的命令,这也可能导致系统崩溃。也有可能是配置错误。你可能会耗尽空间。也可能是,

内存问题。也可能是文件权限问题。你写入堆文件的文件可能会被损坏。权限可能会被其他进程更改。也有很多奇怪的事情可能会出错。也可能会有软件故障。这个不太可能,但也是有可能发生的。比如VBMS的bug。如果你听说过SQL注入,

攻击,这是一种与数据库的对抗性互动,可能导致数据库崩溃。你可能会遇到操作系统的错误。所有这些都可能导致数据库崩溃。你还可能遇到媒体或服务器故障。所以你可能会遇到磁盘崩溃等问题。好了,让我们开始讨论恢复。我们会尽量做我们能做的,虽然我们不会走得很远。

但是在讨论恢复时,我们将假设我们拥有 Alvin 描述的并发控制方式。所以我们将使用严格的 2pl(两阶段锁协议)。我们还将假设更新是在原地进行的。所以数据,这是标准做法。数据在缓冲池中被修改,并且数据库中的页面被覆盖。所以这些事务并不是在数据的私有副本上进行操作。

所以你从磁盘中读取这些页面,然后修改这些页面,再将它们写回磁盘。现在,恢复中的挑战当然是你有这个缓冲区管理器。所以更改首先是在内存中执行的,然后再写入磁盘。由于存在不连续性,你并不是直接写入磁盘。

正是这种不连续性使得恢复变得复杂。在这个上下文中,不连续性就是使恢复变得复杂的因素。所以简要回顾一下缓冲区管理器的角色,给定一个磁盘,你的缓冲区管理器有多个帧,或者说缓冲区管理器管理着缓冲池,每个缓冲池由多个帧组成。这些都在主内存中,每个帧可以存放一个磁盘页面。这些页面是按需请求的。

磁盘。所以你的缓冲池中可能有许多已经填充了磁盘页面的帧,也有一些尚未包含页面的空闲帧。随着页面请求从栈的更高层次发出,数据库系统栈就会发出读或写操作,这会导致页面从磁盘输入或输出。

所以这是我们在推理恢复时所操作的粒度级别。在我们的上下文中,通常一个页面对应一个磁盘块。所以这些是之前我提到的操作,我们将要处理的操作。你有一个读操作和一个写操作。

这些是在内存中的操作,输入和输出,这是从内存到磁盘,或者从磁盘到内存。由于我正好在时间限制内,也许我应该不再继续,而是应该开始回答问题。卢卡斯,这是关于数据库崩溃的原因幻灯片。其中一个原因是操作员错误,错误的命令。是不是这样?

这不是更像是软件故障吗?因为理想情况下,你应该写入数据库,使得错误输入被拒绝,而不是导致数据库崩溃,对吧?是的,所以这可能是一个配置参数的问题。这个问题与用户关系不大。所以这不太可能是因为比如我在 SQL 命令中打了个错别字,对吧?

这可能是因为系统管理员,数据库系统管理员,输入错误对吗?所以这更像是数据库系统配置层面的问题,而不是 SQL 查询层面的问题,这种情况更不容易出错。你是对的。你会捕捉到这些错误。所以例如,当你引用一个不存在的表时。

存在。我们会捕捉到这个错误,避免导致数据库系统崩溃。谢谢。还有人举手,Felix。是的,快速问一下,或许下节课会讲到这个,关于我们在图中看到的磁盘和缓冲区管理器。

是否存在一种区分,即在哪些部分,像是磁盘或缓冲区帧之间,关于恢复的线在哪里?因为我知道数据库管理系统是基于软件的。所以,如果发生磁盘故障,数据库管理系统是否仍然负责,像是,知道该怎么处理这种情况?是的。

所以磁盘故障在某种程度上超出了恢复管理器的范畴。磁盘故障通常通过其他机制来处理,例如冗余磁盘。所以你可能会使用某种读取设置来允许给定数据的多个副本,以便提供冗余。即使你有一个磁盘故障,其他磁盘也能提供数据。

磁盘可以依然保持其副本并且依然能够恢复。我们依然可以操作它。所以我们讨论的这种情况是更常见的。即软件故障,对吗?所以断开设备、软件崩溃,所有这些问题基本上都会导致你在内存中的操作瞬间消失。所以这是一种。

恢复是我们更常需要处理的情况。所以这也是我们接下来要重点讲解的内容。这样讲得通吗?是的,谢谢。我有一个问题,关于我想是第七张幻灯片,当你在做关于 Popeye 和 123 的 SQL 查询时。你在代码中展示了它。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_30.png

错误会在你尝试删除键时出现。这也解释了为什么会出错,因为现在 reserves 表引用了一个不存在的水手。所以这是否意味着,整个交易会在这条命令执行后被中止?因为当你尝试插入一个新的水手时,它显示错误,当前交易非常重要。

所以它是否决定,在你执行错误查询后,立即回滚或中止?没错,是的?

所以一旦你犯了这种类型的错误,交易就会被中止,因为你所得到的保证是:交易要么完全执行,要么根本不执行。所以你不能仅仅说,嘿,这个是个错误,我跳过它,做点别的事情。

其他的情况。因为我的意思是,如果你做了操作,而第三步出错了,对吧?

比如说,你转账,删除了一个账户里的钱,然后在将钱转到另一个账户时出现了错误。你不想让第一个账户的钱保持不变,而第二个账户却出错,对吧?

所以你基本上是说,数据库系统会说,我要声明这一切都不行。我会说第二步出现了错误。你弄明白了。告诉我从头做什么,而不是做一些部分性的操作。所以我,嗯,我的问题更多是关于。

它们是否始终检查,抱歉,它们是检查整个事务的一致性,还是只在结束时检查一次?

因为我认为的是,你在事务开始和结束时检查,看看你的一致性是否仍然存在,然后基于此决定提交或中止。但既然它是在中间出错,那是不是意味着它会在整个事务中都检查?是的,所以,这是个好问题,对吧?也许是不同的实现代码,例如。

避免在事务结束之前检查一致性。然后,因为你可以。例如,通过在后续步骤中添加一个合适的元组来修复你在这里遇到的完整性约束,也许这就是你想表达的。所以你可以让每个单独的 SQL 语句可能违反一致性,但整个事务并不违反一致性。所以,我认为。

这是一个实现选择。我认为在这里,和大多数典型的数据库系统中,你会在每个 SQL 语句之后检查一致性,因为那是你执行操作的粒度。而且你会在执行时检查一致性,而不是等到事务结束时再检查。

然后再检查一致性。因为如果这是一个非常长的事务,包含很多语句,对吧?那样你最终可能会等到最后才发现错误,而不是在每个步骤都检查。所以这种后者方式只是更有效率。嗯。我明白了。我原以为每次检查会更低效,因为你必须每次都检查。

但我认为这有道理,因为如果你能早点捕捉到错误。好的,非常感谢。是的。但我还想补充一点,对吧,但也必须有一个机制来解决这种情况。就像你现在看到的那样,对吧?因为如果它检查每一个语句,那么,就像你说的,你仍然需要一种方法来删除元组。总是会抱怨说,像是。

有外键约束,所以你不能删除它。那么你怎么能删除它呢?对吧?

因为,比如说,当你尝试删除另一侧的数据时,还会说,有外键约束,意思是某个地方引用了那个元组,所以你不能删除数据,对吧?所以一定有一种方式,允许你同时删除两者。

你是不是应该先删除外键约束,再删除水手,或者呢?对,这是一种方法,对吧?另一种方法是基本上指定聊天中输入的内容,对吧?所以,比如说,你可以说,当你定义表时,当某个数据被删除时,你还希望所有持有该外键的行也一并删除。

我说的意思是同时删除。这个是什么?对,所以你可以同时删除不同表中的多行数据。我们只需要写一次。对,所以除了这些约束外,你还描述了如果元组被删除或更新时会发生什么。

并且它们会如何影响正在被引用或引用其他表的多个表,对吧?

所以,举个例子,像Alvin说的,我不知道我是否没有跟上聊天,我也不知道提到什么了,但基本上你会说,如果从水手表中删除数据,也会导致级联删除预定记录,依此类推,对吧?或者,如果你更新水手表中的某个数据,将123更改为341,它会导致预定表的级联更新。

或者你可以说,在更新时,将预定值设置为null,对吧?

空值作为外键的值是完全可以的。所以这些都是你可以在模式级别指定的内容。如果你不能更改模式,也不能更改模式中指定的完整性约束,并且在更新和删除时会执行什么操作,那么基本上你可能最终需要删除预定记录来先行。

然后说,如果你不能更改那个设置,那就删除预定记录。好的,我明白了。非常感谢。抱歉我之前没搞清楚这些。我有点跳过了Preg’s部分。我是新生,所以没学过61C,有点困惑日志的部分,但我觉得我开始明白了。太好了。好的,如果有问题,就来所有这些时间段。非常感谢。

或许我们应该停止录制,Alvin。我这边没有停止。

https://gitee.com/OpenDocCN/cs-notes-zh/raw/master/docs/ucb-cs186-db/img/29600da32d3999ef340320b507cbdadb_32.png

当然。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值