MicrosoftSQLServer查询处理器的内部机制与结构(1)

<script type="text/javascript"><!-- google_ad_client = "pub-2947489232296736"; /* 728x15, 创建于 08-4-23MSDN */ google_ad_slot = "3624277373"; google_ad_width = 728; google_ad_height = 15; //--> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
<script type="text/javascript"><!-- google_ad_client = "pub-2947489232296736"; /* 160x600, 创建于 08-4-23MSDN */ google_ad_slot = "4367022601"; google_ad_width = 160; google_ad_height = 600; //--> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
document.write(baiduCproIFrame());-->摘要:本文介绍了在客户机上处理MicrosoftSQLServer查询的方式,各种客户机与SQLServer的交互方式,以及SQLServer在处理客户机程序的请求时需要完成的工作。(打印共26页)

简介
Microsoft(R)SQLServer(TM)内部机制结构是一个非常大的主题,因此本文仅限于程序开发人员感兴趣的问题,集中研究其他源中没有彻底讨论的问题。在讨论SQLServer的结构时,我们主要观察客户机的处理过程,研究不同的客户机程序与SQLServer的交互方式,以及SQLServer如何处理客户机的请求。还有一些讨论SQLServer其他方面的信息源,特别是MicrosoftPress出版的InsideSQLServer7.0,作者是RonSoukup和KalenDelaney,这本书非常详细地讨论了SQLServer存储引擎的内部机制和处理方法,不过对查询处理器的讨论不够深入。本文正填补了这个空白。

我们期望本文有助于读者编写出更好的应用程序。通过本文,读者会在提高程序性能方面得到新的启发,产生新的理解。

SQLServer是一种客户机/服务器系统
多年来,SQLServer一直被认为是一种客户机/服务器系统。事实上,SybaseDataServer(以此为基础开发了原始的SQLServer)正是第一个作为客户机/服务器系统开发的商用关系数据库系统。那这又说明了什么呢?这不只意味着SQLServer是一个双层系统。从传统上看,双层系统意味着客户机应用程序运行在一台机器上,向另一台计算机上的服务器发送请求。而对于SQLServer,客户机/服务器意味着SQLServer的组成部分,即客户机API部分,驻留在处理结构中的远端,与服务器组件本身是分开的。

在典型的双层模型中,客户机程序部分驻留在台式机上,具有大量客户机应用程序逻辑和业务逻辑,并且会直接向数据库系统发出请求。然后,客户机得到服务器响应这些请求所返回的数据。

三层系统也采用了同样的模型。多年以来,SQLServer一直用在事务处理监视系统中,例如BEA的Tuxedo以及Compaq的ACMSxp,这些系统早在二、三十年前就采用了典型的三层模型。三层模型在今天基于Web的应用系统中占据了支配地位,这类系统以Microsoft的MTS以及新的COM+1.0为代表。从SQLServer的角度看,三层解决方案中的客户机程序是放在中间层的。中间层直接与数据库交互。实际的桌面,或瘦客户机(ThinClient),使用其他机制并通常直接与中间层交互,而不是直接与数据库系统交互。图1描述了这种结构



图1.三层系统模型

客户机结构
结构的角度看,SQLServer关系服务器组件本身并不真正关心客户机程序运行的位置。事实上,就SQLServer而言,即使在运行SQLServer的同一台机器上运行应用程序,仍然还是客户机/服务器模型。服务器运行一个单独的多线程进程,为来自客户机的请求提供服务,不管客户机的位置在哪里。客户机程序代码本身是单独的运行在客户机应用程序内部的DLL,与SQLServer的实际接口是在客户机和服务器之间对话的“表格数据流”(TabularDataStream,TDS)协议。

一个常见的问题是“什么是SQLServer的本机接口呢?”很长时间以来,很多开发人员一直都不愿意使用ODBC这样的接口,因为他们认为由Sybase开发的客户机API,也就是DB-Library,是SQLServer的本机接口。实际上,SQLServer关系服务器本身并没有本机API,它的接口就是在客户机和服务器之间的通信流协议TDS。TDS把客户机发送给服务器的SQL语句封装起来,也把服务器返回给客户机的处理结果封装起来。任何直接处理TDS的API都是SQLServer的本机接口。

