likes
comments
collection
share

数据库的自然语言接口——数据和查询模型

作者站长头像
站长
· 阅读数 3

在本章中,我们回顾了一些数据库中的关键概念,以帮助读者更好地理解本书的后续部分。我们从数据模型的概念开始,数据模型可以被视为数据的语法,类似于使用语法为自然语言和编程语言定义的语法。它提供了一种描述数据约束、查询数据和数据操作的抽象。我们介绍了概念模型的概念,它描述了数据库的设计结构。

然后,我们描述了两种常见的数据模型:(1)关系模型,它将数据组织成称为“关系”的二维表的集合;(2)图模型,它将数据组织成图。我们使用示例介绍了关系模型和图模型的查询语言。最后,我们简要介绍了查询在数据库管理系统内部的过程以及一些组件,例如存储结构、查询评估和优化,这些组件负责在数据库实例上评估查询并检索结果。了解这些组件可能会对查询的成本和NLIDB的评估提供一些见解(第5章)。

概念模型

尽管数据最终以字节序列的形式记录在辅助存储器中,但在这样低物理级别上处理数据是繁琐的。另一方面,结构化数据通常符合描述数据类型和结构的模式。例如,为表的每一列分配一个类型可以确保存储的值在语法上是正确的。类型信息还有助于通过建立在其上的接口解析或解释数据。另一方面,结构描述了如何组织不同但相互关联的数据片段(例如,比尔·盖茨、哈佛大学、1955年和微软公司)以及它们关系的表达方式。

在高层次上,数据库的设计结构可以使用概念建模方案来描述,例如实体-关系(ER)图或统一建模语言(UML)。这些并不被视为数据模型,而是设计方法论,更正式地描述不同数据组件之间的结构和关系。

实体-关系模型

实体-关系(ER)模型的两个基本组件是实体和关系。实体描述可以通过一组属性来区分其他对象的对象或概念。例如,航班、乘客、学生、课程和教师等。

关系描述一个实体与另一个实体之间的关系。一些示例包括航班UA541使用波音737飞机,Davood正在教授CMPUT 291,Mary预订了于2021年12月20日起飞的UA541航班。

图3.1描述了一个航班数据库的ER图。可以看出,数据库由四个实体组成:航班(Flights)、预订(Bookings)、飞机(Aircraft)和乘客(Passengers),在图表中分别表示为矩形。它还包含连接这些实体的关系:assigned、of和booked,每个在图表中表示为菱形。实体和关系也可以有属性。如图3.1所示,航班实体具有flno、airline、departure、origin和destination等属性,分别显示为椭圆形。被下划线标记的属性,称为键,对于每个实体都是唯一的(例如,航空公司和航班号对于每个航班都是唯一的,而bno对于每个预订都是唯一的)。从预订到乘客的箭头表示每个预订由一个乘客进行,而其他关系没有定义这样的约束。基本ER模型可以通过其他组件进行扩展,以描述更复杂的关系(例如,三元关系和IsA层次结构)和约束(例如,键和参与约束)。关于ER模型和相关研究工作的详细教材内容由Thaleheim提供。

数据库的自然语言接口——数据和查询模型

UML

UML的初始设计目标是对软件进行建模。它是多种软件工程方法的积累,这些方法在形成统一语言之前已经被使用过。该语言提供了不同的建模图表,每个图表用于软件开发的不同方面,例如状态图、活动图、用例图和部署图。为了表示实体,UML提供了类图,它描述了对象及其关系,类似于ER模型。与ER不同,对象描述不仅可以包括属性,还可以包括操作这些属性的方法。例如,我们在图3.1中描述的航班数据库的类图可能包括四个类:flights、bookings、aircraft和passenger。类图可以显示每个类的属性和方法,以及类之间的关系,如图3.2所示。关于使用UML进行数据库建模的更多信息可以在其他地方找到(例如,[4])。

数据库的自然语言接口——数据和查询模型

本体论(Ontology)

