数据仓库的 RDBMS 性能优化指南(2)

http://tech.ddvip.com   2008年09月09日    社区交流

本文详细介绍数据仓库的 RDBMS 性能优化指南(2)

  计算列上的索引

  SQL Server 2000 引入了在计算列上创建索引的功能。如果查询是以一般方式提交的,而且会例行提供计算列,但管理员不愿意只是为了允许创建索引而在实际的表列中持续存放数据,在这样的情况下,使用这项功能就会很方便。在此情况下,只要计算列满足索引所需的全部条件,就可以通过引用计算列来创建索引。其他限制包括,计算列表达式必须有确定性、精确,并且不得取值为 text、ntext 或 image 数据类型。

  确定性

  如果要在视图或计算列上创建索引,视图和计算列均无法唤醒调用没有确定性的用户定义函数。所有函数要么有确定性,要么没有确定性:

  无论何时使用一组特定的输入值调用有确定性的函数,这些函数总是会返回相同的结果。

  每次用特定的一组输入值调用没有确定性的函数时,这些函数返回的结果可能各不相同。

  例如,DATEADD 内置函数有确定性,因为对于通过该函数的三个输入参数传入的一组给定参数值,它始终返回可预测的结果。GETDATE 没有确定性。虽然始终用相同的参数值唤醒调用 GETDATE 函数,但每次执行调用返回的值各不相同。

  精确

  如果满足下列条件,说明计算列表达式是精确的:

  它不是 float 数据类型的表达式。

  它不在自己的定义中使用 float 数据类型。例如,在下面的语句中,列 y 是 int 并且有确定性,但不精确。

CREATE TABLE t2 (a int, b int, c int, x float,
y AS CASE x
WHEN 0 THEN a
WHEN 1 THEN b
ELSE c
END)

  COLUMNPROPERTY 函数的 IsPrecise 属性报告 computed_column_expression 是否精确。

  注意 任何 float 表达式均被视为不精确,不能作为索引的键;float 表达式可以在索引视图中使用,但不能用作键。这一规则同样适用于计算列。任何函数、表达式、用户定义函数或视图定义,只要包含任何 float 表达式,包括逻辑表达式(比较),均被视为没有确定性。

  如果在计算列或视图上创建索引,先前能够正确执行的 INSERT 或 UPDATE 操作现在可能无法执行。在计算列导致算术错误时,可能会发生此类无法执行的情况。例如,虽然下表中的计算列 c 导致算术错误,但 INSERT 语句会起作用:

CREATE TABLE t1 (a int, b int, c AS a/b)
GO
INSERT INTO t1 VALUES ('1', '0')
GO

  如果改为在创建该表之后在计算列 c 上创建索引,相同的 INSERT 语句将会失败。

CREATE TABLE t1 (a int, b int, c AS a/b)
GO
CREATE UNIQUE CLUSTERED INDEX Idx1 ON t1.c
GO
INSERT INTO t1 VALUES ('1', '0')
GO

  索引视图

  索引视图是为了实现快速访问而将其结果持续存放于数据库内并创建索引的视图。与任何其他视图一样,索引视图也依靠基表来提供视图数据。此类相关性意味着,如果更改为索引视图提供数据的基表,索引视图可能变得无效。例如,重命名为视图提供数据的列会使该视图无效。为了避免此类问题,SQL Server 支持创建具有架构绑定的视图。架构绑定禁止对表或列进行任何会使视图无效的修改。使用视图设计器创建的索引视图自动获得架构绑定,因为 SQL Server 要求该索引视图具有架构绑定。架构绑定并不是说您不能修改视图;它的意思是您不能按更改视图结果集的方式来修改基础表或视图。另外,就像计算列上的索引一样,索引视图也必须有确定性、精确,且不得包含 text、ntext 或 image 等列。

  索引视图在基础数据不经常更新的情况下效果最佳。维护索引视图的成本可能高于维护表索引的成本。如果基础数据更新频繁,索引视图数据的维护成本就可能超过使用索引视图带来的性能收益。

  索引视图改进了以下几类查询的性能:

  处理多行的联接和聚合。

  许多查询经常执行的联接和聚合操作。

  例如,在某个记录清单的 OLTP 数据库中,预计许多查询要联接 Parts、PartSupplier 和 Suppliers 表。虽然执行这一联接的每个查询不一定都会处理许多行,但成千上万个查询的联合处理加在一起仍然是非常庞大的操作。因为不太可能经常更新这些关系,所以通过定义存储联接结果的索引视图,即可改进整个系统的总体性能。

  决策支持工作负荷。

  分析系统的特点是存储不经常更新的概要数据、聚合数据。许多决策支持查询的特点是进一步聚合数据和联接许多行。

  索引视图通常不会改进以下几类查询的性能:

  经常写入的 OLTP 系统。

  经常更新的数据库。

  不涉及聚合或联接的查询。

  键基数程度高的数据聚合。基数程度高意味着该键包含许多不同的值。唯一键具有基数可能的最高程度,因为每个键的值各不相同。索引视图通过减少查询必须访问的行的数量来改进性能。如果视图结果集的行数量几乎与基表的行数量相同,那么使用该视图几乎就无任何性能收益可言。例如,对于具有 1,000 行的表考虑使用此查询:

  SELECT PriKey, SUM(SalesCol)

  FROM ExampleTable

  GROUP BY PriKey

  如果表键的基数为 100,使用此查询的结果生成的索引视图就只有 100 行。使用该视图的查询平均需要的读取次数为读取基表次数的十分之一。如果该键是一个唯一的键,而该键的基数是 1000,视图结果集将返回 1000 行。使用该索引视图,而不直接读取基表,查询不会带来任何性能改进。

  展开联接,这些联接是结果集大于基表内原始数据的视图。

  您设计的索引视图应能满足多个操作。因为,即使未在 FROM 子句中指定索引视图,优化程序也能使用索引视图,所以设计完好的索引视图可以加快许多查询的处理速度。例如,考虑在以下视图上创建索引:

CREATE VIEW ExampleView (PriKey, SumColx, CountColx)
AS
SELECT PriKey, SUM(Colx), COUNT_BIG(Colx)
FROM MyTable
GROUP BY PriKey

  该视图不仅满足直接引用视图列的查询,而且可以用来满足查询基础基表并且包含 SUM(Colx)、COUNT_BIG(Colx)、COUNT(Colx) 和 AVG(Colx) 等表达式的查询。所有此类查询的速度都会更快,因为它们只需检索视图中的少数几列,而不必读取基表中的所有列。

  在视图上创建的第一个索引必须是唯一的聚集索引。创建了唯一的聚集索引之后,您可以创建其他非聚集索引。视图上的索引命名规则与表上的索引命名规则相同。唯一不同的是表名会替换为视图名。

  如果除去视图,视图上的所有索引也将被除去。如果除去聚集索引,视图上的所有非聚集索引也将被除去。非聚集索引可被单独除去。除去视图上的聚集索引会删除存储的结果集,优化程序会恢复以标准视图的处理方式来处理该视图。

  虽然在 CREATE UNIQUE CLUSTERED INDEX 语句中仅指定构成聚集索引键的列,但视图的完整结果集存储于数据库中。就像在基表的聚集索引中一样,聚集索引的 B 树结构只包含键列,但数据行包含视图结果集中的所有列。

  注意 SQL Server 2000 的任何版本均可创建索引视图。在 SQL Server 2000 企业版中,索引视图将由查询优化程序自动考虑。要在所有其他版本中使用索引视图,必须使用 NOEXPAND 提示。

  覆盖索引

  覆盖索引是根据满足 SQL 查询(在选择条件和 WHERE 谓词两方面满足)所需的所有列建立的非聚集索引。覆盖索引可以节省大量 I/O,从而极大地提升了查询的性能。但是,您需要平衡考虑新索引的创建成本(及其相关的 B 树索引结构维护成本)与覆盖索引带来的 I/O 性能收益。如果覆盖索引将会极大地提升某个查询或某组查询的性能,而这些查询经常在 SQL Server 上运行,那么就值得创建覆盖索引。

  下面的示例说明如何使用覆盖索引交集:

  Create index indexname1 on table1(col2,col1,col3)

  Select col3 from table1 where col2 = 'value'

  执行上述查询时,只会读取较少的索引页,所以能够迅速从基础表中检索到所需的值,从而非常高效地解析该查询。通常,如果覆盖索引较小(就索引中所有列的字节数与该表的单行内的字节数相比较而言),并且使用覆盖索引的查询确实会经常执行,那么适合使用覆盖索引。

  索引选择

  对索引的选择会极大地影响生成的磁盘 I/O 数量,进而极大地影响性能。非聚集索引适合于检索少量的行,而聚集索引适合于区间扫描。下列原则有助于选择要使用的索引类型:

  尽量精简索引(行数和字节数保持最小)。该原则尤其适用于聚集索引,因为非聚集索引将使用聚集索引作为定位行数据的方法。

  在非聚集索引中,选择性非常重要。如果在只有几个唯一值的大表上创建非聚集索引,使用该非聚集索引不会在数据检索过程中节省大量 I/O。实际上,使用该索引生成的 I/O 很可能远远多于只是执行连续表扫描生成的 I/O。适合使用非聚集索引的对象包括发票号、唯一的客户号、社会保障号和电话号码。

  对于涉及到区间扫描的查询,或在经常使用列联接其他表时,聚集索引比非聚集索引的效果好。原因是聚集索引对表数据进行物理排序,允许键值上存在连续的 64 KB I/O。适合使用聚集索引的对象包括省、公司分支机构、销售日期、邮政编码和客户地区。

  针对一个表只能创建一个聚集索引;如果典型查询经常从表的某一列提取大量连续区间,而表的其他列包含唯一值,则在第一列上使用聚集索引,在包含唯一值的列上使用非聚集索引。在每个表上尝试选择用来创建聚集索引的最佳列时,要问的关键问题是:“是否会有大量查询需要根据该列的顺序来提取大量的行?”。答案视每个用户环境的具体情况而定。某个公司可能根据日期区间进行大量查询,而另一个公司可能根据银行分支机构的区间进行大量查询。

  索引的创建和并行操作

  在 SQL Server 2000 企业版和 Developer Edition 中,针对创建索引而建立的查询计划允许在配有多个微处理器的计算机上进行并行、多线程的索引创建操作。

  SQL Server 在为创建索引操作确定并行度(同时运行的单个线程的总数)时使用的算法与为其他 Transact-SQL 语句确定并行度时使用的算法相同。唯一不同是创建索引的 CREATE INDEX、CREATE TABLE 或 ALTER TABLE 语句不支持 MAXDOP 查询提示。索引创建的最大并行度取决于最大并行度服务器配置选项,但您不能为各索引创建操作设置不同的 MAXDOP 值。

  在 SQL Server 建立创建索引查询计划时,并行操作的数量以下面的最小值为准:

  计算机中微处理器或 CPU 的数量。

  最大并行度服务器配置选项中指定的数量。

  尚未超过 SQL Server 线程工作阈值的 CPU 数量。

  例如,某台计算机配有八个 CPU,但最大并行度选项设置为 6,那么为创建索引生成的并行线程不会超过六个。如果在建立索引创建执行计划时,计算机中的五个 CPU 超过 SQL Server 工作的阈值,执行计划将只指定三个并行线程。

  并行索引创建的主要阶段包括:

  协调线程对表进行快速随机的扫描,以估计索引键的分发情况。协调线程建立键边界,键边界的作用是创建多个键区间,键区间的数量与并行操作度相等,预计每个键区间内包含的行数大致相同。例如,如果表中有四百万行,而最大并行度选项设置为 4,协调线程将确定分隔四个行集的键值,每个行集包含一百万行。

  协调线程根据并行操作度分派同等数量的多个线程,然后等待这些线程完成工作。每个线程使用筛选器扫描基表,筛选器只在分配给线程的区间内检索具有键值的行。每个线程为其键区间内的行建立索引结构。

  在所有并行线程完成工作后,协调线程将多个索引子单元连接到单个索引中。单独的 CREATE TABLE 或 ALTER TABLE 语句可以具有多个需要创建索引的约束。虽然可以在配有多个 CPU 的计算机上并行执行每个索引创建操作,但此处所述的多个索引创建操作仍然按序执行。

  索引维护

  在数据库中创建索引时,查询使用的索引信息存储在索引页中。连续索引页之间通过指针一页一页相互链接在一起。对影响索引的数据进行更改时,数据库中的索引信息会被拆散。重建索引将重新组织索引数据的存储(如果为聚集索引,还将重新组织表数据的存储),以便删除碎片。这样可以减少为获得请求数据所需的页读取的数目,从而改进磁盘性能。

  插入活动或更新会修改聚集索引的搜索键值,在执行大量的插入活动或更新时,即会出现碎片。因此,为了防止拆分索引页和数据页,您应该尝试在索引页和数据页上保留一定的开放空间,这一点很重要。如果索引页或数据页不能再存放任何新行,而且由于该页中定义的数据的逻辑排序,需要将某一行插入该页,则会拆分该页。出现此情况时,SQL Server 需要对一整页的数据进行分割,将大约一半的数据移到新页上,这样,新旧两页都能保留一定的开放空间。因为这样会耗用系统资源和时间,所以建议不要经常这样做。

  最初建立索引时,SQL Server 尝试将索引的 B 树结构放在物理上连续的页上;这样就能在使用连续 I/O 扫描索引页时优化 I/O 性能。在发生拆分页和需要将新页插入索引的逻辑 B 树结构时,SQL Server 必须分配新的 8 KB 索引页。如果在硬盘的其他位置发生此情况,将会破坏索引页的物理连续特性。这样会导致 I/O 操作的执行从连续切换为不连续,而且还会极大地降低性能。通过重建索引来恢复索引页的物理连续顺序,应该能解决页拆分过多的问题。同样的行为还可能在聚集索引的叶级别上发生,从而影响表的数据页。

  在系统监视器中,尤其要注意“SQL Server:访问方法 – 页拆分/秒”。该计数器的非零值表示正在进行页拆分,应使用 DBCC SHOWCONTIG 作进一步分析。

  DBCC SHOWCONTIG 命令也可用来揭示表上是否已进行了过多的页拆分。扫描密度是 DBCC SHOWCONTIG 提供的关键指标。该值应尽量接近 100%,越接近越好。如果该值大大低于 100%,请考虑对出现问题的索引运行维护。

  DBCC INDEXDEFRAG

  一个索引维护选项要使用 SQL Server 2000 中引入的新语句 (DBCC INDEXDEFRAG)。DBCC INDEXDEFRAG 可以为表和视图上的聚集索引和非聚集索引整理碎片。DBCC INDEXDEFRAG 在索引的叶级别整理碎片,因此各页的物理顺序与叶节点从左至右的逻辑顺序一致,从而改进了索引扫描性能。

  DBCC INDEXDEFRAG 还压缩索引的各页,它会考虑在创建索引时指定的 FILLFACTOR。由压缩创建的空页将被删除。

  如果索引跨越多个文件,DBCC INDEXDEFRAG 一次为一个文件整理碎片。索引页不会在文件之间迁移。DBCC INDEXDEFRAG 每隔五分钟向用户报告一次预计已完成的百分比。在执行过程中,您随时都可以终止 DBCC INDEXDEFRAG,已完成的所有工作会被保留。

  与 DBCC DBREINDEX(或一般的索引建立操作)不同,DBCC INDEXDEFRAG 是联机操作。它不会长期保持锁定,因而不会阻止运行查询或更新。为相对而言没有碎片的索引整理碎片可以比建立新索引快,因为整理碎片所需的时间与碎片量相关。为非常零碎的索引整理碎片的时间可能比重建索引的时间要长得多。此外,不论数据库恢复模型设置如何,始终完全记录碎片整理情况(请参阅 ALTER DATABASE)。为非常零碎的索引整理碎片所生成的日志甚至可能比记录整个索引创建过程所生成的日志还多。不过,由于碎片整理是作为一系列小事务执行的,因此,如果经常进行日志备份,或者恢复模型设置为 SIMPLE,则不需要大日志。

  另外,如果两个索引在磁盘上交错存放,则不适合使用 DBCC INDEXDEFRAG,因为 INDEXDEFRAG 会打乱索引页的位置。要改进索引页的聚集,请重建索引。出于相同的原因,DBCC INDEXDEFRAG 无法更正页拆分。对于已按反映搜索键的连续顺序分配的索引页,它实质上会进行重新排序。索引页的次序可能因多种原因而变得不正确,这些原因包括:无序数据装载、过多的插入、更新、删除活动,等等。

  “SQL Server 联机丛书”中提供了一段示例代码,您只需对该代码稍加修改,即可使用它来自动执行各种索引维护任务。该示例说明如何用一种简单的方法来对数据库中碎片量大于声明阈值的所有索引进行碎片整理。有关更多信息,请参阅“SQL Server 联机丛书”中的主题“DBCC SHOWCONTIG”。

  DBCC DBREINDEX

  根据所用语法不同,DBCC DBREINDEX 可以只重建表的某一个指定索引,或者也可以重建表的所有索引。与除去并重新创建各个索引时采用的方法类似,DBCC DBREINDEX 语句也具备能够在一条语句中重建表的所有索引这一优点。这样比编写单独的 DROP INDEX 和 CREATE INDEX 语句更方便,并且,在重建表的一个或多个索引时,不必知道表结构或任何指定的约束条件。另外,DBCC REINDEX 语句固有原子性。如果要在编写单独的 DROP INDEX 和 CREATE INDEX 语句时获得相同的原子性,必须将多个单独的命令包含在一个事务内。

  与单独的 DROP INDEX 和 CREATE INDEX 语句相比,DBCC DBREINDEX 会自动利用更多优化方案,在多个非聚集索引引用具有聚集索引的表时尤其如此。DBCC DBREINDEX 也可用于重建强制 PRIMARY KEY 或 UNIQUE 约束的索引,而不必删除和重新创建约束(因为,如果不先删除约束,则无法删除为了强制 PRIMARY KEY 或 UNIQUE 约束而创建的索引)。例如,您可能希望通过在 PRIMARY KEY 约束上重建索引来为索引重新建立给定的填充因子。

  DROP_EXISTING

  重建索引或整理索引碎片的另一种方法是:除去索引后再重新创建索引。通过删除旧索引,然后再重新创建相同的索引来重建聚集索引,这种方法很昂贵,因为所有二级索引都依赖指向数据行的聚集键。如果只删除聚集索引,然后再重新创建索引,您可能会不慎导致所有引用非聚集索引被删除和重新创建两次。在除去聚集索引时进行第一次除去/重新创建。在重新创建聚集索引时进行第二次除去/重新创建。

  为了避免这一开销,使用 CREATE_INDEX 的 DROP_EXISTING 子句就可以一步完成这一重新创建的过程。采用一个步骤重新创建索引会告诉 SQL Server 您要重新组织现有索引,并避免删除和重新建相关非聚集索引等不必要的工作。这种方法还有一点明显的好处:可以使用现有索引中已预先排序的数据,因而不需要执行数据排序。这样就可以明显减少重新创建聚集索引的时间和成本。

  DROP INDEX / CREATE INDEX

  维护索引的最后一种方法是:直接除去索引,然后再重新创建索引。此选项仍在广泛使用,并且可能是以下人员的首选:熟悉此选项的人员、其处理窗口能够容纳表上所有索引完整的重新创建的人员。使用此方法的缺点是必须手动控制事件,以使事件按照适当的顺序发生。在手动除去和重新创建索引时,一定要在除去和重新创建聚集索引之前,除去所有非聚集索引。否则,在创建聚集索引时,将自动创建所有非聚集索引。

  手动创建非聚集索引有一个优点:各个非聚集索引可以同时重新创建。不过,您的分区策略可能会影响所生成的索引的物理布局。如果同时在同一个文件(文件组)上重建两个非聚集索引,这两个索引的索引页可能在磁盘上交错在一起。这可能会打乱数据的存储顺序。如果多个文件(文件组)位于不同的磁盘上,您可以指定单独的文件(文件组)在创建索引之后保存索引,从而保持了索引页的顺序连续性。

  前面提及的有关在预先排序的数据上建立索引的问题在此处同样适用。在已排序的数据上建立的聚集索引不必执行额外的排序步骤,从而可以极大地减少建立索引所需的时间和处理资源。

  FILLFACTOR 和 PAD_INDEX

  FILLFACTOR 选项提供了一种方法,用于指定在索引页和数据页上保留的开放空间的百分比。CREATE INDEX 的 PAD_INDEX 选项会在非叶级别的索引页上应用 FILLFACTOR 的设置。如果没有 PAD_INDEX 选项,FILLFACTOR 主要影响聚集索引的叶级别索引页。最好同时使用 PAD_INDEX 选项和 FILLFACTOR 选项。

  PAD_INDEX 和 FILLFACTOR 用于控制页拆分。为 FILLFACTOR 指定的最佳值取决于在给定时间段内插入 8 KB 索引页和数据页的新数据量。请记住,通常,SQL Server 索引页包含的行数远远多于数据页包含的行数,因为索引页只包含与该索引相关的列数据,而数据页包含整行的数据,这一点很重要。

  另外,请记住维护窗口的出现频率,维护窗口允许重建索引,以便更正即将发生的页拆分。请尝试只在大多数索引页和数据页已填满数据时再重建索引。如果表的聚集索引选择得当,则不会经常需要重建索引。如果聚集索引均匀地分布数据,从而所有与表相关的数据页上都会在该表中插入新行,那么,数据页将会均匀填充。总体说来,这将在开始发生页拆分且有必要重建聚集索引之前提供更多的时间。

  为了确定用于 PAD_INDEX 和 FILLFACTOR 的适当的值,您需要发出判断请求。在作决定之前,您应该考虑两方面:一是在页上保留大量开放空间,二是可能发生的拆分页的数量,这两方面要保持性能上的平衡。如果为 FILLFACTOR 指定的百分比很小,它将在索引页和数据页上保留大量开放空间,这样,为了回答查询,SQL Server 就需要读取大量部分填充的页。对于大量读取操作而言,如果索引页和数据页上的压缩数据越多,SQL Server 的处理速度会明显加快。指定过高的 FILLFACTOR 会使各页上保留的开放空间过少,这样,各页很快就会溢出,从而导致页拆分。

  在确定 FILLFACTOR 或 PAD_INDEX 值之前,请记住,在许多数据仓库环境中,读取操作的数量往往比写入操作的数量多得多。不过,如果定期装载数据,可能就不是这种情况了。许多数据仓库管理员尝试对表/索引进行分区和组织,以便容纳预计会出现的定期数据装载。

  根据一般经验,如果预计的写入量相当于读取量的一大部分,最佳方法是按可行情况尽高地指定 FILLFACTOR,同时在每个 8 KB 页上保留足够的可用空间,以避免经常发生页拆分,至少要让 SQL Server 能够到达重新创建索引所需的下一个可用时间窗。该策略均衡了 I/O 性能(尽量填满各页),并且避免了页拆分(不让各页溢出)。如果不写入 SQL Server 数据库,FILLFACTOR 应设置为 100%,以便填满所有索引页和数据页,获得最佳 I/O 性能。

  用于分析和优化的 SQL Server 工具

  本节提供在表中装载数据的示例代码,稍后再用该段代码说明如何使用 SQL 事件探查器和 SQL 查询分析器分析和优化性能。

  样本数据和工作负荷

  下面的示例说明如何使用 SQL Server 性能工具。首先构造下表:

create table testtable
(nkey1 int identity,
col2 char(300) default 'abc',
ckey1 char(1))

  然后,在该表中装载 20,000 行测试数据。装载到 nkey1 列的数据适用于非聚集索引。ckey1 列中的数据适用于聚集索引,col2 中的数据只是为了将每行的大小增加 300 个字节而填入的数据。

declare @counter int
set @counter = 1
while (@counter <= 4000)
begin
insert testtable (ckey1) values ('a')
insert testtable (ckey1) values ('b')
insert testtable (ckey1) values ('c')
insert testtable (ckey1) values ('d')
insert testtable (ckey1) values ('e')
set @counter = @counter + 1
end

  下列查询构成了数据库服务器工作负荷:

select ckey1 from testtable where ckey1 = 'a'
select nkey1 from testtable where nkey1 = 5000
select ckey1,col2 from testtable where ckey1 = 'a'
select nkey1,col2 from testtable where nkey1 = 5000

  SQL 事件探查器

  优化性能的常用方法通常称为标记和度量。要验证为改进性能所做的更改是否确实改进了性能,首先需要建立现有不良性能情况的基线或标记。度量 指建立一些可量化的方法,用来证明性能正在得到改进。

  SQL 事件探查器是用来进行标记和度量的工具。它不仅可以捕获服务器内发生的活动,供您进行性能分析;而且可以稍后再回放该活动。SQL Server 中的回放功能提供了一种有用的回归测试工具。使用回放功能,您可以方便地确定目前为了改进性能而采取的操作是否能达到预期效果。

  回放功能还可以模拟负载或压力测试。您可以设置多个事件探查器客户端会话,让它们同时回放。例如,利用这一功能,管理员可以轻松地捕获五个并发用户的活动,然后同时启动十个回放,模拟有 50 个并发用户时的系统性能。您还可以跟踪数据库活动,然后在正进行修改的数据库中回放该活动,或在正进行测试的新硬件配置中回放该活动。

  请记住,您可以利用 SQL 事件探查器记录 SQL Server 数据库中发生的活动。可以对 SQL 事件探查器进行配置,让它监视和记录对 SQL Server 执行查询的一个或多个用户。除了 SQL 语句外,使用该工具还能捕获各种各样的性能信息。使用 SQL 事件探查器记录的某些性能信息包括:I/O 统计信息、CPU 统计信息、锁定请求、Transact-SQL 和 RPC 统计信息、索引和表扫描、引发的警告和错误、数据库对象的创建/除去、建立连接/断开连接、存储过程操作、游标操作,等等。

  捕获供索引优化向导使用的事件探查器信息

  SQL 事件探查器和索引优化向导的结合使用,形成了一个功能非常强大的工具组合,它可以帮助数据库管理员确保在表和视图上放置正确的索引。SQL 事件探查器可以将查询的资源耗用情况记录到三个位置上。可以将输出定向到 .trc 文件、SQL Server 表或监视器。之后,索引优化向导从 .trc 文件或 SQL Server 表读取捕获的数据。索引优化向导对捕获的工作负荷中的信息和有关表结构的信息进行分析,然后针对改进性能提出应该创建哪些索引的建议。有了索引优化向导,

  您就能自动完成以下任务:为数据库创建正确的索引、调度稍后进行的索引创建、生成可以手动检查和执行的 Transact-SQL 脚本。

  分析查询负载需要完成以下步骤:

  设置 SQL 事件探查器

  在工具菜单上选择 SQL 事件探查器,从 SQL Server 企业管理器中启动 SQL 事件探查器。

  按 CTRL+N 键新建一个 SQL 事件探查器跟踪。在连接到 SQL Server 对话框中,选择要连接到的服务器。

  从下拉列表框中选择 SQLProfilerTuning 模板。

  选中另存为文件或另存为表复选框。另存为表选项将打开连接对话框,在该对话框中,您可以将跟踪信息保存到探查查询的服务器以外的其他服务器。如果要将跟踪的活动同时另存为文件和表,请选中这两个复选框。如果要另存为 .trc 文件,请指向有效的目标和文件名。如果以前已运行过跟踪,现在再次运行同一跟踪,请指向现有的跟踪表;如果这是第一次将跟踪活动捕获到表中,您也可以提供新的表名。单击确定。

  单击运行。

  运行工作负荷若干 (3-4) 次

  从 SQL Server 企业管理器或开始菜单启动 SQL 查询分析器。

  连接到 SQL Server,然后将当前数据库设为在其中创建测试表的数据库。

  在 SQL 查询分析器的查询窗口中输入下列查询:

select ckey1 from testtable where ckey1 = 'a'
select nkey1 from testtable where nkey1 = 5000
select ckey1,col2 from testtable where ckey1 = 'a'
select nkey1,col2 from testtable where nkey1 = 5000

  按 CTRL+E 键执行查询。反复执行该步骤三至四次,生成工作负荷样本。

  停止 SQL 事件探查器

  在“SQL 事件探查器”窗口中,单击红色方块,停止该事件探查器跟踪。

  将跟踪文件或表装载到索引优化向导中

  在 SQL 事件探查器中,选择工具菜单上的索引优化向导,以启动该向导。单击下一步。

  选择要分析的数据库。单击下一步。

  选择是否要保留现有索引的选项,或者添加索引视图。

  选择一种优化模式(快速、适中或彻底)。在“快速”优化模式下,索引优化向导执行分析所需的时间较少,但分析不够彻底,在“彻底”模式下生成的分析最彻底,但所需的分析时间最长。

  要查找用 SQL 事件探查器创建的跟踪文件/表,请选择我的工作负荷文件或 SQL Server 跟踪表。单击下一步。

  在选择要优化的表对话框中,选择要分析的表,然后单击下一步。

  索引优化向导将分析跟踪的工作负荷和表结构,然后在索引建议对话框中确定应创建的正确索引。单击下一步。

  该向导提供了几个选项:立即创建索引、安排索引创建的时间(之后会自动执行的任务),或者创建包含用于创建索引的命令的 Transact-SQL 脚本。选择首选项,然后单击下一步。

  单击完成。

  索引优化向导针对样本数据库和工作负荷生成的 Transact-SQL