让我们来看一下客户机的组件,如图2所示。客户机结构中的某些部分就不在这里讨论了,因为它们不属于SQLServer的范畴。但如果您在编写应用程序的话,就必须了解这些部分。大家知道得最多的应该是各种对象模型,如果您正在编写ASP或MicrosoftVisualBasic(R)应用程序,就需要通过ADO与数据库系统交互,而不是直接调用底层的API,例如ODBC或OLE-DB。ADO映射到OLE-DB,而RDO映射到ODBC。因此,作为这种最常用的编程模型的对象模型,并不是SQLServer客户机结构中的严格意义上的组件。此外,还有另外一些组件可以插接到SQLServer基础结构上面的这一层。OLE-DB的“会话池服务提供程序(SessionPoolingServiceProvider)”就是这种组件的一个例子。



图2.客户机结构

客户机接口
SQLServer有两个接口可以认为是SQLServer7.0的本机接口,即OLE-DB和ODBC。DB-Library接口也是本机的,它与TDS通信,但是DB-Library使用的是TDS较老的版本,需要在服务器上进行一些转换。现有的DB-Library应用程序仍然可以继续与SQLServer7.0协同使用,但是很多新的功能和性能提高等好处只能通过ODBC和OLEDB才能利用。更新DB-Library使其支持SQLServer7.0的新能力,将会导致与现有应用程序的很多不兼容性,因此需要修改应用程序。ODBC在五年之前就替代了DB-Library,是新的SQLServer应用程序更理想的API,因此引入不兼容的DB-Library新版本并不明智。

从图2可以看到,所有这些客户机API都有三个部分。最上面的部分实现API的细节,例如行集和游标应该是什么样等等。TDS格式化程序负责处理实际请求,例如SQL语句,并将其封装成TDS消息包,发送给SQLServer,获得返回的结果,然后再把结果反馈到接口实现。

还有一些供所有提供程序使用的公共库代码。例如,BCP设备就是ODBC和OLE-DB都可以调用的库。DTC也是这样。第三个例子是ODBC规范的SQL语法,即带有参数标记的CALL语法,这些对于所有提供程序都是通用的。

除了我们在前面已经提到的局限性,即DB-Library仍然只能使用SQLServer6.5版,TDS协议对于所有API都是相同的。ODBC和OLE-DB在与SQLServer7.0通信时使用SQLServer7.0版,但也能够与6.5或6.0服务器通信。另一个是Net-Library,这是一个抽象层,客户机和服务器都在此层上同网络抽象接口通信,不必为IPX还是TCP/IP困扰。在这里我们将不讨论Net-Library的工作细节;只要知道它们的工作基本上是将来自的网络通信底层的细节隐藏起来不让软件的其他部分看到就可以了。

从客户机的角度看服务器
前面已经提到过,客户机与SQLServer通信的主要方法就是通过使用TDS消息。TDS是一种简单协议。当SQLServer接收到一条消息时,可以认为是发生了一个事件。首先,客户机在一个连接上发送登录消息(或事件),并得到返回的成功或失败的响应。当您希望发送SQL语句时,客户机可以把SQL语言消息打包发送给SQLServer。另外,当您希望调用存储过程、系统过程或虚拟系统存储过程(我们后面还要详细讨论)时,客户机可以发送RPC消息,这种消息相当于SQLServer上的一个RPC事件。对于上面的后两种情况,服务器会以数据令牌流的形式送回结果。Microsoft没有把实际的TDS消息写入文档中,因为这被认为是SQLServer组件之间的私用契约。

目录存储过程是另一类关键的客户机/服务器的交互部分。这些存储过程首先在ODBC的SQLServer6.0中出现,包括诸如sp_tables和sp_columns等存储过程。ODBC和OLE-DBAPI定义了描述有关数据库对象的元数据的标准方法,这些标准需要适用于所有类型的RDBMS服务器,而不必调整为SQLServer自己的系统表。不是客户机向服务器发送对系统表的多个查询,并在客户机端建立标准的元数据视图,而是创建一组存储在服务器上的系统存储过程,并对API返回适当格式的信息。这种方法使得通过一次通信就可以完成很多重要的元数据请求。

为ODBC编写的过程已经写入文档,通常适合需要从系统表中获取信息但其他机制没有提供这种方法的情况。这使得Transact-SQL过程和DB-Library应用程序可以访问元数据,而不需要编写对SQLServer系统表的复杂查询,并且使应用程序不受今后Microsoft修改系统表的影响。

OLEDB定义了一组架构行集,它们类似于ODBC的元数据,但又和它不同。它创建了一组新的目录存储过程,以更有效地为这些架构行集植入数据。但是,这组新的存储过程没有写入文档,因为这些存储过程重复了早先提供的功能。通过现有的若干种方法都可以得到元数据,因此SQLServer开发组决定不显露这些并没有为编程模型增加新内容的对象。