本体论可以通过定义术语和概念的含义并形式化它们的关系,为概念模型增加语义层。通常,本体中的每一项都是一个类或者是一个类的实例。例如,在我们的航班数据库中,乘客和飞机定义了一些类,而约翰和波音757分别是这些类的实例。常见的关系类型是子类关系。例如,我们可以说乘客是人,飞机是物体。另外两种关系类型是等价关系和不交关系。例如,在航空公司预订中,人们可能会说乘客和客户是等价的,意思是它们包含相同的实例集。本体的主要优势在于描述不同类之间的关系,并允许我们进行推理。例如,知道人和物体是不相交的,我们可以说乘客和飞机也是不相交的;或者考虑到每个客户都有一个家乡,那么每个乘客必须有一个家乡。Jepsen [5] 提供了一种教程风格的本体论介绍,而一些其他人则研究了本体论在数据库设计中的作用(例如,[6])。

本体论在解释自然语言问题并将其映射到数据库上的形式化查询中起着重要作用。例如,考虑一个提及客户(而不是乘客)的自然语言问题,以及一个具有名为乘客的表的数据库。通过一个声明乘客等同于客户的本体,可以将问题与乘客表联系起来;没有本体的情况下,这可能不太直接。同样,列出了一些类的实例的本体(例如,约翰是乘客,波音757是飞机)可以帮助将问题词映射到数据库元素中[7,8]。

关系模型

关系模型是由Ted Codd [9]引入的,它围绕着描述数据结构的关系(或表)的简单但数学上严谨的概念构建,并且使用代数和演算来表达查询。由于其简单性,该模型易于广泛的最终用户访问;由于其基础是集合论,关系可以使用强大的声明式查询语言进行查询。该模型提供了描述完整性约束的手段,形式包括域约束、主键和外键约束以及函数依赖,稍后将进行描述。

图3.3显示了一个示例关系数据库。通常,每一行描述一个实体(例如,航班AC162)或一个关系(例如,Davood预订了于2021年8月14日启程的AC162航班)。描述相同类别的实体或关系的行通常具有相同的结构,也称为元组。元组被分组到表中,也称为关系(例如,航班,预订)。我们区分表结构,称为模式,和表内容,称为实例。

数据库的自然语言接口——数据和查询模型

一个关系模式可能由关系名称、列的名称和类型以及有时的完整性约束组成。两种常用的约束是主键约束和外键约束。表的主键指的是一组在关系中唯一的表属性,是识别关系中元组的主要手段(例如,Flights关系中的航班号flno)。一个表可以有多组唯一的属性,称为键,但每个表只能有一个主键。表的外键是指引用另一张表中唯一行的一组属性(例如,Bookings中的航班号flno指的是Flights中有效的航班号flno)。创建表时还可以指定更一般的约束。这些约束的示例包括“航班中预订的乘客人数不能超过飞机的容量”和“两家航空公司不能拥有相同的航班号”。

关系查询语言

数据库查询是从数据库中检索或操作信息的一种方式。对于存储在关系数据库中的数据,可以使用各种功能强大的结构化查询语言来提出查询。与传统的编程语言不同,查询语言通常是声明性的,用户在其中指定必须满足的属性或条件,而不是详细说明检索或操作信息的步骤。这些语言通常具有有限的表达能力(不一定是图灵完备的),并且用这些语言表达的查询更容易评估。这些查询语言的广泛采用是关系数据库存储数据的主要推动力之一。

一阶逻辑

一阶逻辑(First-Order Logic,FOL)是大多数关系查询语言的数学基础。考虑图 3.3 中的示例数据库,假设我们想要找到所有预定飞往西雅图的乘客。这个查询可以用一阶逻辑表示为两个谓词的合取:flights(.xal , .x f n , ‘Seattle’, . y1, . y2, . y3, . y4) 和 bookings(.x p ,.xal ,.x f l,. y5,. y6),写成以下形式: ans(.x p). ←flights(.xal ,.x f n , ‘Seattle’, . y1, . y2, . y3, . y4), bookings(.x p,.xal ,.x f l,. y5,. y6) 在一个带有绑定变量的规则语言中,变量名称相同(例如,.xal 和 .x f n )表示对应列的匹配。变量 .x p 是一个自由变量,用于迭代结果集。