/* Created by: Index Tuning Wizard */
/* Date: 9/6/2000 */
/* Time: 4:44:34 PM */
/* Server Name: JHMILLER-AS2 */
/* Database Name: TraceDB */
/* Workload File Name: C:Documents and SettingsjhmillerMy Documentstrace.trc */
USE [TraceDB]
go
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET NUMERIC_ROUNDABORT OFF
go
DECLARE @bErrors as bit
BEGIN TRANSACTION
SET @bErrors = 0
CREATE CLUSTERED INDEX [testtable1] ON [dbo].[testtable] ([ckey1] ASC )
IF( @@error <> 0 ) SET @bErrors = 1
CREATE NONCLUSTERED INDEX [testtable2] ON [dbo].[testtable] ([nkey1] ASC )
IF( @@error <> 0 ) SET @bErrors = 1
IF( @bErrors = 0 )
COMMIT TRANSACTION
ELSE
ROLLBACK TRANSACTION

  索引优化向导为样本表和数据建议的索引正是我们所需要的:在 ckey1 上创建聚集索引,在 nkey1 上创建非聚集索引。ckey1 只有五个唯一值,每个值有 4000 行。假定其中一个样本查询 (select ckey1, col2 from testtable where ckey1 = 'a') 需要根据 ckey1 中的某个值来检索表,则适合在 ckey1 列上创建聚集索引。第二个查询 (select nkey1, col2 from testtable where nkey1 = 5000) 根据 nkey1 列的值提取一行。因为 nkey1 是唯一的,并且有 20,000 行,所以适合在该列上创建非聚集索引。

  在使用了很多表并且要处理很多查询的实际数据库服务器环境中,将 SQL 事件探查器和索引优化向导组合使用,功能会非常强大。在数据库服务器处理典型的一组查询时,使用 SQL 事件探查器记录 .trc 文件或跟踪表。随后,将跟踪装载到索引优化向导中,以确定要建立的正确索引。按照索引优化向导中的提示执行操作,以自动生成索引,或安排索引创建作业在非高峰时间运行。您可能希望定期运行 SQL 事件探查器和索引优化向导的组合(也许每周一次或每月一次),以查看目前在数据库服务器上执行的查询是否发生了重大变化,这样就有可能会需要不同的索引。定期组合使用 SQL 事件探查器和索引优化向导,有助于数据库管理员在查询工作负荷不断变化和数据库日渐增大的情况下,仍保持 SQL Server 处于最佳运行状态。

  使用 SQL 查询分析器分析事件探查器中记录的信息

  在信息记录到 SQL Server 表中之后,可以使用 SQL 查询分析器来确定系统中哪些查询消耗资源最多。这样,数据库管理员就能集中精力改进那些最需要帮助的查询。如果将跟踪数据存储在表中,您就能方便地对跟踪数据的子集进行选择和筛选,从而为优化性能标识出性能最差的查询。例如,在上面的示例中,Duration 列是您使用 SQLProfiler Tuning 模板自动捕获的列,它可以用来标识需要最长执行时间(以毫秒计)的查询。要查找前 10% 的运行时间最长的查询,您可以运行下面这样的查询:

SELECT TOP 10 PERCENT *
FROM [TraceDB].[dbo].[Trace]
ORDER BY Duration DESC

  要查找运行时间最长的前五个查询,您可以运行类似下面的查询:

SELECT TOP 5 *
FROM [TraceDB].[dbo].[Trace]
ORDER BY Duration DESC

  要只将希望用于优化的行放在单独的表中,请考虑使用下面的 SELECT/INTO 语句:

SELECT TOP 10 PERCENT *
INTO TuningTable
FROM [TraceDB].[dbo].[Trace]
ORDER BY Duration DESC

  前面提到的 SQLProfiler Tuning 模板只是针对优化建议的一组预选列和筛选器设置。您可能会发现,您需要捕获更多的信息。当然,您完全可以创建自己的自定义优化模板,方法是:只需打开预先提供的一个模板,然后用不同的名称保存即可。许多事件都可被捕获,包括 I/O 统计信息、锁定信息,等等。

  SQL 查询分析器

  SQL 查询分析器用于优化查询。该工具提供了多种类似“统计信息 I/O”的机制和用来解决查询问题的执行计划。

  统计信息 I/O

  SQL 查询分析器提供了一个选项,利用该选项,您能够获得在 SQL 查询分析器中执行的查询在 I/O 消耗方面的相关信息。要设置该选项,请在 SQL 查询分析器的查询菜单上,选择当前连接属性,以显示当前连接属性对话框。选中设置 statistics I/O 复选框,然后关闭该对话框。然后,执行查询并在结果窗格中选择消息选项卡,查看 I/O 统计信息。

  例如,在选中设置 statistics IO 选项时,对前面的“SQL 事件探查器”一节中创建的样本数据进行以下查询,将在消息选项卡上返回以下 I/O 信息:

select ckey1, col2 from testtable where ckey1 = 'a'
Table 'testtable'.Scan count 1, logical reads 800, physical reads 62, read-ahead reads
760.

  使用统计信息 I/O 是监视查询优化效果的一种好方法。例如,创建索引优化向导为样本数据建议的索引,然后再次运行该查询。

select ckey1, col2 from testtable where ckey1 = 'a'
Table 'testtable'.Scan count 1, logical reads 164, physical reads 4, read-ahead reads
162.

  请注意,在可以使用索引时,逻辑读取和物理读取的数量会明显降低。

  执行计划

  使用图形化执行计划可以显示有关查询优化程序所做操作的详细信息,从而让您着重关注有问题的 SQL 查询。

  查询的预计执行计划可以显示在 SQL 查询分析器的“结果”窗格中,方法是:用 CTRL+L 键执行 SQL 查询,或在查询菜单上选择显示预计的执行计划。各图标表明了查询优化程序如果执行了查询后会执行哪些操作。各箭头表示查询的数据流方向。将鼠标指针悬停于操作图标上方,即可显示有关每个操作的详细信息。各个操作图标下方还注明了每个操作步骤的大致成本。通过此标签,您可以迅速判断出查询中哪项操作是最昂贵的。

  您也可以查看查询的实际执行计划,方法是在查询菜单上选择显示执行计划,然后执行查询。与显示预计的执行计划选项相比,显示执行计划先执行查询,然后才显示用于该查询的实际执行计划。

  您可以创建执行计划的文本版本,方法是在查询菜单上选择当前连接属性,然后在该对话框中选中设置 showplan_text 选项。执行查询时,执行计划将在结果选项卡中显示为文本。

  您还可以在查询内设置执行计划选项,方法是执行以下任一命令:

set showplan_all on
go
set showplan_text on
go

  SET SHOWPLAN_ALL 供读取其输出的应用程序使用。使用 SET SHOWPLAN_TEXT 返回 Microsoft MS-DOS® 应用程序(如 osql 实用工具)可读取的输出。

  SET SHOWPLAN_TEXT 和 SET SHOWPLAN_ALL 以一组文本行的形式返回信息,这些文本行所形成的分层树表示 SQL Server 查询处理器在执行每个语句时所采取的步骤。输出中反映的每个语句包含一个语句文本行,后面紧接若干行分别描述执行步骤的详细信息。

  执行计划输出示例

  这些结果是使用前面定义的查询示例和在 SQL 查询分析器中执行的“set showplan_text on”得出的。

  查询 1

  select ckey1,col2 from testtable where ckey1 = 'a'

  基于文本的执行计划输出

  |--Clustered Index Seek (OBJECT:([TraceDB].[dbo].[testtable].[testtable1]),

  SEEK:([testtable].[ckey1]='a') ORDERED FORWARD)

  同等的图形化执行计划输出

  下图显示查询 1 的图形化执行计划。

数据仓库的 RDBMS 性能优化指南(2)

  如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。

  执行计划利用 ckey1 列上的聚集索引来解析查询,正如聚集索引查找所示。

  如果从表中删除了聚集索引,并且再次执行相同的查询,查询将恢复使用表扫描。下面的图形化执行计划表明该行为变化。

  基于文本的执行计划输出

  |--Table Scan(OBJECT:([TraceDB].[dbo].[testtable]), WHERE:([testtable].[ckey1]=[@1]))

  同等的图形化执行计划输出

  下图显示查询 1 的图形化执行计划。

数据仓库的 RDBMS 性能优化指南(2)

  如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。

  该执行计划使用表扫描来解析查询 1。要从小表检索信息,最有效的方法是使用表扫描。但在大表上,由执行计划指明的表扫描实际是一种警告,它说明表需要最好的索引,或者现有索引的统计信息需要更新。您可以使用 UPDATE STATISTICS 命令在表或索引上更新统计信息。如果启发式页与基础索引值的同步差异过大,SQL Server 将自动更新索引。例如,如果您从 testtable 中删除了所有包含 ckey1 值等于“b”的行,然后,没有先更新统计信息就运行查询。最好让 SQL Server 自动维护索引统计信息,因为它有助于确保查询始终能够使用完好的索引统计信息。如果使用 ALTER DATABASE 语句将 AUTO_UPDATE_STATISTICS 数据库选项设为 OFF,则 SQL Server 不会自动更新统计信息。

  查询 2

  select nkey1,col2 from testtable where nkey1 = 5000

  基于文本的执行计划输出

--Bookmark Lookup(BOOKMARK:([Bmk1000]),
OBJECT:([TraceDB].[dbo].[testtable]))
|--Index Seek(OBJECT:([TraceDB].[dbo].[testtable].[testtable2]),
SEEK:([testtable].[nkey1]=Convert([@1])) ORDERED FORWARD)

  同等的图形化执行计划输出

  下面两图显示查询 2 的图形化执行计划。

数据仓库的 RDBMS 性能优化指南(2)

  如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。