客户机与服务器的交互还有第三个方面。它最初出现在SQLServer6.0中,但是没有得到普遍使用。这就是虚拟系统存储过程的概念;在SQLServer7.0中起很重要的作用。当第一次为SQLServer6.0开发服务器端游标时,开发人员就需要选择采取什么方法管理客户机/服务器的交互。游标并不特别适合现有的TDS消息,因为这些消息允许逐行返回数据,不需要客户机指定额外的SQL语句。开发人员本来可以向TDS协议添加更多的消息,但是需要修改太多的其他组件。SQLServer6.0中的TDS版本还需要向Sybase版本靠拢,以便确保两者的可互操作性,于是开发人员选择了另外的处理机制。他们开发了外表看起来像是系统存储过程的新功能(服务器端游标),实际上是指向SQLServer代码的入口存储过程。它们被客户机应用程序使用标准的RPCTDS消息来调用。它们被称为虚拟系统存储过程,因为在客户机上,它们像其他存储过程那样被调用,和其他存储过程不同的是,它们并不是由简单的SQL语句组成。大多数虚拟系统存储过程都是私用的,并且没有写入文档。对于游标过程,所有API都显露其自有的一组游标API模型和它们自己的游标操作函数,因此没有必要为存储过程本身编写文档。即使是在Transact-SQL语言中,也有显露游标的语法,可以使用DECLARE、OPEN、FETCH等,所以完全没有必要为虚拟系统存储过程编写文档,例如sp_cursor,因为这些过程只在内部使用。

ODBC和OLEDB中出现了带参数的查询和准备/执行模型的概念。在SQLServer7.0以前的版本中,这些概念是由客户机API中的代码实现的。在SQLServer7.0中,Microsoft为这些概念添加了对“关系服务器”的支持,并且通过新的虚拟系统存储过程显露了这种支持。本文后面还要介绍这些功能,以及服务器如何支持这些功能。通过sp_executesql过程对带参数的查询的支持,被认为对直接Transact-SQL和DB-Library的使用特别有用,所以将其写入了文档。准备/执行的过程,被ODBC驱动程序和OLEDB提供程序专用。

这样,可以与SQLServer通信的所有客户机程序,都建立在这三组功能之上:TDS协议、目录存储过程和虚拟系统存储过程。

服务器结构
SQLServer,或更确切一点地说,是“SQLServer关系服务器”,经常被说成是由两个主要部分组成,即关系引擎和存储引擎。正如前面提到过的那样,已经有很多文献介绍存储引擎的细节了,所以本文主要介绍关系引擎的功能。图3给出了SQLServer关系引擎部分的主要组件。所给出的组件可以分为三组子系统。左边的组件编译查询,包括查询优化器。查询优化器是所有关系数据库引擎中的最神秘的部分之一,从性能的角度看也是最重要的部分。查询优化器负责提取SQL语句中的非过程请求部分,并将其翻译成一组磁盘I/O、过滤以及其他能够高效地满足该请求的过程逻辑。图中右侧是执行基础结构。这里实际上只有很少的功能。当编译组件的工作完成之后,所产生的结果只需用很少几个服务即可直接执行。



图3.服务器结构

图的中间是称为SQLManager的部分。SQLManager控制着SQLServer内部的所有数据的流动。SQLManager控制着RPC消息,在SQLServer7.0中,绝大多数来自客户机的功能调用都是通过RPC消息进行的。上一节中介绍的虚拟系统存储过程逻辑上也是SQLManager的一部分。通常,作为TDSSQL语言消息的SQL语句直接在编译一端执行,与早期版本相比,SQLServer7.0较少使用这种方法,但还算是比较常见的。执行结果由称为ODS的执行引擎中的组件格式化为TDS执行结果消息。

绝大多数输出都来自图中的执行端,而且输出结果也真正出自表达式服务。“表达式服务”库是进行数据转换、谓词评估(过滤)以及算法计算的组件。它还利用了ODS层,把输出结果格式化为TDS消息。

还有几个组件,我们只是在这里简单地提一下,这些组件在关系引擎内部提供附加服务。这些组件中的一个是目录服务组件,用于数据定义语句,例如CREATETABLE、CREATEVIEW等。目录服务组件主要放在关系引擎中,但是实际上大约有三分之一的目录服务组件是在存储引擎中运行的,所以可以看作是共享组件。