一般来说,一阶逻辑中的每个查询被描述为一个或多个规则的形式: ans(. t). ← . R1(t1), ..., . Rn (tn) 规则主体中的 . R1, ..., . Rn 是数据库中关系的名称,规则头部中的 ans 是查询返回的关系名称,. t, t1, ..., tn 是元组,其数量与相应的关系 ans, R1, ..., Rn 相匹配。每个元组 . t 可以写成 .< x1, ..., xm >,其中每个 .xi 可以是一个自由变量,范围是相应域的值,也可以是一个常量。在大多数关系查询语言中,规则头部 . t 中的变量受限于出现在规则主体 . t1, ..., tn 中的变量。对于任何自由变量的赋值,使得规则主体为真,规则头部成立,并将一个元组添加到 ans 中。

关系代数(Relational Algebra)

基于代数的视角来编写关系查询基于一组一元和二元运算符,每个运算符接受一个或两个关系作为输入,并产生一个关系作为结果。其中三个基本运算符包括选择(Selection)、投影(Projection)和笛卡尔积(Cartesian Product)。选择运算符作用于关系实例 .r,表示为 .σc(r),选择满足条件 .c 的关系中的所有行。投影运算符,表示为 .πa1,...,aj(r),返回其参数关系中未列出的属性。移除某些列可能会产生重复项,而这些重复项将被移除,使结果成为一个关系。笛卡尔积被定义为两个集合的笛卡尔积,表示为 r1 × r2,将来自两个关系实例的信息组合起来。例如,要找到所有飞往埃德蒙顿的航空公司,我们可以写成 .πairlineσdestination='Edmonton' Flights。

使用笛卡尔积和选择可以联接多个表中的信息,但通常更容易使用联接运算符。特别是,两个表的自然连接,表示为 r1 ►◄ r2,基于它们的共同列联接两个表,如果两个元组在共同列中具有相同的值,则它们将被联接。例如,以下查询找到所有预订了从丹佛飞往纽约的航班的乘客。. πpassengerσorigin='Denver' and destination='NewYork' (Flights ►◄ Bookings)。

将每个表视为一组行,可以使用集合操作将表组合在查询中。例如,以下查询将给出没有预订的航班。. πairline, flno Flights - πairline, flno Bookings。

因此,关系代数包括集合并、集合交和集合差等集合操作。其他关系代数运算符包括重命名和除法。关于这些运算符及如何组合它们来构建查询的更多信息可以在任何数据库系统中找到(例如,教科书 [10])。

在查询的复杂性方面,对于具有 N 行的表来说,很容易看出关系代数运算符可以在 O(N^2) 的时间内计算完成,其中联接是最昂贵的操作。在相同的基础上,具有固定操作数量的任何关系代数查询都是基于输入大小的多项式。另一方面,在考虑查询的复杂性时,我们通常对数据复杂性和组合复杂性进行区分。在前者中,我们假设查询是固定的,复杂性以数据库大小表示,而在后者中,数据库大小和查询都会变化。给定数据库实例 I 和查询 Q,评估查询的成本是多项式关于 |I|,指数关于 |Q|(参见 [11])。

SQL

SQL是一种结构化查询语言,也是用于查询关系型数据库的 ANSI 标准。该语言可以被视为是在一阶逻辑之上的语法糖。因此,它是一种声明性语言。然而,SQL支持使用函数处理不同的数据类型,以及聚合、分组和结果排序等操作;这些操作在一阶逻辑中是不可用的。

一个基本的SQL查询由select、from和where子句组成,其中在from子句中列出要查询的关系,在where子句中列出要满足的查询条件。select子句描述要返回的列。例如,查询“找到所有飞往西雅图的航班”可以用SQL表示为:

Q1.

select *

from Flights

where destination=‘Seattle’;

在select子句中的where *表示应返回表Flights的所有列。SQL与关系代数也有密切的关系,后者被认为是一种用于查询关系型数据库的过程化语言。首先,SQL表达式可以在SQL引擎内映射到关系代数表达式,这提供了优化查询和高效查询评估的机会。例如,基本的select-from-where查询在关系代数中被映射为select-project-join(SPJ)查询。其次,SQL中的集合操作是从关系代数中借鉴的,这使得一些具有过程性结构的查询更容易编写。

SQL查询维度

