《Expert MySQL》翻译——chapter2.解剖数据库系统(4)
查询处理
在一个基于C/S模型的数据库系统中,数据库服务端必须根据客户端提出的请求来返回相应的数据。有个专业术语“查询传递”(query shipping)可用来描述这种通过传递查询来获得返回数据的情况。这种做法的好处在于:相对于将查询放在本地来使用有限资源去执行,将查询传递出去能够更加减少客户端间的数据交流,同时也防止了溢出的可能性。还有一个好处就是将数据的访问和检索操作与拿到数据后的处理过程做了分隔。也就是说,C/S模式能够使数据独立。
数据独立性是Codd在上世纪70年代的论文《The separation of the physical implementation from the logical implementation》中提出的关于关系模型的一个主要优势。根据Codd的说法:
拥有大数据的用户没必要知道数据是如何组织的......当内部数据的呈现机制改变的时候,大多数应用以及用户最终的活动应该不受其影响。
这种分隔使得强大的业务逻辑能够独立于具体的物理实现。数据独立的目标在于将逻辑数据与其物理实现分隔开。比如说,数据在逻辑层面上如何与各表各列产生关系是完全跟这个数据在存储层面上如何存储相独立的。
将数据独立之后,带来的一个挑战就是数据库程序就要分成两个部分去实现。第一部分是关于逻辑查询,这部分用来描述查询语句应该要干什么。第二部分就是关于物理实现,也就是怎么将逻辑查询需要的数据展示出来。
逻辑查询可以有很多种实现方法,比如说使用类似SQL这样的高层语言,也可以使用基于某种代数系统的查询树。举个例子,在传统关系模型中,一个逻辑查询可以被描述成关系型积分(relational calculus)或者关系型代数(relational algebra)。当我们关注于需要服务器计算的东西的时候,关系型积分在这方面比较好。而关系型代数,更加关注于提供一个算法,能够让你找到你需要查询的东西,然而,在计算这个查询的时候所需要的更多其他细节(也就是关系型积分偏重的部分)依旧还需要进行额外处理。
物理实现则是可以被数据库引擎理解并执行的一个查询树。所谓的查询树就是这样的一个树结构:每一个节点都包含一个查询时需要的计算符号(可以简单理解为加减乘除、逻辑判断、求笛卡尔积等一些计算符号),然后这个节点的孩子们就是参与这个计算符号运算的所有表。这个查询树通过优化器可以转换为查询的执行计划。这个执行计划可以理解为能够被执行查询的引擎所理解的一段程序。
一条查询在被执行之前还会经过几个过程:分析、验证、优化、执行计划的生成和编译,然后就是执行。图2-2描述了一个典型数据库系统针对查询的处理流程。每个查询语句都会在解析之后进行验证,然后检查一下语法是不是对,以及辨别一下是哪种查询操作。然后这个分析器就会生成一个“中间表述”来交给优化器,最终形成一个经过优化的执行计划。然后引擎就会执行这些查询,最终将结果返回给客户端。这一过程在图2-2中有描述:当分析结束之后,就会检查是否有错误,没错误之后就开始优化,然后生成一个计划并且编译这个计划,然后这个查询就被执行了。(特喵的这个还真的不是我翻译的罗嗦,原文就这么罗嗦,同样的概念讲了两遍。。。)
第一步是将逻辑查询通过关系型代数(relational algebra)从SQL转换为一个查询树。这一步是通过分析器完成的,而且通常情况下,分析器会将SQL拆解,然后再组装成为查询树。下一步就是把这个基于逻辑代数的查询树翻译成一个查询计划。通常来说,一个查询树可以翻译成很多种查询计划,寻找最佳查询计划的这个过程就叫做查询优化。也就是说我们希望获得一个最优的查询计划,使得在将来的性能测试(比如说测试一下执行时间)中表现较好。也就是在优化器能力范围内能够搜索到的所有查询计划中,找一个最优或者次优的查询计划来工作。优化器开始的时候是将基于关系型代数的查询树复制到自己的搜索范围内,然后在有限迭代的情况下不断生成查询计划来扩展自己的搜索范围,直到找到最优的为止。
在这一层次上,优化器通常可以被看作是SQL语言编译器的一个代码生成器。事实上,在有些数据库系统中,编译这一步就可以将查询转换成一个可执行程序。但是,大多数数据库系统通常是把查询翻译成一种在执行阶段可以被数据库系统的内建库函数执行的形式。在这种情况下,代码编译的过程给查询执行引擎提供代码,除非优化器真的很强大,能够生成非常有效率的代码。举个例子,优化器需要使用数据库系统的目录来获得相关信息,这些信息包括查询中涉及的数据库中存储的关系等,然而一个传统的编译器通常不做这事,所以优化器生成的代码只要能够被引擎理解就好了。最终,优化器将经过优化的执行计划从它的内存结构中复制出来,提交给查询执行引擎。然后查询执行引擎使用数据库中已经存储的数据关系作为输入,来跑这个执行计划,然后生成符合查询要求的表单。
全部的这些过程都需要额外的执行时间,这就要求开发这个数据库系统的人负担起更大的责任,要将查询优化器和执行引擎的性能作为影响全局性能的因素去考虑。这个优化过程消耗资源很多,因为每个可以互相替代的执行计划有不同的访问数据的方式和访问顺序,因此一条查询就能够生成无数种不同的执行计划。但是,数据库通常会使用一些现有已知的最佳实践方案来跳过这种问题。
另外,导致生成一大堆执行计划的原因也在于优化器需要获得各种不同的运行时参数,然而这些参数在优化过程中又不能够真切地获得。因此,数据库系统在数据库内容中(比如在关系型的属性中赋一些值),在物理表中(比如一些索引类型),在系统参数中(比如说可用的缓存区数量),以及查询语句中涉及的常数都做了一些假设。
(小节完,全文未完待续)