关系引擎中的另一种组件是“用户模式调度程序(UMS)”,这是SQLServer自己内部的纤程和线程规划器。把任务分配给纤程或线程是一种非常复杂的内部机制,取决于对服务器如何配置,以及在SMP系统中允许SQLServer进行处理器之间的适当的负载平衡。UMS还可以避免SQLServer由于同时运行太多的线程而导致性能过低。最后,还有大家熟悉的系统过程,逻辑上它们也属于关系引擎的一部分。这些组件肯定不是服务器代码,因为可以很容易地使用sp_helptext检查定义这些过程的Transact-SQL代码。但是,系统过程被作为服务器的一部分来对待,因为系统过程的用途是显露重要的服务器能力,像系统表一样,以供应用程序在更高的层次上和更适当的层次上使用。如果应用程序开发人员将较高层次的系统过程—更容易使用—作为一种接口,即使随着版本的更新,原始层次上的系统表发生变化时,应用程序仍然可以继续使用。

处理SQL语句时的客户机/服务器交互
下面我们将讨论当客户机应用程序与SQLServer交互时客户机的动作。以下是一个ODBC调用的例子:

SQLExecDirect(hstmt,"SELECT*FROMpartswherepartid=7",
SQL_NTS)

(OLE-DB也有一个与这个调用几乎直接等价的调用,此处不再讨论这个调用,因为这个调用实际上与ODBC调用相同。)该ODBC调用取一个SQL语句,然后将其发送给SQLServer来执行。

在这个具体的查询语句中,我们从零件表中提取具有特定零件标识号的所有行。这是特定SQL的一个典型例子。在SQLServer7.0以前的版本中,特定的SQL与存储过程的一个显著差别是,查询优化器所生成的计划从不缓存。查询语句要被读入、编译、执行,然后再抛弃计划。在SQLServer7.0中,正如稍后还要讨论的,实际上提供了可以缓存特定查询语句的计划的机制。

在这条语句被送往SQLServer之前,还必须要问几个问题。所有客户机程序都要提供某种游标说明,所以客户机程序在内部必须询问的一个问题是,程序员请求的是什么样的结果集或什么样的游标。最快的类型是在文档中被称为默认结果集的游标。这种游标由于历史上的原因被称为消防站游标,有时甚至根本不把它作为游标看待。当SQL请求被送到服务器之后,服务器开始把结果返回给客户机,这个返回结果的过程持续进行,直到把全部数据集发送完毕为止。这就像一个将数据抽给客户机的大型消防站。

一旦客户机程序确定了这是默认结果集,则下一步就是确定是否有参数标记。使用这个ODBCSQLExecDirect(以及OLE-DB中等价的调用)调用的选项之一是,不是在WHERE从句中给出像7这样的具体值,而是可以用一个问号来传递参数标记,如下所示:

SQLExecDirect(hstmt,"SELECT*FROMpartswherepartid=?",
SQL_NTS)

请注意,您必须分别提供实际的参数值。

客户机需要知道SQL语句中是否有参数标记,或者它是否为真正特定的非参数化SQL。这将影响到客户机将用这个语句在内部做什么,并确定将什么作为消息真正发送给SQLServer。在没有问号时,很明显,客户机只想将这个请求作为SQLLanguageTDS消息发送,然后客户机将位于流水的末端,并将结果返回。然后客户机能将结果返回给基于应用程序参数的应用程序。客户机的内部处理选择会模糊一点,这取决于您通过ODBC或OLEDBAPI请求什么。例如,应用程序不直接请求默认结果集。相反,在ODBC中,如果请求一个只读的、只向前的且每次只给出一行的游标,那么对于客户机内部运行来说,这就是在定义流水游标(默认结果集)。

流水游标有一个主要问题。除非客户机已将所有的行全部接收完毕,客户机不能将任何其他SQL语句向下发送给服务器。因为结果集可能有很多行,所以有些应用程序使用流水游标时不能顺利运行。后面将要描述的只向前的快速游标,是SQLServer7.0版的一个新特点,尤其适合于处理这种情况。

在SQLServer7.0版之前,SQLExecDirect调用在很大程度上是以相同方式处理的,而不管是否用参数标记来代替常数。如果您定义一个参数标记,客户机将实际取您通过不同调用提供的值(本节的开始示例中的值“7”),并将它插入问号处。然后,使用代替值的新语句被向下发送,作为一个特定的SQL语句。在服务器上使用参数化的SQL没有任何好处。

然而,在SQLServer7.0版中,如果SQLExecDirect使用了参数标记,向下发送给SQLServer的TDS消息便不是SQL语言消息。相反,它被下发给使用sp_executesql过程的服务器,所以,就TDS协议来说,它是RPC。在客户机上,结果基本上相同。客户机将取回数据流水。