自 1974 年首次发布以来,SQL 已经不断发展,并经历了几次修订,其中 SQL:1999 是该语言的第四次修订。随着该语言的广泛使用以及通过这些修订,SQL 已经变得非常复杂。语言复杂性可以从一些方面进行分析,例如连接的关系数量、聚合和分组的使用、查询嵌套、结果排序、否定和递归等。接下来,我们将使用示例讨论 SQL 在这些方面的情况。

单个关系上的查询

查询 Q1,如上所示,是一个简单的查询示例,from 子句中只有一个关系。该查询返回所有飞往西雅图的航班。要仅返回航空公司和航班号码,可以在 select 子句中列出这些列名(如图 3.3 中所列)。

select airline, flno

from Flights

where destination=‘Seattle’;

在 where 子句中可以使用逻辑连接词 and、or 和 not 表达多个条件。

多个关系的查询

通常,与查询公式相关的数据存储在多个表中,并且这些表需要进行连接。在许多情况下,数据通过规范化或出于性能原因分布在多个表中,这些表可能需要连接以制定查询。例如,为了找到计划于2021年9月飞往西雅图的乘客,需要连接两个表 Flights 和 Bookings,如下所示:

Q2.

select passenger

from Flights f, Bookings b

where f.airline=b.airline

and f.flno=b.flno

and destination=‘Seattle’

and strftime(‘%Y’,dep_date)=‘2021’

and strftime(‘%m’,dep_date)=‘09’;

其中,strftime 是一个 SQLite 函数,用于提取年份和月份字段。商业系统可能在支持此类数据类型函数方面有所不同。例如,Oracle 和 DB2 提供了一个 extract 函数,用于从日期类型中提取年、月和日字段。

集合操作

集合操作 . ∪、 . ∩ 和—are 使用关键字 union、intersect 和 except 在 SQL 中支持,分别对应它们的关系代数对应项,使得使用程序结构编写某些查询变得更加容易。例如,下一个查询找到没有运营任何波音 737 Max 飞机的航空公司:

Q3.

select airline

from Flights

except

select airline

from Flights f, Aircraft a

where f.aid=a.aid and lower(name) like ‘boeing 737 max%’;

分组和聚合

数据元素有时需要分组和/或聚合来构建查询。例如,要查找每个航空公司的所有预订,必须根据航空公司对 Bookings 中的行进行分组,这些分组可以根据某些聚合函数或添加到 SQL 的 having 子句中的其他约束进一步细化。例如,检索那些有超过 20 个预订的航班。分组和聚合为语言增加了另一层复杂性:

Q4.
select airline
from Flights f, Bookings b
where f.airline=b.airline and f.flno=b.flno
group by f.airline, f.flno
having count(*) > 20;

嵌套查询

SQL允许一个查询嵌套在另一个查询中,这与编程语言中的函数调用类似。嵌套在外部查询内部的查询被称为子查询,它是一个select-from-where表达式,可以返回标量或集合。一些常见的子查询用例包括成员测试、将标量与集合进行比较以及测试集合的基数。

外部查询可以使用in连接符测试子查询的结果是否包含某个元素。例如,子查询可能会找到超售的航班,外部查询可以检查乘客是否在其中之一。外部查询可以使用op all或op some将标量与集合进行比较,其中op可以是 >、≥、<、≤、= 或 !=。例如,子查询可以计算每个目的地的航班数量,而外部查询可以找到航班数量最多的目的地(见Q5)。作为子查询的另一个用例,外部查询可能想要检查子查询结果是否为空或非空,这可以使用exists和not exists连接符完成。例如,子查询可以找到乘客在加拿大航空公司的所有航班,外部查询可以检查该集合是否为空(见Q6):

Q5.

select destination
from Flights
group by destination
having count(*) >= all (select count(*)
from Flights
group by destination);

Q6.

select passenger
from Bookings p
where not exists (select *
from Bookings b
where p.passenger=b.passenger and airline='AC');

排序和前K个

结果可能需要进行排序,并且可能需要在排序后的结果上进行选择。例如,我们可能希望根据预订数量对结果进行排序,或者找到预订数量最多的前五位乘客。查询Q7找到预订数量最多的三家航空公司,而查询Q8根据平均飞行距离对飞机进行排序:

Q7.