数据仓库的 RDBMS 性能优化指南(2)

  如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。

  查询 2 的执行计划在 nkey1 列上使用非聚集索引。这是由 nkey1 列上的 Index Seek 操作指明的。Bookmark Lookup 操作指明 SQL Server 需要执行指针跳转,从表的索引页跳转到数据页,以检索请求的数据。需要执行指针跳转的原因是查询要求查找 col2 列,而非聚集索引内不包含该列。

  查询 3

  select nkey1 from testtable where nkey1 = 5000

  基于文本的执行计划输出

  |--Index Seek(OBJECT:([TraceDB].[dbo].[testtable].[testtable2]),

  SEEK:([testtable].[nkey1]=Convert([@1])) ORDERED FORWARD)

  同等的图形化执行计划输出

  下图显示查询 3 的图形化执行计划。

数据仓库的 RDBMS 性能优化指南(2)

  如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。

  查询 3 的执行计划使用 nkey1 上的非聚集索引作为覆盖索引。请注意,该查询不需要执行 Bookmark Lookup 操作。原因是该查询(SELECT 和 WHERE 子句)所需的全部信息都由非聚集索引提供。这就是说,非聚集索引页中不需要有指向数据页的指针跳转。与需要书签查找的情况相比,I/O 有所减少。

  系统监视

  系统监视器提供大量的有关数据库服务器执行期间所发生的 Windows 和 SQL Server 操作的信息。

  在系统监视器的图形模式下,请注意最大和最小值。因为过大和过小的数据点都会使平均值失真,所以对过于强调平均值的情况一定要小心。研究图形的形状并与最小和最大值比较,以便准确地理解行为。使用 BACKSPACE 键,用一条白线突出显示计数器。

  您可以使用系统监视器在日志文件中记录所有可用的 Windows 和 SQL Server 系统监视器对象/计数器,而同时以交互方式查看系统监视器(图表模式)。采样间隔的设置决定了日志文件增大的速度。日志文件可能很快变大(例如,如果打开所有计数器,采样间隔设为 15 秒,日志文件在 1 小时内就能达到 100 兆字节)。测试服务器上最好有足够的空闲千兆字节来存储这些类型的文件。不过,如果保留空间对您很重要,请尝试采用较长的日志间隔,以免系统监视器过于频繁地对系统采样。请尝试 30 或 60 秒。这样,系统监视器会以合理的频率对所有计数器重新采样,同时又能保持较小的日志文件大小。

  系统监视器也会耗用少量 CPU 资源和磁盘 I/O 资源。如果系统没有多余的备用磁盘 I/O 和/或 CPU,请考虑从另一台计算机运行系统监视器,然后通过网络监视 SQL Server。在通过网络监视时,请只使用图形模式。与通过局域网发送信息相比,在 SQL Server 本地记录性能监视信息的效率往往会更高。如果您必须通过网络记录日志信息,可以只记录最重要的计数器信息。

  在性能测试运行期间,将所有可用计数器的信息记录到某个文件中供以后分析,这不失为一个好做法。这样,对于任何计数器,以后都可以再作进一步检查。您可以配置系统监视器将所有计数器记录到日志文件中,与此同时,在其他某种模式(如图形模式)下监视最感兴趣的计数器。这样,在性能运行期间,所有信息都会被记录下来,但您最关注的计数器会以清晰整洁的系统监视器图形显示出来。

  设置要记录的系统监视器会话

  从 Windows 2000 开始菜单中,指向程序、管理工具,然后单击性能,打开系统监视器。

  双击性能日志和警报,然后单击计数器日志。

  现有的日志都会在详细信息窗格中列出。绿色图标表示日志正在运行;红色图标表示日志已被停止。

  右键单击详细信息窗格的空白区域,然后单击新日志设置。

  在名称中键入日志的名称,然后单击确定。

  在常规选项卡上,单击添加。选择要记录的计数器。您在此处确定要在会话期间监视的 SQL Server 计数器。

  如果要更改默认文件,请在日志文件选项卡上进行更改。

  记录的会话可以设置为按预定义的时间段自动运行。为此,请在调度选项卡上修改调度信息。

  注意 要保存日志文件的计数器设置,请用右键单击详细信息窗格中的文件,然后单击将设置另存为。然后,指定用来保存这些设置的 .htm 文件。要在新日志中重用已保存的设置,请用右键单击详细信息窗格,然后单击新日志设置来自。

  启动已记录的监视会话

  从 Windows 2000 开始菜单中,指向程序、管理工具,然后选择性能,打开系统监视器。

  双击性能日志和警报,然后单击计数器日志。

  右键单击要运行的计数器日志,然后选择启动。

  现有的日志都会在详细信息窗格中列出。绿色图标表示日志正在运行;红色图标表示日志已被停止。

  停止已记录的监视会话

  从 Windows 2000 开始菜单中,指向程序、管理工具,然后选择性能,打开系统监视器。

  双击性能日志和警报,然后单击计数器日志。

  右键单击要运行的计数器日志,然后选择停止。

  从已记录的监视会话向系统监视器装载数据供分析使用

  从 Windows 2000 开始菜单中,指向程序、管理工具,然后选择性能,打开系统监视器。

  单击系统监视器。

  右键单击系统监视器的详细信息窗格,然后单击属性。

  单击源选项卡。

  在数据源下,单击日志文件,然后键入文件路径,或单击浏览,查找所需的日志文件。

  单击时间区间。要在日志文件中指定希望查看的时间区间,请拖动滑动条或滑动条柄,设置相应的开始和结束时间。

  单击数据选项卡,然后单击添加,打开添加计数器对话框。您在日志配置期间选择的计数器会显示出来。您可以在图形中包括所有这些计数器或其中一部分。

  如何使系统监视器记录的事件与过去的某个时点相关

  从系统监视器会话中,右键单击系统监视器的详细信息窗格,然后单击属性。时间区间和滑动条允许您设定要在图形中查看的开始、当前和结束时间。

  需要监视的关键性能计数器

  有几个性能计数器提供了有关以下重要方面的信息:内存、分页、处理器、I/O 和磁盘活动。

  监视内存

  默认情况下,SQL Server 会根据可用系统资源动态更改其内存需求。如果 SQL Server 需要更多内存,它会查询操作系统,以确定是否有可用的空闲物理内存,并使用可用内存。如果 SQL Server 当前不需要分配给它的内存,它将向操作系统释放内存。不过,动态使用内存的选项会被服务器配置选项替代,这些选项是最小服务器内存、最大服务器内存和设置工作集大小。有关更多信息,请参阅“SQL Server 联机丛书”。

  要监视由 SQL Server 使用的内存量,请检查下列性能计数器:

  进程:工作集

  SQL Server:缓冲管理器:缓存命中率

  SQL Server:缓冲管理器:全部页

  SQL Server:内存管理器:总的服务器内存 (KB)

  工作集计数器显示由进程使用的内存量。如果该数字一直低于 SQL Server 配置使用的内存量(由服务器选项最小服务器内存和最大服务器内存设置),说明为 SQL Server 配置的内存比它实际需要的内存多。否则,使用设置工作集大小服务器选项调整工作集的大小。

  缓存命中率计数器是特定于应用程序的;不过,该比率达到或超过 90% 比较理想。请增多内存,直到该值稳定地达到 90% 以上,这样就表明数据缓存满足了 90% 以上的数据请求。

  如果与计算机中的物理内存量相比,总的服务器内存 (KB) 计数器值一直较高,说明需要更多内存。

  强制分页

  如果内存:页/秒大于零或内存:页读取/秒大于五,说明 Windows 正在使用磁盘来解决内存引用(强制分页错误)。它耗用磁盘 I/O + CPU 资源。内存:页/秒清楚地指明了 Windows 正在执行的分页量,以及数据库服务器当前的 RAM 配置是否够用。系统监视器中的强制分页信息有一个子集记录的是 Windows 为了解决内存引用而必须读取分页文件的次数/秒,它由内存:页读取/秒表示。如果内存:页读取/秒大于 5,则对性能不利。

  为了避免分页,SQL Server 自动内存优化将尝试动态调整 SQL Server 对内存的使用。每秒读取少量页是正常的,但如果分页过多,则需采取更正措施。

  如果 SQL Server 自动优化内存,您可以选择添加更多 RAM 或从数据库服务器中删除其他应用程序,以帮助内存:页/秒达到合理水平。

  如果 SQL Server 内存是在数据库服务器上手动配置的,则需要减少指定给 SQL Server 的内存,从数据库服务器中删除其他应用程序,或向数据库服务器添加更多 RAM。

  保持内存:页/秒为零或接近零,对数据库服务器性能有利。这就是说,Windows 及其所有应用程序(包括 SQL Server)不会为满足内存请求中的任何数据而转到分页文件,所以服务器上的 RAM 是充足的。页/秒略大于零尚可接受,但请记住,每次从分页文件(而非 RAM)检索数据时,都将遭受相对较高的性能惩罚(磁盘 I/O)。

  对与 Windows 分页文件相关的所有驱动器间的内存:页输入/秒与逻辑磁盘:磁盘读取/秒以及内存:页输出/秒与逻辑磁盘:磁盘写入/秒进行比较是很有用的,因为通过它们可以知道真正与分页而非其他应用程序(即 SQL Server)相关的磁盘 I/O 量。隔离分页文件 I/O 活动的另一种便捷方式是确保分页文件与所有其他 SQL Server 文件不在同一组驱动器上。将分页文件与 SQL Server 文件隔开也对磁盘 I/O 性能有利,因为它允许与分页相关的磁盘 I/O 和与 SQL Server 相关的磁盘 I/O 并行执行。

  软分页

  如果内存:分页错误/秒大于零,说明 Windows 正在分页,但计数器中既有强制分页,也有软分页。我们已在上一节讨论过强制分页。软分页表示数据库服务器上的应用程序正在请求的内存页仍然位于 RAM 以内,但已位于 Windows 工作集之外。内存:分页错误/秒有助于获得正在发生的软分页量。系统中没有称为“软分页错误/秒”的计数器。您可以使用此公式计算每秒钟发生的软分页错误数:内存:分页错误/秒 - 内存:页输入/秒 = 软分页错误/秒

  要确定导致过多分页的是否是 SQL Server 而不是其他进程,请监视 SQL Server 进程的进程:分页错误/秒计数器,并注意相关 Sqlservr.exe 实例的分页错误数/秒是否接近内存:页/秒的数值。

  与硬分页错误相比,软分页错误对性能的不利影响通常要小一些,因为它们耗用 CPU 资源。硬分页错误耗用磁盘 I/O 资源。获得良好性能的最佳环境是杜绝任何类型的错误。

  注意 在 SQL Server 第一次访问它所有的数据缓存页时,对每一页的第一次访问均会导致软分页错误。在 SQL Server 第一次启动和第一次使用数据缓存时,不必担心最初的软分页错误。

  监视处理器

  您的目标应该是:尽可能充分地利用所有分配给服务器的处理器,以获得最佳性能,而同时又避免因过于繁忙而出现处理器瓶颈。性能优化所面临的挑战是:如果 CPU 不是瓶颈,总有其他东西是瓶颈(很可能是磁盘子系统),因而浪费了 CPU 容量。通常,CPU 是最难扩展的资源(某些配置特定的级别除外,例如,许多最新系统上的 4 CPU 或 8 CPU),因此,如果繁忙系统上的 CPU 使用率超过 95%,说明系统运行良好。同时,您应该监视事务的响应时间,确保响应时间合理;如果响应时间不合理,而 CPU 使用率超过 95%,则可能说明可用 CPU 资源承担的工作负荷过多,您要么增加 CPU 资源,要么减少或优化工作负荷。

  请查看系统监视器计数器处理器:% 处理器时间,确保每个 CPU 上的处理器使用率一直低于 95%。系统:处理器队列长度是 Windows 系统上所有 CPU 的处理器队列。如果每个 CPU 的系统:处理器队列长度大于二,则说明出现了 CPU 瓶颈。在检测到 CPU 瓶颈时,您需要向服务器添加处理器,或减少系统上的工作负荷。减少工作负荷的方法是:通过优化查询或改进索引来减少 I/O,从而减少 CPU 使用率。

  在怀疑出现 CPU 瓶颈时需要监视的另一个系统监视器计数器是系统:上下文切换/秒,因为它指明了 Windows 和 SQL Server 必须从在一个线程上执行切换到在另一个线程上拖动的频率(次数/秒)。它耗用 CPU 资源。上下文切换是多线程、多处理器环境的正常组件,但过多的上下文切换会降低系统性能。应对方法是在有处理器队列时,只关注上下文切换。

  如果观察处理器队列,则将上下文切换级别用作 SQL Server 性能优化的尺度。如果看起来是上下文切换导致出现瓶颈,您可以考虑两种方法:使用关系掩码选项,使用基于纤程的调度。

  使用关系掩码选项可以提高重负荷下运行的对称多处理器 (SMP) 系统(微处理器数量超过四个)的性能。您可以使线程与特定处理器相关,并指定 SQL Server 将使用的处理器。您还可以使用关系掩码选项设置来阻止 SQL Server 活动使用某些处理器。在更改关系掩码的设置之前,请记住,Windows 会将与 NIC 的相关延迟进程调用 (DPC) 活动分配给系统中编号最高的处理器。在安装并激活了多个 NIC 的系统中,另外每增加一个卡的活动,就会分配给下一个编号最高的处理器。例如,安装了两个 NIC 的八处理器系统将每个 NIC 的 DPC 分配给处理器 7 和处理器 6(从 0 开始计数)。在使用轻量池选项时,SQL Server 切换到基于纤程的调度模式,而不是默认的基于线程的调度模式。纤程本质上是轻量线程。使用命令 sp_configure 'lightweight pooling',1 可启用基于纤程的调度。

  通过监视处理器队列和上下文切换,您可以监视设置关系掩码和轻量池的值之后产生的效果。某些情况下,这些设置非但不会改进性能,反而会使性能下降。另外,除非系统中有四个或更多个处理器,否则它们一般不会带来很大的收益。DBCC SQLPERF (THREADS) 提供了映射回 SPID 的有关 I/O、内存以及 CPU 使用率的更多信息。执行下面的 SQL 查询可调查当前最耗用 CPU 时间的使用者:

  select * from master.sysprocesses order by cpu desc

  监视处理器队列长度

  如果系统:处理器队列长度大于二,说明服务器的处理器收到的工作请求多于它们能够以一个组的方式集体处理的请求。因此,Windows 需要将这些请求放在队列中。

  某些处理器队列说明 SQL Server 的总体 I/O 性能良好。如果没有处理器队列,并且 CPU 使用率低,说明系统某处可能出现了性能瓶颈,最有可能的地方就是磁盘子系统。处理器队列中留有合理的工作请求量,这说明 CPU 并不空闲,系统的其余部分也与 CPU 保持同步。

  根据一般经验,理想的处理器队列数是数据库服务器中的 CPU 数乘以二。

  如果处理器队列数明显高于该值,可能表明服务器遇到了 CPU 瓶颈,您需要进行调查。过多的处理器队列会耗用查询执行时间。多个不同活动可能导致出现处理器队列。消除强制分页和软分页有助于节省 CPU 资源。其他有助于减少处理器队列的方法包括:优化 SQL 查询、挑选更好的索引以减少磁盘 I/O(从而减少 CPU 使用量)、在系统中添加更多 CPU(处理器)。

  监视 I/O

  磁盘写入字节/秒和磁盘读取字节/秒计数器表明磁盘的数据吞吐量,以每个逻辑驱动器或物理驱动器每秒的字节数计。请仔细地将这些数字与磁盘读取/秒和磁盘写入/秒均衡比较。不要看到较低的字节数/秒,就相信磁盘 I/O 子系统不忙。

  监视与 SQL Server 文件相关的所有驱动器的磁盘队列长度,然后确定哪些文件与过多的磁盘队列相关。

  如果系统监视器表明某些驱动器不如其他驱动器繁忙,则可将 SQL Server 文件从出现瓶颈的驱动器移到不太繁忙的驱动器。这样有助于将磁盘 I/O 活动更均匀地分布到各个硬盘。如果将一个大型驱动器池用于 SQL Server 文件,磁盘队列的解决方法是在池中添加更多的物理驱动器,从而增大驱动器池的 I/O 容量。

  出现磁盘队列可能表明某个 SCSI 通道中的 I/O 请求数量已达到饱和。系统监视器无法直接确定该情况是否属实。存储器供应商通常会另外提供工具,以帮助监视 RAID 控制器所服务的 I/O 数量,以及控制器是否在对 I/O 请求进行排队。如果 SCSI 通道上连接了许多磁盘驱动器(十个或更多个),并且所有驱动器都以全速执行 I/O,则更有可能发生该情况。应对此情况的解决方案是:将一半磁盘驱动器连接到另一个 SCSI 通道或 RAID 控制器,以平衡该 I/O。通常,在 SCSI 通道之间重新平衡驱动器需要重建 RAID 阵列以及完全备份/还原 SQL Server 数据库文件。

  磁盘时间百分比

  在系统监视器中,物理磁盘:% 磁盘时间和逻辑磁盘:% 磁盘时间计数器监视磁盘因读取/写入活动而处于繁忙状态的时间百分比。如果 % 磁盘时间计数器很高(超过 90%),请检查当前磁盘队列长度计数器,以查看有多少系统请求正在等待磁盘访问。等待 I/O 的请求数量应该始终不超过构成物理磁盘的轴数量的 1.5 至 2 倍。大多数磁盘只有一个轴,然而不昂贵的磁盘冗余阵列 (RAID) 设备通常有多个轴。硬件 RAID 设备在系统监视器中显示为一个物理磁盘;通过软件创建的 RAID 设备显示为多个实例。

  磁盘队列长度

  监视过长的磁盘队列是一项重要任务。

  要监视磁盘队列长度,您需要观察多个系统监视器磁盘计数器。要启用这些计数器,请从 Windows 2000 或 Windows NT 命令窗口运行 diskperf –y 命令,然后重新启动计算机。

  出现磁盘队列的物理硬盘驱动器将在弥补 I/O 处理的同时阻止磁盘 I/O 请求。这些驱动器上的 SQL Server 响应时间也不如从前。此操作会耗用查询执行时间。

  如果使用 RAID,为了计算每个物理驱动器的磁盘队列,您需要了解有多少个物理硬盘驱动器与每个 Windows 视为单个物理驱动器的驱动器阵列相关。为了了解每个物理驱动器保存 SQL Server 数据的具体方式,以及每个 SCSI 通道上分发的 SQL Server 数据量,请向硬件专家咨询,让他们来解释 SCSI 通道和物理驱动器分发。

  通过系统监视器查看磁盘队列的选择有多种。逻辑磁盘计数器与通过磁盘管理器分配的逻辑驱动器盘符相关,而物理磁盘计数器与磁盘管理器视为一个物理磁盘设备的内容相关。请注意,磁盘管理器视为一个物理设备的驱动器可能是一个硬盘驱动器,也可能是一个包含多个硬盘驱动器的 RAID 阵列。当前磁盘队列长度是对磁盘队列的即时度量,而平均磁盘队列长度是采样期间磁盘队列度量的平均值。如果指明出现以下任一情况,请加以注意:

  逻辑磁盘:平均磁盘队列长度 > 2

  物理磁盘:平均磁盘队列长度 > 2

  逻辑磁盘:当前磁盘队列长度 > 2

  物理磁盘:当前磁盘队列长度 > 2

  这些建议的度量适于每个物理硬盘驱动器。如果 RAID 阵列与磁盘队列度量相关,则需要用该度量除以 RAID 阵列中的物理硬盘驱动器的数量,以确定每个物理硬盘驱动器的磁盘队列。

  注意 在保存 SQL Server 日志文件的物理硬盘驱动器或 RAID 阵列上,磁盘队列不是有用的度量方法,因为日志管理器不会对多个针对 SQL Server 日志文件的 I/O 请求进行排队。

  了解 SQL Server 技术内幕

  了解 SQL Server 2000 的一些技术内幕有助于您管理数据库的性能。

  工作线程

  SQL Server 维护着一个 Windows 线程池,这些线程的作用是为成批提交到数据库服务器的 SQL Server 命令提供服务。sp_configure 选项最大工作线程的设置规定了可以为所有传入的命令批提供服务的线程(在 SQL Server 术语中称为工作线程)的总数。如果主动提交命令批的连接数大于指定的最大工作线程数,将在主动提交命令批的连接之间共享工作线程。许多安装都适合使用默认值 255。请注意,大部分连接大多数的时间都在等待从客户端接收命令批。

  从 SQL Server 缓冲区缓存中写出 8 KB 脏页的任务主要由工作线程来完成。为了获得最佳性能,工作线程会异步调度它们的 I/O 操作。

  惰性写入器

  惰性写入器是在缓冲管理器内运行的 SQL Server 系统进程。惰性写入器刷新脏的旧缓冲(必须先将这些缓冲内所含的更改写入磁盘,随后才能将缓冲重新用于其他不同的页)批,然后将它们提供给用户进程。该活动有助于生成和维护可用的空闲缓冲,它们是大小为 8 KB,不含任何数据,可以重新使用的数据缓存页。在惰性写入器将每个 8 KB 缓存缓冲区刷新到磁盘上时,缓存页的标识会被初始化,这样,其他数据就可以写入空闲的缓冲区。惰性写入器在磁盘 I/O 量少时工作,从而将该活动对其他 SQL Server 操作的影响减到最小。

  SQL Server 自动配置和管理空闲缓冲水平。性能计数器 SQL Server:缓冲管理器:惰性写入/秒指明了物理写出到磁盘的 8 KB 页的数量。请监视 SQL Server:缓冲管理器:可用页,查看该值是否下降。最佳状态是:惰性写入器使该计数器在所有 SQL Server 操作之间保持水平,这意味着惰性写入器与用户对空闲缓冲的需求保持同步。如果系统监视器对象 SQL Server:缓冲管理器:可用页的值达到零,说明用户负载有时需要较高水平的空闲缓冲,而惰性写入器无法提供这一水平的空闲缓冲。

  如果惰性写入器难以使空闲缓冲保持稳定或至少保持在零以上,说明磁盘子系统可能无法提供足够的磁盘 I/O 性能。要证明是否确实如此,请将空闲缓冲水平的下降与磁盘队列作比较。解决办法是向数据库服务器磁盘子系统添加更多物理磁盘驱动器,以提高磁盘 I/O 处理能力。

  在系统监视器中监视当前的磁盘队列水平,方法是查看逻辑磁盘或物理磁盘的性能计数器平均磁盘队列长度或当前磁盘队列长度,确保与任何 SQL Server 活动相关的每个物理驱动器的磁盘队列小于 2。对于使用硬件 RAID 控制器和磁盘阵列的数据库服务器,记住用“逻辑/物理磁盘”计数器报告的数字除以与该逻辑驱动器盘符或物理硬盘驱动器盘符(依据磁盘管理器的报告)相关的实际硬盘数量,因为 Windows 和 SQL Server 不知道与 RAID 控制器相连的物理硬盘驱动器的实际数量。为了正确地解释系统监视器报告的磁盘队列数量,一定要知道与 RAID 阵列控制器相关的驱动器数量。

  有关更多信息,请参阅“SQL Server 联机丛书”。

  检查点

  SQL Server 的每个实例需要定期确保将所有脏日志和数据页刷新到磁盘。这称为检查点。在重新启动 SQL Server 的实例时,使用检查点可以减少从故障中恢复所需的时间和资源。在检查点期间,脏页(进入缓冲区缓存后已经过修改的缓冲区缓存页)会被写入 SQL Server 数据文件。在检查点处写入磁盘的缓冲仍然包含数据页,用户可以读取或更新该页,而不必从磁盘重新读取,这一点与惰性写入器创建的空闲缓冲不同。

  检查点逻辑尝试让工作线程和惰性写入器负责大部分的脏页写出工作。为此,如有可能,检查点逻辑在写出脏页之前,尝试额外多等待一个检查点。这样,工作线程和惰性写入器就有更多的时间来写出脏页。某些情况下,检查点逻辑在写出脏页之前需要额外多等一段时间,有关这些情况的详细信息,请参阅“SQL Server 联机丛书”中的主题“检查点和日志的活动部分”。要请住的重点是,检查点逻辑会尝试通过等待额外的检查点在更长的时间段内均衡 SQL Server 磁盘 I/O 活动。

  在有大量的数据页需要从缓存刷新到磁盘上时,为了使检查点操作更有效,SQL Server 将要刷新的数据页按照它们在磁盘上出现的顺序进行排序。这有助于尽量减少磁盘在缓存刷新过程中的来回移动,并在可能的情况下使用连续磁盘 I/O。检查点进程也向磁盘子系统异步提交 8 KB 磁盘 I/O 请求。这样,SQL Server 就能更快地完成对所需磁盘 I/O 请求的提交,因为检查点进程不必等待磁盘子系统发回指明已将数据实际写入磁盘的报告。

  重要的一点是要监视与 SQL Server 数据文件相关的硬盘驱动器上的磁盘队列,确定 SQL Server 目前发送的 I/O 请求是否超过磁盘的实际处理能力;如果情况属实,必须提高磁盘子系统的磁盘 I/O 能力,使它能够处理负载。

  日志管理器

  像所有其他主流 RDBMS 产品一样,SQL Server 也可以确保在发生中断 SQL Server 联机状态的事件(例如,断电、磁盘驱动器有故障、数据中心起火,等等)时,数据库上执行的所有写入活动(插入、更新和删除)不会丢失。SQL Server 日志记录进程有助于确保可恢复性。在完成任何隐式(单个 SQL 查询)或显式事务(所定义的发出 BEGIN TRAN/COMMIT 或 ROLLBACK 命令序列的事务)之前,日志管理器必须从磁盘子系统收到信号,表明与该事务相关的所有数据更改均已成功写入相关的日志文件。这一规则可以确保:如果 SQL Server 因某种原因而突然关机,而检查点和惰性写入器尚未将写入数据缓存的事务刷新到数据文件,那么,SQL Server 可以在启动后读取和重新应用事务日志。恢复是指服务器停机之后读取事务日志以及向 SQL Server 数据应用事务。

  由于在每个事务完成时,SQL Server 必须等待磁盘子系统完成对 SQL Server 日志文件的 I/O,所以包含 SQL Server 日志文件的磁盘要有足够的磁盘 I/O 处理能力来承受预期的事务负载,这一点很重要。

  SQL Server 日志文件的相关磁盘队列的监视方法与 SQL Server 数据库文件的相关磁盘队列的监视方法不同。请使用系统监视器计数器 SQL Server:数据库 <数据库实例>:日志刷新等待时间和 SQL Server:数据库 <数据库实例>:日志刷新等待/秒来查看磁盘子系统上是否有处于等待完成状态的日志写入器请求。

  具备缓存功能的控制器性能最高,但除非该控制器能够确保将它负责的数据最终写入磁盘,甚至在电源故障时也能最终写入磁盘,否则不得将它用于包含日志文件的磁盘。有关具备缓存功能的控制器的更多信息,请参阅本章的“硬件 RAID 控制器板载缓存的效果”一节。

  预读管理

  SQL Server 2000 为大规模连续读取表扫描等活动提供了自动管理功能。预读管理完全自行配置和自行优化,并且与

  SQL Server 查询处理器的操作紧密地结合在一起。预读管理用于大表扫描、大索引区间扫描、探测聚集索引和非聚集索引二进制树,以及其他情况。原因是预读采用的是 64 KB I/O,与 8 KB I/O 相比,64 KB I/O 能使磁盘子系统达到更大的磁盘吞吐量。如果需要检索大量数据,SQL Server 就使用预读来获得最大的吞吐量。

  SQL Server 使用简单有效的索引分配映射表 (IAM) 存储结构,该结构支持预读管理。IAM 是 SQL Server 用于记录扩展盘区位置的机制,即每 64 KB 扩展盘区包含八页数据或索引信息。每个 IAM 页是包含紧密打包(位映射)信息的 8 KB 页,这些信息指明哪些扩展盘区包含所需的数据。IAM 页的压缩特性加快了它们的读取速度,经常使用的 IAM 页还可以保留在缓冲区缓存中。

  预读管理可以将来自查询处理器的查询信息与需要从 IAM 页读取的所有扩展分区的位置信息组合在一起,从而构成多个连续的读取请求。连续的 64 KB 磁盘读取提供优异的磁盘 I/O 性能。SQL Server:缓冲管理器:预读页/秒性能计数器提供有关预读管理的有效性及效率的信息。

  SQL Server 2000 企业版根据现有内存量动态调整预读页的最大数量。在 SQL Server 2000 的所有其他版本中,该值固定不变。SQL Server 2000 企业版的另一改进之处是通常所说的“旋转木马式扫描”,它允许多个任务共享整个表扫描。如果 SQL 语句的执行计划要求扫描表中的数据页,并且关系数据库引擎检测到已经为另一个执行计划扫描过该表,那么数据库引擎在第二次扫描的当前位置将第二次扫描加入到第一次扫描中。数据库引擎每次读取一页,并将每一页的所有行同时传递到这两个执行计划。此操作会一直进行,直至到达表的结尾。此时,第一个执行计划具有完整的扫描结果,但第二个执行计划仍必须检索在它加入正在进行的扫描之前所发生的数据页。然后,为第二个执行计划执行的扫描会折返回表的第一个数据页,并且向前扫描至它加入第一次扫描的位置。用这种方式可以组合任意数量的扫描;数据库引擎将一直在所有数据页之间循环,直到完成所有扫描。

  有关预读管理需要注意一点,那就是过多的预读会对总体性能不利,因为它在缓存内填入不需要的数据页,占用本应用于其他用途的 I/O 和 CPU。对于这一点,只能通过一般的性能优化来解决:优化所有 SQL 查询,尽量减少进入缓冲区缓存的页数量。它包括确保具备正确的索引并在使用这些索引。使用聚集索引可以获得有效的区间扫描,定义非聚集索引有助于快速定位单行或更小的行集。例如,如果您准备在表中只创建一个索引,并且该索引将用于提取单行或更小的行集,该索引应为聚集索引。从表面上看,聚集索引比非聚集索引的速度快。

  其他性能主题使用星型架构和雪花形架构的数据库设计

  数据仓库使用维度建模来组织数据,以便进行分析。维度建模会生成星型架构和雪花架构,这样也就为数据仓库中经常执行的大量数据读取操作带来了性能效率。大量的数据(通常成千上万行)存储在事实数据表中,表内各行都很短,这就使存储需求和查询时间减到最少。业务事实数据的属性会非正常化为维度表,以最大程度地减少检索数据时的表联接数量。

  有关数据仓库的数据库设计的讨论,请参阅第 17 章“数据仓库设计注意事项”。

  在 Transact-SQL 查询中使用等价运算符

  在 SQL 查询中使用非等价运算符将强制数据库使用表扫描来对非等价对象取值。如果经常对非常大的表运行这些查询,将会生成高 I/O。包含“NOT”运算符(!=、<>、!<、!>)的 WHERE 子句(如 WHERE <column_name> != some_value)将生成高 I/O。

  如果需要运行此类查询,请尝试更改查询的结构,从其中消除 NOT 关键字。例如:

  不使用:

  select * from tableA where col1 != "value"

  尝试使用:

  select * from tableA where col1 < "value" and col1 > "value"

  减少行集大小和通讯开销

  使用 Microsoft ActiveX® 数据对象 (ADO)、远程数据对象 (RDO) 和数据访问对象 (DAO) 数据库 API 等易用界面的 SQL 数据库程序员需要考虑他们生成的结果集。

  ADO、RDO 和 DAO 为程序员提供了极好的数据库开发界面,程序员即使没有太多的 SQL 编程经验也能实现丰富的 SQL 行集功能。如果程序员仔细考虑他们的应用程序返回到客户端的数据量,并且跟踪 SQL Server 索引的位置以及 SQL Server 数据的安排方式,就能避免性能问题。SQL 事件探查器、索引优化向导和图形化的执行计划都是非常有用的工具,它们可以帮助程序员精确定位和修复出现问题的查询。

  在使用游标逻辑时,请选择最适合您的处理类型的游标。不同类型的游标开销也不同。您应该了解所要执行的是何种类型的操作(只读、只向前处理,等等),然后选择相应的游标类型。

  寻找各种机会来减少返回的结果集的大小,方法包括在选择列表中消除不需要返回的列、只返回所需的行。这有助于减少 I/O 和 CPU 消耗。

  使用多个语句

  通过在数据库上执行处理,您可以减少结果集的大小,并避免在客户端和数据库服务器之间进行不必要的网络通讯。为了执行无法用单个 Transact-SQL 语句执行的处理,SQL Server 允许您将多个 Transact-SQL 语句以下列方式组合在一起。