如果您不想取回这个数据流水,则可以始终使用块游标或可滚动游标。在这种情况下,数据流变得大不相同。调用是对通过SQL文本中的sp_cursoropen输入点(这些虚拟存储过程之一)进行的。该sp_cursoropen利用SQL来增加附加逻辑,以使其滚动,它潜在地将某些结果重定向到一个临时表,然后用句柄给游标一个响应,表明游标现在是打开的。仍然在程序员的控制之外,客户机调用sp_cursorfetch,将一行或多行转到客户机上,然后返回到用户应用程序。客户机还可使用sp_cursor来重新配置游标,或改变某些统计数字。在您处理完游标之后,客户机将调用sp_cursorclose。

让我们看一个简单的情况,即只返回一行给客户机。至于默认的结果集,需要从客户机到服务器往返发送一次消息。SQL消息(或sp_executesql)向下发往服务器,然后结果返回来。在同一行(非流水)的游标情况下,您会看到传统情况下能用SQLServer看见的东西。一个往返行程用于打开,一个往返行程用于取得数据,一个往返行程用于关闭。这个过程使用消息的次数是默认结果集使用的三倍。在SQLServer7.0中,有一种所谓只向前的快速游标,它使用同样的游标结构。它与流水的表现不一样,因为在发送任何附加SQL消息之前,它不需要您处理全部结果行。所以,如果您带回5行,还有更多的数据,您仍能将更新向下发送给服务器。

一个只向前的快速游标在服务器上比常规游标更快,它让您指定两个附加选项。一个称为自动取数,另一个称为自动关闭。自动取数将返回第一个行集合,作为打开的响应消息的一部分。自动关闭在读完最后一行后自动关闭游标。因为它是只向前的和只读的,所以不能回滚。SQLServer只传回一个带有说明游标已关闭的最后数据集的消息。如果您正在使用只向前的快速游标,则在行数少的消息里,您可向下与同一往返行程通信。如果您有很多行,则您至少还要对每一行块支付附加开销。如果您使用只向前的快速游标,那么游标处理会更加接近默认的结果集。

SQLExecDirect模型流程如图4所示。



图4.客户机/服务器交互

准备/执行模型
除了执行直接模型(在ODBC中用SQLExecDirect调用)外,在ODBC和OLE-DB中,还有一种执行模型,称为准备/执行模型。定义要执行的SQL,是作为一个独立于实际执行SQL的步骤来完成的。以下是ODBC中的一个例子:

SQLPrepare(hstmt,"SELECT*FROMpartswherepartid=?",SQL_NTS)
SQLExecute(hstmt)

在SQLServer7.0版本之前,准备/执行从来都不是SQLServer的本机模式。如今在7.0版本中,有两个提供本机接口的虚拟系统存储过程。对于准备调用,我们要再次研究游标的类型,然后调用sp_prepare或sp_cursorprepare。这些过程会完成SQL或存储过程的编译,但不会实际执行计划。相反,虚拟系统存储过程只是返回该计划的句柄。现在,应用程序可以反复地执行SQL了,例如传入不同的参数值,而不需要重新编译。

在SQLServer6.5中,由于没有本机接口,需要模拟准备和执行两个阶段。可以通过下面的两种方法做到这一点。在第一种方法中,不会真正出现准备阶段。只有执行部分返回元数据(有一些选项可以做到这一点),所以SQLServer可以把结果的格式描述返回给应用程序。在第二种方法中,SQLServer实际上创建一个特定存储过程,这个过程是单个用户私用的,不能共享计划。这第二种方法可能会占满tempdb数据库的空间,因此大多数应用程序开发人员都通过ODBC配置对话框中的复选框,关闭此选项,以使用第二种方法。

在SQLServer7.0中,准备/执行方法是SQLServer的本机功能。准备好SQL语句之后,才会执行它。至于默认的结果集,应用程序只需要调用sp_execute,提供准备操作生成的句柄,语句就会被执行。对于游标,与其他游标处理过程看起来很相似,事实上,它也具有相同的特性,包括如果游标是快速只前向型,还可以使用autofetch和toclose。

准备/执行操作的流程如图5所示。 <script type="text/javascript"><!-- google_ad_client = "pub-2947489232296736"; /* 728x15, 创建于 08-4-23MSDN */ google_ad_slot = "3624277373"; google_ad_width = 728; google_ad_height = 15; //--> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
<script type="text/javascript"><!-- google_ad_client = "pub-2947489232296736"; /* 160x600, 创建于 08-4-23MSDN */ google_ad_slot = "4367022601"; google_ad_width = 160; google_ad_height = 600; //--> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
阅读更多
个人分类: 数据库
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