水滴石穿

渴望成为高手--Amy.Qiu
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

转:SQL Server查询处理器机制与结构

Posted on 2013-04-26 17:31  AmyQiu  阅读(177)  评论(0编辑  收藏  举报

把计划放入缓存之后,SQL Manager 按照执行要求逻辑进行检查,确定是否有更改的内容,是否需要重新编译。即使编译到执行之间时间间隔只有几毫秒,也可能有人会执行一条数据定义语句 (DDL),为关键的表加了索引。这种可能性不大,但是确实存在,因此 SQL Server 必须考虑这一点。有几种情况 SQL Server 必须重新编译存储规划。元数据的修改,例如增加或删除索引,是重新编译的最主要的原因。服务器必须确信所使用的计划反映了索引的当前状态。

重新编译的另一种原因是统计情况发生变化。SQL Server 还维护不少数据使用频率的统计信息。如果数据使用频率分布情况变化很大,则可能需要另一个查询计划以便更有效地执行。SQL Server 跟踪表数据插入和删除的统计数据,如果数据修改的数量超过根据表的容量变化的某一阈值,则需要根据新的分布数据重新编译计划。

图 7 给出了编译和执行过程的流程。

  

图 7. 编译与执行

注意,实际参数的改变并不会导致重新编译,环境的改变,例如可用内存的增加或所需数据的增加,也不会导致重新编译。

执行是比较简单的,如果需要执行的查询很简单,如"插入一行",或从带有唯一索引的表中查询数据,则执行处理会非常简单。但是,很多查询都要求大量的内存以提高运行效率,或至少从所增加的内存得到好处。在 SQL Server 6.5 中,每个查询能够使用的内存限制在 0.5MB 或 1MB 以下。有一个控制查询内存使用的参数,称为排序页。顾名思义,它主要是限制可能占用大量内存的排序操作。不管要处理的排序有多大,在 SQL Server 6.5 中,内存的使用不能超过 1MB。即使您使用的机器上配置了 2GB 内存,需要对数百万行数据排序,也不能突破限制。显然,复杂的查询不能高效执行,因此 SQL Server 开发人员增加了 SQL Server 7.0 的能力,使得单个查询可以使用大量的内存。

另一个问题随之而来。一旦您开始允许查询使用大量内存,就必须确定如何把内存分配给可能需要内存的很多查询。SQL Server 按照以下方法解决这个问题。当查询计划优化之后,优化器要确定有关给该查询使用的内存的两部分信息。第一,该查询有效执行所需要的最小内存,该参数与查询计划一起存放。优化器还要确定该查询可以获益的

过程缓存

我们在前面已经多次提到 SQL Server 的过程缓存。需要注意的是,SQL Server 7.0 的过程缓存与以前的版本有很大不同。在早期的版本中,有两个有效配置值用于控制过程缓存的容量:一个是定义 SQL Server 总可用内存的固定容量,另一个是供存储查询计划使用的内存百分比(扣除满足固定需要的内存)。在老版本中,特定 SQL 语句从不存入缓存,只有存储过程计划才存入其中。在 SQL Server 7.0 中,内存的总容量是动态的,用于查询计划的空间也是经常变化的。

在处理查询时,SQL Server 7.0 首先会问的是:这个查询既是特定的又是易于编译的吗?如果是,SQL Server 就根本不会将其写入缓存中。将来重新编译这些计划比把复杂的计划或数据页推出内存更合算。如果查询不是特定的或不易于编译,则 SQL Server 会从缓存区中分配一些缓存内存存储该计划,因为该缓存区是 SQL Server 7.0 用来满足 99% 内存需求的唯一来源。在少数特殊情况下,SQL Server 会直接从操作系统中分配大块内存,但是这种情况极为罕见。SQL Server 的管理是集中式的。

 

上一页 1 2 3 下一页

写入缓存的除计划外,还有反映通过编译该查询实际创建该计划的成本的成本因子。如果这是一个特定计划,则 SQL Server 将它的成本设置为 0,表示可以立即将它撤出过程缓存。对于特定 SQL,虽然有可能被重复使用,但可能性很小,如果系统内存紧张,总是愿意首先撤出特定语句的计划。这样,特定查询的计划是最适合清出缓存的对象。如果查询不是特定的,则 SQL Server 会把该成本设置为实际编译查询的成本。这些成本是以磁盘 I/O 为单位的。如果从磁盘中读出一个数据页,则有一个 I/O 成本。在编译计划时,信息从磁盘中读出,包括统计数据和查询本身的文本。SQL 要进行附加的处理,而且这处理工作被正常化为 I/O 成本。现在,建立过程的成本可用执行 I/O 的成本表示。该成本非常恰当反映了,与打算用磁盘缓存的数据量相比,管理实际打算分配给存储过程和任何种类查询计划的缓存量的能力。该成本被计算出来之后,该计划就会被写入缓存。

图 8 显示计算计划成本并将其写入缓存的流程。

   

图 8. 将计划写入缓存

如果另一个查询可以重新使用该计划,则 SQL Server 要再次判定计划的类型。如果是一个特定计划,SQL Server 会把成本加 1。这样,如果特定计划确实要被重新使用,则它会在缓存中稍作停留,停留时间越长,成本就增加越多。如果该计划经常被重新使用,则成本会一次增加一个单位地不断增长,直到增长到其实际编译成本。该成本和设置的成本一样高。不过该计划经常被重复使用;如果同一用户或其他用户不断重新提交完全一样的 SQL 文本,该计划就会留在缓存中。

如果查询不是特定的,也就是说是一个存储过程、带参数的查询或自动参数化的查询,则每次该计划被重新使用时,成本都会设置回原来的值。只要计划被重新使用,就会留在缓存中。即使有一段时间没有被使用,取决于最初的编译代价的高低,计划停留在缓存中的时间也有长短。

图 9 显示从缓存中检索计划并调整成本的流程。

   

图 9. 从缓存中检索计划

迟缓写入器(Lazywriter) 是使计划过时的机制,负责在必要的时候从缓存中删除计划。迟缓写入器实际上是存储引擎的一部分,但是因为迟缓写入器对于查询处理机制是如此重要,我们还是在这里进行讨论。迟缓写入器管理查询计划内存使用的机制与管理页面的机制一样,因为 SQL Server 7.0 计划存储在普通缓冲存储器中。迟缓写入器要检查系统中所有的缓冲器标题。如果系统的内存不紧张,检查的次数就很少;如果开始紧张,则迟缓写入器就会经常运行。当迟缓写入器运行时,它要检查缓冲区标题,并检查缓存区中该页面的当前成本。如果成本为 0,则意味着自从上次迟缓写入器检查以来,该页面没有被使用过,于是迟缓写入器就会释放该页面,以便为系统增加可用内存,用于页面 I/O 或其他计划。此外,如果该缓冲区包含过程计划,则迟缓写入器会调用 SQL 管理器,以完成一些清理工作。最后,该缓冲区会被放到可用内存表中供重新使用。

如果与缓冲区关联的成本大于 0,则迟缓写入器会把成本减 1,并继续检查其他缓冲区。这成本实际上反映的,某计划若是没被使用,它在缓存中还能存在多少个迟缓写入器的检查周期。这种算法,除了如果对象是存储过程则调用 SQL Manager 这一步之外,对缓存中的计划和缓存的数据或索引没有什么区别。迟缓写入器并不知道对象是否存储过程,这种算法很好地平衡了磁盘 I/O 对缓存的使用和存储过程计划对缓存的使用。

您会发现,如果计划的编译成本很高,那么即使很长一段时间都没有被重新使用,也仍然会停留在缓存中,这是因为其初始成本太高了。经常被重新使用的计划也会长期停留在缓存中,这是因为每当它被重新使用时其成本已被重新设置,迟缓写入器不会看到它的成本降为 0。

图 10 显示迟缓写入器处理缓存的流程。

   

图 10. 迟缓写入器处理缓存的流程

 

上一页 1 2 3

处理客户机的 SQL

下面再看看提交 SQL 语句之后的处理过程。首先,我们将研究客户机向 SQL Server 发送 RPC 事件。因为 SQL Server 收到了 RPC 事件,所以它会知道该事件是某种参数化的 SQL;它是准备/执行模型,或者是 EXECUTESQL。SQL Server 需要构建一个缓存键,以标识这个具体的 SQL Server 文本。如果 SQL Server 处理的是实际的存储过程,则不需要建立它自己的键;直接使用该过程的名称即可。对于通过 RPC 调用发来的简单 SQL 文本,则通过杂凑该 SQL 文本来建立缓存键。此外,该键还要反映一定的状态信息,如某些 ANSI 设置。使所有 ANSI 设置为 ON 的连接和另一个使所有 ANSI 设置为 OFF 的连接,即使它们来自相同的查询,也不能使用相同的计划。处理过程是不同的。例如,如果一个连接把 concat_null_yields_null 设置为 ON,另一个把 concat_null_yields_null 设置为 OFF 的连接,即使它们执行的是完全相同的 SQL 文本,但所产生的结果则完全不同。这样,SQL Server 可能需要在缓存中保存计划的多个版本,每个版本对应于一个不同的 ANSI 设置组合。启用的选项设置是键的一部分,而键字是使用这种缓存处理机制检查对象的核心,因此 SQL Server 建立这种键并用来检查缓存。如果在缓存中没有发现该计划,则 SQL Server 会按照前面介绍的方式编译该计划,并把该计划与键一起存入缓存中。

SQL Server 还需要确定该命令是否是准备操作,这意味着该计划应该只编译但不执行。如果是准备操作,则 SQL Server 会给客户机返回一个句柄,供客户机在以后检索并执行该计划。如果不是一个准备操作,则 SQL Server 提取并执行该计划,就像最初从缓存中找到该计划一样。

准备/执行模型为缓存管理增加了复杂因素。预备给出了今后能够执行该计划的句柄。应用程序可以在几小时或几天之内保持该句柄是激活的,以定期执行计划。即使需要在缓存中为更多的活动计划或数据页面腾出空间,也不能使该句柄无效。SQL Server 实际所做的就是将计划放入缓存,此外还从预备操作中将 SQL 保存到更加紧凑的空间。如果空间紧张,则可按前述的方式释放计划所占用的空间,但仍有 SQL 的副本准备着。如果客户机要执行预备的 SQL,但在缓存中没有找到计划,则 SQL Server 能够检索到该文本并编译它,再将它放回缓存中。这样,缓存中的 16 千字节 (KB) 或更多的页面用来保存可重用的计划,而长期占用的空间或许是存储在其他处的 SQL 代码的 100 或 200 字节。

处理来自客户机的语句时的另一种情况是,查询是作为 SQL 语言事件出现的。除了一点以外,此流程并无太大的差异。在这种情况下,SQL Server 试图使用称为自动参数化的技术。SQL 文本与自动参数化模板相匹配。自动参数化是个棘手的问题,因此,过去一直能够利用共享的 SQL 的其他数据库管理产品, 一般并没有提供这一选项。随之而来的问题是,如果 SQL Server 自动地参数化每个查询,那么对于随后提交的某些特定值而言,这些查询中的某些(或绝大多数)将获得非常糟糕的计划。在程序员将参数标记放在代码之中的场合下,其假定是程序员知道所期望的值的范围,并愿意接受 SQL Server 提供的计划。但当程序员实际补充一个特定的值,并且 SQL Server 决定将该值当做一个可变的参数来对待时,所产生的任何适合于某个值的计划可能不适合于后续的值。利用存储过程,通过在过程中放入 WITH RECOMPILE 选项,程序员可以强制产生新的计划。利用自动参数化,程序员无法指出必须为每一个新值开发新的计划。

 出处:http://www.szwblm.com/Databases/ShowArticle.asp?ArticleID=43835