分组方法说明
批处理批处理是以一个单元的形式从应用程序发送到服务器的一组 Transact-SQL 语句,其中可包含一条或多条语句。SQL Server 在执行每个批处理时将其视为单个的可执行单元。
存储过程存储过程是已在服务器上预定义和预编译的一组 Transact-SQL 语句。存储过程可以接受参数、返回结果集、返回代码,还可以将参数输出到调用应用程序。
触发器触发器是一类特殊的存储过程。它不由应用程序直接调用,而是每当用户对表执行指定修改(INSERT、UPDATE 或 DELETE)时执行。
脚本脚本是存储在文件中的一组 Transact-SQL 语句。该文件可以用作 osql 实用工具或 SQL 查询分析器的输入。然后,这些实用工具执行存储于该文件中的 Transact-SQL 语句。

  下面的 SQL Server 功能使您可以对同时使用多个 Transact-SQL 语句的情况进行控制。

功能说明
控制流语句允许您包含条件逻辑。例如,如果国家为加拿大,则执行某一组的 Transact-SQL 语句。如果国家为英国,则执行另一组的 Transact-SQL 语句。
变量允许您存储数据,在稍后的 Transact-SQL 语句中用作输入。例如,您可以编写这样一个查询:每次执行该查询时,都需要在 WHERE 子句中指定不同的数据值。您可以在编写该查询时在 WHERE 子句中使用变量,并编写相应的逻辑,以使用正确数据填充该变量。存储过程的参数是一类特殊变量。
错误处理允许您自定义 SQL Server 响应问题的方式。您可以指定在发生错误时采取的相应的操作,或显示对用户来说比一般的 SQL Server 错误信息更有用的自定义错误信息。

  重用执行计划

  如果 SQL Server 能够利用先前查询的现有执行计划,则可以提高性能。要促使 SQL Server 重用执行计划,开发人员可以做的工作有很多。Transact-SQL 语句应根据以下原则编写。

  使用对象的完全限定名,例如表和视图。

  例如,请不要这样编写 SELECT 语句:

  SELECT * FROM Shippers WHERE ShipperID = 3

  而应使用 SQLBindParameter ODBC 函数(以使用 ODBC 为例):

  SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = 3

  使用参数化查询,并提供参数值,而不要指定存储过程参数值或直接在搜索条件谓词中指定值。使用 sp_executesql 中的参数替代,或使用 ADO、OLE DB、ODBC 和 DB-Library API 的参数绑定。

  例如,请不要这样编写 SELECT 语句:

  SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = 3

  而应使用 SQLBindParameter ODBC 函数(以使用 ODBC 为例),将参数标记 (?) 绑定到程序变量,并按下面这样编写 SELECT 语句:

  SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = ?

  在 Transact-SQL 脚本、存储过程或触发器中,使用 sp_executesql 执行 SELECT 语句:

DECLARE @IntVariable INT
DECLARE @SQLString NVARCHAR(500)
DECLARE @ParmDefinition NVARCHAR(500)
/* Build the SQL string. */
SET @SQLString =
N'SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = @ShipID'
/* Specify the parameter format once. */
SET @ParmDefinition = N'@ShipID int'
/* Execute the string. */
SET @IntVariable = 3
EXECUTE sp_executesql @SQLString, @ParmDefinition,
@ShipID = @IntVariable

  如果要避免创建和维护单独的存储过程的开销,则可使用 sp_executesql。

  对多个批处理重用执行计划

  如果多个并发应用程序将用一组已知参数执行同一个批处理,请将该批处理实现为将由这些应用程序调用的存储过程。

  在 ADO、OLE DB 或 ODBC 应用程序将会多次执行同一个批处理时,请使用执行该批处理的 PREPARE/EXECUTE 模型。使用绑定到程序变量的参数标记来提供所需的全部输入值,例如,在 UPDATE VALUES 子句或搜索条件谓词中使用的表达式。

  维护列中的统计信息

  SQL Server 允许创建与某个列中值的分布有关的统计信息,即使该列不是索引的一部分也不成问题。查询处理器可以使用该统计信息来确定评估查询的最佳策略。在您创建索引时,SQL Server 会自动存储与索引列中的值的分布有关的统计信息。除索引列外,如果 AUTO_CREATE_STATISTICS 数据库选项设置为 ON(默认设置),只要在谓词中使用了某个列,即使该列不在索引中,SQL Server 也会自动创建该列的统计信息。

  随着列中数据的更改,索引和列的统计信息将会过时,从而导致查询优化程序所做的有关如何处理查询的决策也不如以前理想。随着表中数据的更改,SQL Server 会定期地自动更新此统计信息。采样是在数据页中随机进行的,而且是从统计信息所需的表中或列上最小的非聚集索引中采样。在从磁盘读取了数据页之后,该数据页上的所有行都会用来更新统计信息。更新统计信息的频率由列或索引中的数据量以及发生更改的数据量决定。

  例如,某个表中包含 10,000 行,如果其中的 1,000 个索引值发生了更改,这时就可能需要更新该表的统计信息,因为这 1,000 个值可能代表了表中很大一部分数据。但是,对于包含 1000 万个索引条目的表而言,其中 1000 个索引值发生了更改就没有太大关系,因此可能不会自动更新统计信息。不过,SQL Server 始终会确保对最小数量的行进行采样;始终会对小于 8 MB 的表通过完全扫描来收集统计信息。

  注意 使用 SQL 查询分析器以图形方式显示查询的执行计划时,将以示警的形式(表名用红色文字显示)指出统计信息过时或缺少统计信息。另外,使用 SQL 事件探查器监视缺少的列统计信息事件类,可以发现什么时候缺少统计信息。

  通过使用 sp_createstats 系统存储过程,您可以使用单个语句,在当前数据库内的所有用户表中的所有适合的列上很轻松地创建统计信息。不适于创建统计信息的列包括:不确定的或不精确的计算列,或是数据类型为 image、text 和 ntext 的列。

  如果手动创建统计信息,则您可以创建包含多个列密度(列组合重复的平均数)的统计信息。例如,某个查询包含以下子句:

  WHERE a = 7 and b = 9

  同时在两列(a 和 b)上创建手动统计信息可以使 SQL Server 更好地预估查询,因为统计信息也包含 a 和 b 列组合的非重复值的平均数。这样,SQL Server 就可以利用 col1 上建立的索引(此情况下最好为聚集索引),而不必进行表扫描。有关如何创建列统计信息的信息,请参阅“SQL Server 联机丛书”中的主题“创建统计信息”。

来源:Microsoft Tech Net    责编:豆豆技术应用

正在加载评论...