SELECT airline
FROM Bookings
GROUP BY airline
ORDER BY COUNT(*) DESC
LIMIT 3;

在查询Q7中,我们首先对航空公司进行分组,然后按预订数量降序排序。最后,使用LIMIT子句限制结果集为前三名航空公司。

Q8.

SELECT aid, name
FROM Aircraft a, Flights f
WHERE a.aid=f.aid
GROUP BY aid, name
ORDER BY AVG(dist);

在查询Q8中,我们首先将飞机和航班表连接起来,然后按平均飞行距离对结果进行排序。

递归、否定和空值

假设我们想找出所有城市 S 和 T 的配对,使得乘客可以通过任意数量的航班从 S 到达 T。Q9 展示了递归的典型示例,其中查询引用自身的结果来构建完整的结果集。在 SQL 中,递归是在固定点语义下定义的,其中 Connected 是按层计算的,每一层都会添加新的行,直到不能再添加更多行为止:

Q9.

with recursive Connected (origin, destination) as (
select origin, destination
from Flights
union
select c.origin, f.destination
from Flights f, Connected c
where c.destination=f.origin)
select origin, destination
from Connected;

否定可能显示为谓词(例如,name != 'Joe')在 where 子句或 having 子句中,或者以集合差分的形式出现(例如,Q3)。否定也可能出现在查询使用 not exists 连接符时(例如,Q6)。递归和否定不太适合在 SQL 查询中一起使用,带有否定的递归 SQL 查询可能无法终止。出于同样的原因,SQL 不允许使用 not exists 和集合差分进行递归。

空值的存在使得 SQL 查询变得更加复杂。例如,如果航班的出发地是 null,则谓词 origin='Seattle' 不会计算为 true 或 false。在 SQL 中,谓词的语义是在三值逻辑下定义的,其中表达式可以计算为 true、false 和未知。

图模型

图模型将实体表示为图的节点,将关系表示为图的边。尽管图模型长期以来一直被视为关系模型的一种替代方式(例如,参见[12, 13]),并且查询被表达为图模式,但它直到最近才受到关注,这与知识图和 RDF(一种用于描述 Web 上资源的 W3C 标准)的兴趣激增有关。

数据库的自然语言接口——数据和查询模型

在使用 RDF 表示域知识的典型图模型中,每个实体都表示为一个节点,并且关于每个实体的信息使用从节点发出的有向边描述。一条边可以连接一个实体 .e1 到另一个实体 .e2,表示两个实体之间的关系或描述一个谓词,其中 .e1 是主语,.e2 是宾语(例如,AC 162 由加拿大航空公司运营)。一条边也可以将一个实体连接到一个文字节点,该节点具有实体的属性值。图3.4展示了描述航班、航空公司和一些加拿大城市的示例图。每个实体和谓词都有一个唯一的标识符,以使陈述更准确。在我们的示例中,标识符来自 Wikidata,其常见前缀未显示。

图模型允许实体之间的更复杂的关系,而无需受到表的固定结构的约束。例如,要在两个实体 .e1 和 .e2 之间添加关系,关系模型的基础模式必须允许描述这样的关系,而在图模型中,可以在任意一对节点之间自由添加边。

SPARQL

SPARQL是W3C用于查询RDF的查询语言,其中数据被视为三元关系,形式为(主语、谓词和宾语),称为三元组或语句。该语言将三元组视为一等公民,并允许三元组模式中的每个主语、谓词和宾语都是变量。需要注意的是,尽管可以使用标准的关系型查询语言(如SQL)查询RDF数据,但查询看起来不像在SPARQL中那样自然或直观。以下是一个用于查找美国所有国际机场的SPARQL查询示例:

SELECT ?airport
WHERE
{
  ?airport wdt:P31 wd:Q644371.
  ?airport wdt:P17 wd:Q30.
}

谓词和实体的ID来自Wikidata(如图3.4所示)。第一个谓词将?airport绑定到那些是国际机场(wd:Q644371)的实例(wdt:P31),第二个谓词确保那些机场的国家(wd:Q30)在美国。前缀wdt和wd预定义如下:

prefix wd: <http://www.wikidata.org/entity/>
prefix wdt: <http://www.wikidata.org/prop/direct/>

我们的下一个查询查找在美国拥有最大人口的前10个大城市:

SELECT ?city
WHERE
{ 
  ?city wdt:P31 wd:Q515.
  ?city wdt:P17 wd:Q30.
  ?city wdt:P1082 ?population.
}
ORDER BY DESC(?population) LIMIT 10

类似于SQL,SPARQL允许聚合和分组。例如,我们的下一个查询返回每个国家的国际机场数量。为每个国家进行分组,并根据国际机场数量对结果进行排序:

SELECT ?country (COUNT(?airport) AS ?c)
WHERE
{
  ?airport wdt:P31 wd:Q644371.
  ?airport wdt:P17 ?country.
}
GROUP BY ?country
ORDER BY DESC(COUNT(?airport))

SPARQL允许使用可选模式查询可能存在或不存在的边类型,并使用过滤器谓词来包含和排除特定的图模式。

存储和索引

数据库查询语言(包括 SQL 和 SPARQL)的一个重要优势是它们的声明性:用户描述存储和检索的信息,数据库系统决定存储结构的具体细节和评估算法。每个关系表可以存储为堆(未排序)或排序文件,并且可以构建索引以提供对数据的快速访问。这些索引可以存储为单独的文件。大多数商业关系型数据库会自动在每个表的主键上构建索引,因为通常使用主键来访问行。用户和数据库管理系统可以根据需要构建额外的索引。存储数据的存储结构的选择以及数据库引擎在回答查询时采用的访问路径不影响查询结果,但可能对运行时间产生重大影响。

举个例子,考虑我们在图 3.3 中给出的航班表。该表可以组织为堆文件,每个磁盘页面存储固定数量的记录。可以在不同列上构建索引以加快搜索速度。图 3.5 显示了这样一个堆文件,每个页面最多存储三行,以及一个独立的索引文件在起点列上。在我们的情况下,索引文件是排序的,并提供对与给定起点匹配的记录的高效访问。索引文件可以组织为树(例如 B+ 树)或哈希,以进一步提高访问的效率。此外,可以在其他列上构建更多的索引,例如目的地和航班号,以允许对这些列进行更有效的搜索。关于文件组织和索引的更多细节可以在多本教科书中找到。

查询评估和优化

正如图3.6所示,查询处理和优化可以分为以下三个步骤:查询解析和计划生成、查询优化和查询评估。

数据库的自然语言接口——数据和查询模型

查询解析和计划生成

在查询解析过程中,首先将查询语法与SQL语法进行检查,识别语法片段和类别(例如,select列表、from列表和条件)。解析器还可能强制执行一些语义规则,这些规则不在语法中。例如,查询中引用的每个关系必须存在于模式中,并且在select和where子句中提到的每个属性必须是当前范围内关系的属性。

在查询通过这些语法(和一些非语法)检查之后,解析树被映射到一个逻辑查询计划,其中每个叶子表示查询中的一个基本表,每个内部节点表示对数据执行的关系代数运算符,通过子节点进行流式传输。树中的每个内部节点将其结果传输到父节点,根节点返回查询的结果。图3.7a显示了我们在第3.2节中的一个查询的查询计划。首先,表Aircraft和Flights根据aid,这两个表之间的一个公共列,进行连接。结果根据aid和name进行分组,然后在返回每个组的aid和name之前对组进行排序。

查询优化

相同的查询语义可以用SQL的几种不同方式来表达。每个SQL查询可能会映射到多个不同的关系代数表达式,每个表达式都提供一个查询计划。关系代数表达式之间的等价关系还允许重写计划,使得初始计划和重写计划在每个合法的数据库实例上产生相同的元组集合。查询优化器负责选择给定查询的最有效的计划。

Query Equivalence

数据库的自然语言接口——数据和查询模型 在图 3.7 中显示了两个计划,它们在投影分布到连接上是等价的,但预期的成本可能不同。在查询计划 2 中,投影被推入树中,这应该会减少中间结果的大小,从而降低整体执行成本。一般来说,检测两个查询表达式之间的等价性问题是不可解的,即使查询被限制在简单的关系代数中。如果将查询限制为关系代数的一个子集,其中仅包含选择、投影和连接,则该问题会变得 NP-难,这称为合取查询。这将对应于仅具有 select、from 和 where 子句的 SQL 查询,其中 where 子句中的谓词被限制为列之间的相等和列与常量之间的相等。然而,查询优化器通过使用等价规则来重写查询来避免决策问题。以下是两个等价规则的示例,显示连接操作在关系之间是可交换的和可结合的。R1、R2 和 R3:

  1. R1 ►◄ R2 ≡ R2 ►◄ R1,
  2. (R1 ►◄ R2) ►◄ R3 ≡ R1 ►◄ (R2 ►◄ R3)。

对于给定的查询,可以构造许多可能的树,其中在叶子节点处查询关系,并且所有这些变化都会产生具有成本的可能的查询计划的大空间。例如,具有 n 个关系在叶子处的全二叉树的数量是著名的卡特兰数 C(n-1) = (2n-2)! / (n!(n-1)!)。查询优化器必须遍历所有这些计划,以选择具有最小成本的计划。这意味着优化器必须在评估之前估计每个查询计划的成本。对于存储在磁盘等二级存储器上的大型数据库来说,主要成本是 I/O 次数。

基于成本优化

成本估算一直是数据库研究中备受争议的话题,因为准确的估算往往取决于许多可能未知的参数,比如条件的选择性和连接的大小,或者随着表的更新而改变。由于每个操作的结果都会传递给查询计划中的下一个操作,估算中间结果的大小对整体成本估算有很大影响。数据库系统维护表和列的统计信息,以帮助估算它们的大小。例如,维护列中不同值的数量可以帮助估算满足等式条件的行返回的概率,假设列中的所有值是等可能的。当一些值比其他值更可能时,直方图和前几个频繁值及其频率的集合可以提供更好的估算。连接的大小可能从上限受到笛卡尔积的大小的限制,从下限受到关系的大小的限制,例如当连接列是一张表中的键时。

期望基于成本的优化器搜索可能计划的空间,并找到成本最低的查询计划。连接顺序选择是一种常见的优化类型,适用于大量查询,并且通常是每个查询优化器的一部分。在给定要连接的关系集合的情况下,这些关系的不同括号化将产生不同的连接顺序,查询优化器通常寻找成本最低的括号化。虽然括号化的数量随着关系的数量迅速增加,但一种高效的解决方案是使用动态规划算法来存储和重用计算结果。将其他操作添加到基于成本的优化器中会迅速增加计划的数量,达到难以管理大型查询的水平。

为了减少搜索空间和成本,一些启发式方法可能会在基于成本的优化器之上使用。一个旨在减少中间结果大小的启发式方法是尽早进行选择和投影。另一个启发式方法是,System R优化器只考虑形成左深树的连接顺序,这提供了更多的管道机会。例如,对于连接的右操作数存储在表中,只有一个输入被流水线处理。嵌套子查询可以被视为接受某些参数作为输入并返回一行或一组行作为输出的函数。一些子查询可以被展开并与查询的其余部分合并,使用连接,而其他一些子查询可能会被缓存以避免对相同表达式的多次评估。关于关系系统中的查询优化的更多信息可以在相关文献和数据库教材中找到。

评估引擎

索引查询评估引擎可能会从查询计划的最低级操作开始,并自底向上地评估查询。可以使用几种策略来减少中间结果的大小和操作的成本。一种策略是在尝试下一个操作之前评估一个操作。一些操作,比如排序,在检查完所有输入之前无法产生任何输出。这些阻塞操作的中间结果必须被实例化或存储,而非阻塞操作的输出可以被流式传输到评估顺序中的下一个操作。多个操作可以形成一个流水线,其中一个操作的结果被传递给流水线中的下一个操作。流水线的一些优点包括可能节省中间数据写入和读取的成本,以及当流水线包括查询树的根节点时生成早期的查询结果。

总结

数据库中的一些关键概念进行了回顾和讨论。特别是,数据的结构可以使用概念模型来描述,我们对其中一些模型进行了审查。我们还审查了SQL和关系代数作为关系模型的两种查询语言,以及SPARQL作为一种查询图形数据存储或视为RDF的语言。给定一个查询,查询必须经过一些过程来计算答案。我们简要回顾了其中一些过程,包括查询优化和评估。