看了这几篇文章觉得很是不错(都是转载)

 

看了这几篇文章觉得很是不错,转一下,也方便自己阅读

怎样分辨SQLServer不起作用的索引

--王成辉翻译整理,转贴请注明出处

作为DBA,检查数据库看是否有任何提升性能的方法是我们的常规工作。虽然给数据库添加新的或更好的索引是我们能提升性能的主要方法,然而帮助提升数据库性能的一个经常被遗忘的方法是删除那么不再被使用的索引,或者找出花费大量SQLServer资源的过度庞大的索引。不起作用的索引会降低INSERTUPDATEDELETE的性能,增加不必要的磁盘I/O。所以为了更好的性能,我们要随时删除不必要的索引。

另外,经常分辨非常大的索引看看它们是否被正确使用是一个好的想法。一个缺乏设计又大的索引也能引起你有不起作用的索引时看到的同类问题。

问题在于,我们怎么知道哪个索引有用哪个没用呢?怎样能轻易分辨出很大的索引呢?在这一点上,SQLServer没有提供太多自动化的方法,要分辨不起作用或非常大的索引常常必须依靠我们自己。

本文提及的分辨不起作用和很大索引的方法是简单的,但要达到目的却需要时间和耐心。 

方法

我们的关键工具是事件探查器。我们必须使用它来收集至少45天的数据库活动。虽然这个时间段不是至关重要的,但它有助于确保很少使用的索引不被标识出来从而被意外地删掉。

我们使用事件探查器必须收集的事件有:

Stored Procedures

RPC:Completed

SP:StmtCompleted

TSQL

SQL:BatchCompleted

SQL:StmtCompleted

至于列,建议收集的有:

ApplicationName

EventClass

TextData

Duration

LoginName

Reads

NTUserName

为了帮助减少随着时间的推移事件探查器跟踪所收集的事件数量,建议你仅仅收集那些duration50毫秒或更长的事件。这仍然会产生相当大的文件,所以你要确保不要把这些数据存在SQLServer所在的服务器上,也要为存储跟踪文件留足空间。 

产生用到的索引的报告

接下来,要使用索引调优向导分析跟踪文件。确保使用“Keep all existing indexes”选项并使用“Thorough”调优模式。在“Advanced Options”高级选项下,不选“Limit number of workload queries to sample”

正如你所料,运行这个分析将对你的SQLServer产品服务器带来负荷。确保在不是很忙的时候运行。

一旦索引调优向导完成了,我们要看看索引使用报告,如下所示。注意这个报告告诉我们索引是否有用,它也告诉我们每个索引的大小。

此时,我们可以停止使用这些结果作为我们的基础来移除未使用的索引和分辨大索引。但如果有大量要处理的表,那么你可以考虑下面另外的步骤。

 假定我们要使这些另外的分析自动化,我们所要做的就是使用保存按钮用文本格式来保存这个报告,然后将这个文本文件导入到SQLServer的表里。 

导入报告到SQLServer表里

选择任意一个数据库,创建下面的表:

CREATE TABLE [ dbo].[Analysis ] (

[ Table ] [ to varchar ] (50) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL,

[ Indice ] [ to varchar ] (100) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL,

[ Uso ] [ to varchar ] (50) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL,

[ Peso ] [ int ] NOT NULL,

[ Base ] [ to varchar ] (15) COLLATE SQL_Latin1_General_CP1_CI_AI NULL,

[ Date ] [ datetime ] NOT NULL

) ON [ PRIMARY ]

一旦创建了表,就可以使用DTS导入向导来把文本文件导入到表里了。当导入这个文本文件时,选择TAB作为列分隔符,表Analysis为目标表。

一般导入了文本文件,就能使用下面的存储过程来列出不起作用的索引了。

CREATE PROCEDURE Indices_Inactivos AS

declare it @Base varchar(15), @Tabla varchar(50), @Indice varchar(100), @BaseAnt varchar(15), @TablaAnt varchar(50)

IndicesInactivos declares for cursor select Base, Table, Indice from Analysis where Indice like ' [ [ ]IX% ' group by Base, Table, Indice having sum(convert(money, replace(Uso,',','.'))) = 0

they open IndicesInactivos
fetch next from IndicesInactivos into @Base, @Tabla, @Indice

while (@@FETCH_STATUS < > -1)
begin 
   if @Base < > @BaseAnt 
   begin 
      Print ' BASES: ' + @Base 
      select @BaseAnt = @Base 
      select @TablaAnt = '' 
   end 
   if @Tabla < > @TablaAnt 
   begin 
      Print ' TABLE: ' + @Tabla 
      Print ' INDICES: ' 
      select @TablaAnt = @Tabla 
   end

   Print ' ' + @Indice 
   fetch next from IndicesInactivos into @Base, @Tabla, @Indice 
   continue
end

close IndicesInactivos    
deallocate IndicesInactivos

GO

在查询分析器里不带参数执行这个过程。输出如下的未使用索引的报告:

BASE: EXAMPLE

TABLE: [ dbo].[APLICACIONES ]

INDICES:

[ IXC03.10.31_APLICACIONES_NroTrans ]

TABLE: [ dbo].[AWItemsAcumHistoricos ]

INDICES:

[ IX_AWItemsAcumHistoricosFecha ]

[ IX_AWItemsAcumHistoricosItem ]

[ IXC_AWItemsAcumHistoricos_Fecha_CodItm ]

TABLE: [ dbo].[CAJASREG ]

INDICES:

[ IXCP04.01.16_CAJASREG_CodCaj2_NroTrans ]

TABLE: [ dbo].[CHEQUES ]

INDICES:

[ IXC04.01.09_CHEQUES_FechaVto ]

[ IXCP04.01.16_CHEQUES_CodCtacte_NroTrans_Secuencia_NroTransegr_Tipo_Directo ]

下面是另一个存储过程。它用来列出在跟踪里使用的索引。用这些结果来列出那些非常大的索引。

CREATE PROCEDURE Indices_Usados AS

declare it @Base varchar(15), @Tabla varchar(50), @Indice varchar(100), @BaseAnt varchar(15), @TablaAnt varchar(50), @Uso varchar(50)

IndicesUsados declares for cursor select Base, Table, Indice, to convert(varchar, max(convert(money, replace(Uso, ', ', '.'))))as Uso from Analysis where Indice like ' [ [ ]IX% ' group by Base, Table, Indice having sum(convert(money, replace(Uso,',','.'))) > 0 Order by Base, Table, to convert(varchar, max(convert(money, replace(Uso,',','.')))) desc, Indice

they open IndicesUsados
fetch next from IndicesUsados into @Base, @Tabla, @Indice, @Uso

while (@@FETCH_STATUS < > -1)
begin 
   if @Base < > @BaseAnt 
   begin 
      Print ' BASES: ' + @Base 
      select @BaseAnt = @Base 
      select @TablaAnt = '' 
   end 
   if @Tabla < > @TablaAnt 
   begin 
      Print ' TABLE: ' + @Tabla 
      Print ' INDICES: ' 
      select @TablaAnt = @Tabla 
   end

   if len(@Uso) = 4 
   begin 
      select @Uso = ' 0 ' + @Uso 
   end

   Print @Uso + ' ' + @Indice 
   fetch next from IndicesUsados into @Base, @Tabla, @Indice, @Uso 
   continue

end

close IndicesUsados deallocate IndicesUsados

GO

一旦执行了,将包含下面的报告。例如:

BASE: EXAMPLE

TABLE: [ dbo].[APLICACIONES ]

INDICES:

79.20 [ IXCY04.05.03_APLICACIONES_NroTransegr_NroTrans_RefVto ]

33.30 [ IXCY04.05.03_APLICACIONES_Aplicaciones_NroTransegr_RefVto_NroTrans_FechaVto_Importe ]

20.50 [ IXC03.11.24_APLICACIONES_NroTransegr_AplNrotrans_AplRefvto ]

02.10 [ IXCP03.11.12_APLICACIONES_Nrotrans_NroTransegr_Importe_FechaVto_AplrefVto ]

100.00 [ IXC04.05.27_APLICACIONES_NroTrans_NroTranselim_AplNroTrans_FechaVto_AplRefvto ]

00.10 [ IXC04.05.27_APLICACIONES_AplNrotrans ]

00.10 [ IXCY04.05.07_APLICACIONES_AplNrotrans_AplRefvto_FechaVto_NroTrans_NroTransing ]

TABLE: [ dbo].[AWItemsAcumHistoricos ]

INDICES:

00.10 [ IXC03.05.16_AWItemsAcumHistoricos_CodItm_Fecha ]

TABLE: [ dbo].[BANCOSCOD ]

INDICES:

100.00 [ IXCY04.05.14_BANCOSCOD_CodBan_Descripcion ]

 两个结果都重要。第一个告诉我们哪一个索引未使用,第二个列出每个索引的大小,或许给我们一个线索任何索引都要倍加小心的检查。尺寸非常大的索引也许表明有潜在的压力去降低速度。 

综述

一旦删除了所有不必要或过大的索引,你要紧盯你数据库的性能,以确保你删掉的索引是真正需要的,但又很少用到以至于在你的跟踪里没有显示。

 

DBCC SHOWCONTIG在SQL Server 2005与SQL Server 2000里的比较和改进

DBCC SHOWCONTIG用来显示指定表的数据和索引的碎片信息

在SQLServer2000里,DBCC SHOWCONTIG在下面的情形下使用时会在表上产生一个S锁,因而会影响DML操作(INSERT、UPDATE、DELETE)。

  • 当表是一个堆表时
  • 当表有聚集索引,但是仅指定table_name参数调用DBCC SHOWCONTIG(无论使用WITH FAST选项与否)。
  • 当表有聚集索引,并且指定了table_name参数和index_name或index_id参数,但是没有使用WITH FAST选项。

对于大表,DBCC SHOWCONTIG会花费大量的时间。在此期间对DML操作的影响会波及应用程序。如果表有聚集索引,你能在SQLServer2000里解决这个问题。

这个问题在SQLServer2005里已经得到解决。在SQLServer2005里,所有使用DBCC SHOWCONTIG在表上要求IS锁,因而允许同时进行DML操作。

此外,在SQLServer2005里,建议使用表值动态管理函数sys.dm_db_index_physical_stats来代替DBCC SHOWCONTIG命令。

本文描述在SQLServer2000里的问题和解决方法,在SQLServer2005里的解决方法和建议。

生成测试环境
执行下面步骤模拟环境:
1.创建表:
USE [TESTDB]
GO
CREATE TABLE [dbo].[test_contig](
[SK_Store_ID] [smallint] NOT NULL,
[SK_Item_ID] [int] NOT NULL,
[SK_Date_ID] [int] NOT NULL,
[SK_Buyer_ID] [int] NULL,
[SK_Model_Strategy_ID] [smallint] NULL,
[Model_QTY] [int] NULL,
[On_Hand_Qty] [int] NULL,
[On_Order_Qty] [int] NULL,
[Return_Qty] [int] NULL,
[Retail_Amt] [numeric](9, 2) NULL,
[Days_In_Stock] [tinyint] NULL,
[ETL_Load_ID] [int] NULL,
[Last_Modified_Date] [datetime] NULL,
[Modified_by_User] [varchar](30) NULL
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX ix_test_contig ON test_contig(sk_store_id, sk_item_id, sk_date_id)
GO
2.载入大量数据到表里。在表里载入3千万行。这会确保一个会话有足够的时间运行DBCC SHOWCONTIG,同时切换到另一个会话去查询目录表看看DBCC SHOWCONTIG运行期间用的什么锁。
3.连接到SQLServer2000执行下面的命令:
DBCC SHOWCONTIG (test_contig)

SQL Server 2000里的局限性
正如前面测试所显示,DBCC SHOWCONTIG在表上产生了一个S锁。为了查看它,当DBCC SHOWCONTIG运行时用另一个会话来运行下面的查询:
SELECT o.name, CASE l.rsc_type
WHEN 1 THEN 'NULL'
WHEN 2 THEN 'Database'
WHEN 3 THEN 'File'
 WHEN 4 THEN 'Index'
WHEN 5 THEN 'Table'
WHEN 6 THEN 'Page'
WHEN 7 THEN 'Key'
WHEN 8 THEN 'Extent'
WHEN 9 THEN 'RID'
WHEN 10 THEN 'Application'
               END AS Resource_Type,
               CASE l.req_mode
                WHEN 0 THEN 'NULL'
WHEN 1 THEN 'Sch-S'
WHEN 2 THEN 'Sch-M'
WHEN 3 THEN 'S'
 WHEN 4 THEN 'U'
WHEN 5 THEN 'X'
WHEN 6 THEN 'IS'
WHEN 7 THEN 'IU'
WHEN 8 THEN 'IX'
WHEN 9 THEN 'SIU'
WHEN 10 THEN 'SIX'
WHEN 11 THEN 'UIX'
WHEN 12 THEN 'BU'
WHEN 13 THEN 'RangeS_S'
 WHEN 14 THEN 'RangeS_U'
WHEN 15 THEN 'RangeI_N'
WHEN 16 THEN 'RangeI_S'
WHEN 17 THEN 'RangeI_U'
WHEN 18 THEN 'RangeI_X'
WHEN 19 THEN 'RangeX_S'
WHEN 20 THEN 'RangeX_U'
                WHEN 21 THEN 'RangeX_X'
               END AS Lock_Type
FROM testdb.dbo.sysobjects o, master.dbo.syslockinfo l
WHERE l.rsc_objid = o.id
GO
输出结果如下:

--Locks held in SQL Server 2000

Name Resource_Type Lock_Type
test_contig Table S

在SQLServer2000里,由于DBCC SHOWCONTIG在表上有S锁,所以其他的事务如试图插入、更新或删除表里的数据都将被阻塞。这就是DBCC SHOWCONTIG的局限性。对于大表,它会花费更长的时间。对于测试环境的例子,DBCC SHOWCONTIG(test_config)花费了将近2分钟;但对于大表,可能会更长。因此,在日常事务处理的时间不要去执行DBCC SHOWCONTIG。

在下面的执行语句里,在DBCC SHOWCONTIG期间也会对表产生一个S锁:
DBCC SHOWCONTIG (test_contig) WITH FAST

注意我们仍然在使用SQLServer2000

使用WITH FAST选项意味着DBCC SHOWCONTIG将对索引执行快速的扫描并输出最小的信息。快速扫描不会读索引的叶级或数据页。即使快速扫描花费更少的时间,它仍然在表上产生S锁。

SQL Server 2000里的解决方法
在SQLServer2000里,可以改变DBCC SHOWCONTIG命令如下来解决:
DBCC SHOWCONTIG (test_contig, 1) WITH FAST
GO

通过在DBCC SHOWCONTIG调用里指定index_id=1,你要求它检查聚集索引的碎片信息。通过指定WITH FAST选项,你要求执行快速扫描且输出最小的信息。快速扫描不会读索引的叶级或数据页。

这个执行在表上产生IS锁。因而它不会阻塞同时对表进行的插入、更新或删操作。如果你在执行DBCC SHOWCONTIG期间通过使用前面的那个脚本来查询目录表的话,你会得到下面的结果:
--Locks held in SQL Server 2000 with index_id=1

Name Resource_Type Lock_Type
test_contig Table IS

注意这个解决方法只适用于有聚集索引的表。

SQL Server 2005里的解决方法

在SQLServer2000里的阻塞问题在SQLServer2005里已经得到了解决。对于SQLServer2005,所有DBCC SHOWCONTIG的执行都在表上产生IS锁。因而不会阻塞同时对表进行的插入、更新或删除操作。当DBCC SHOWCONTIG运行时,用另一个会话执行下面的查询以便列出锁信息:
SELECT o.name, l.resource_type, l.request_mode
FROM sys.objects o, sys.dm_tran_locks l
WHERE l.resource_associated_entity_id = o.object_id
GO
输出结果如下:
--Locks held in SQL Server 2005
Name Resource_Type Lock_Type
test_contig Table IS

SQL Server 2005里的建议
DBCC命令在将来的SQLServer版本里将不再被支持。因而,建议你使用函数sys.dm_db_index_physical_stats。为了检查表的碎片,执行下面的查询:
SELECT * FROM sys.dm_db_index_physical_stats(db_id(), object_id('test_contig'), NULL, NULL , 'DETAILED')
GO
当该查询运行时,如果你使用前面的脚本来检查锁的话,将会输出下面的结果:
--Locks held in SQL Server 2005

Name Resource_Type Lock_Type
test_contig Table IS

综述
在SQLServer2000里,DBCC SHOWCONTIG的一些使用会在表上产生S锁,因而会影响DML(INSERT、UPDATE、DELETE)操作。在大表上,DBCC SHOWCONTIG会花费更长的时间。在SQLServer2000里如果表有聚集索引,那么可以解决这个问题。

这个问题在SQLServer2005里已经得到了解决。在SQLServer2005里,所有DBCC SHOWCONTIG的使用都在表上产生的是IS锁,因而允许同时进行DML操作。此外,在SQLServer2005里,建议使用表值动态管理函数sys.dm_db_index_physical_stats来代替DBCC SHOWCONTIG命令。

并非所有的索引天生平等


 
--王成辉翻译整理,转贴请注明出处
--原帖地址:http://www.sql-server-performance.com/indexes_not_equal.asp

如果你对索引有很多的经验,那么你可能已经熟悉聚集索引和非聚集索引的区别。但本篇文章不讲这个。本篇文章是关于SQLServer查询优化器是否会使用你精心创建的索引。也许你不了解这个,但是只是因为一列上有索引并不意味着查询优化器会使用它。你可以想像,索引不被使用是浪费时间的,甚至在最糟的情况下,它会降低你应用程序的性能。让我们来看看这是为什么。

现在开始,让我们看看一个简单的例子。假定我们有一个accounting数据库。里面有一个表Orders,该表有很多不同的列,而我们只关心两列:Orderidemployeeid。该表有150000行并在employeeid列(原文里这里是表,可能是笔误)上有一个非聚集索引。现在让我们运行下面的查询:

SELECT orderid FROM orders WHERE employeeid = 5

对于查询注意到的第一件事就是查询的WHERE子句里用到的列employeeid上有一个非聚集索引。因此,当通过查询优化器查询运行查询时,你最可能会认为查询优化器将使用这个索引来产生所请求的结果。

不幸的是,你不能自动的作出这个假设。仅仅因为有一个可用的索引并不意味着查询优化器会使用它。这是因为查询分析器在使用一个索引前总是评估它是否有用。如果查询分析器检查一个索引发现它没有用,它会忽略这个索引。如果需要,它会执行一个表扫描来产生所请求的结果。

那么什么是有用的索引呢?为了回答这个问题,我们需要明白,查询优化器最大的一个潜在目的就是减少I/O数量,以及执行查询所花费的相应时间。换句话说,查询优化器评估查询能够执行的很多不同方法,然后找出它认为会产生最少I/O数量的一个。但是令人惊异的是,使用一个可用的索引并不总是意味着使用最少的I/O数量。在大多数情况下,特别是有一个非聚集索引,表扫描比可用的索引会产生更少的I/O

在查询优化器使用一个索引之前,查询优化器评估索引看其是否有足够的可选择性。这是什么意思?可选择性是指查询返回的行数和表的行数的一个百分比。如果返回的行数很少,查询被认为具有高可选择性。通常说来,如果查询返回的行数少于表行数的5%,则具有高可选择性,索引更可能会被使用。如果查询返回表行数的5%-10%,索引也许会、也许不会被使用。如果查询返回表行数的10%以上,索引更可能不会被使用。如果对于查询没有其他有用的索引,将执行一个表扫描。

让我们回头看看这个查询例子:

SELECT orderid FROM orders WHERE employeeid = 5

仅仅通过查看这个查询我们并不能知道employeeid上的索引是否可用。话说回来,我们知道表有150000行,“employeeid = 5”的行有5000。如果用5000除以150000得到3.3%。由于3.3%少于5%,查询优化器将更可能使用这个索引。但如果“employeeid = 5”的行有25000行,这样25000除以150000得到16.6%。由于16.6%大于5%,甚至大于10%,查询优化器将更可能使用表扫描来代替索引的使用。

那么一个表扫描怎么能使用比索引(比如例子中的非聚集索引)更少的I/O呢?如果索引具有高可选择性,特别是如果只返回1行,那么非聚集索引作用很大。但如果要返回很多记录,那么索引不具有可选择性,返回数据的I/O会很高。这是因为查询优化器不得不先根据索引定位数据(用尽I/O),然后根据表返回数据(更多的I/O)。在某些时候,查询优化器确定扫描整个表所花费的I/O比在索引和表之间往返以返回请求的行所花费的I/O要少。

上面给出的例子适用于大部分非聚集索引。如果可用的索引是聚集索引,那么索引会被使用,即使它的可选择性很低,因为索引就是表并且I/O操作很有效。

那么查询优化器怎么知道一个可用的索引选择性足够有用呢?这通过维护每个表上的每个索引的统计来完成。索引统计是存储在sysindexes表里的一个柱状图的值。这些统计是可用行的一个抽样,以近似的告诉查询优化器一个特定索引的可选择性是多少。

每当索引创建、重建、运行UPDATE STATISTICS命令或查询优化器需要时自动创建创建索引统计。索引统计不会实时的维护,因为那样会带给服务器很多的负荷。但因为索引统计不是实时的,它们可能会过期,所以有时候查询优化器会因为索引统计不是最新的而作出差的选择。

但仅仅因为统计是最新的也不意味着查询优化器会使用一个可用的索引。记住,查询优化器基于索引的可选择性作出决定,而它使用索引统计来决定可选择性。
 
所以如果查询优化器能检查看看一个特定索引是否有用,我们怎么能做相同的事情呢?幸运的是,有一个命令能让我们检查一个索引,并发现这个特定的索引是否有足够的可选择性去使用。
我们要知道一个索引是否有足够的可选择性是因为如果它没有,那么它不会被使用。如果一个索引不被使用,那么它的存在就没有意义了。删除不必要的索引最有可能提升你应用程序的性能,因为你可能知道,由于维护索引的开销会降低对表的INSERTUPDATEDELETE的性能。如果正被讨论的表在一个频繁更高的数据库里,那么索引维护会引起一些瓶颈。所以我们的目的是确保如果有一个索引,它就要有足够的可选择性以便使用。我们不要维护不被使用的索引。

我们将使用下面的命令来找出一个索引的可选择性:

DBCC SHOW_STATISTICS (table_name, index_name)


当运行这个命令时,它输出类似下面的结果。这是我维护的一个数据库的真实结果。
索引in_tran_idx的统计:

Updated              Rows    Rows Sampled  Steps  Density       Average key length 
-------------------- ------- ------------  ------ ------------  ------------------
Feb 24  2001 3:36AM  7380901 7163688       300    2.2528611E-5  0.0

(1 row(s) affected)

All density Columns 
------------------------
2.2528611E-5 in_tran_key

Steps 
----------- 

283 
301 
340 
371 
403 
456 

44510 

(300 row(s) affected)


DBCC execution completed. If DBCC printed error messages, contact your system administrator.

这个结果包含了大量的信息,大多数超过了本文的范围。我们关注的一个焦点是列“All density”的密度值“2.2528611E-5”

密度是指一个索引重复行的平均百分比。如果一个索引列,如employeeid,有很多重复数据,那么称索引有很高的密度。但如果索引列有很多唯一的数据,那么称索引有很低的密度。

密度跟可选择性是相反的关系。如果密度是一个很高的数值,那么可选择性就很低,这意味着索引不会被使用。如果密度是一个很低的数值,那么可选择性就很高,索引更可能被使用。

在上面例子的输出结果里,索引的密度小于1%。反过来,这意味着可选择性高于99%,即索引可能对于查询优化器来说很有用。

如果你是一个高级DBA,你可能已经注意到我过分简单了。虽然那样,我在本文里的观点仍然很正确,我的观点是,并非所有的索引都是平等的。因为索引对查询优化器可用并不意味着会被使用。

DBA来说,这意味着你要了解你表的索引。你可以运行DBCC SHOW_STATISTICS命令一段时间,看看你索引的实际可选择性如何。你也许会发现你的一些索引没被使用。如果有这种情况,你要考虑将它们删除,这反过来会加速你的应用程序。

对于新的DBA来说,移除而不是添加索引似乎对于调优数据库性能是后退了一步。但你对SQLServer内部运行了解得越多,你会更理解使用索引调优你的应用程序的局限性。

揭示API服务器游标

 
--王成辉翻译整理,转贴请注明出处

导言

最近,我得知执行分布式查询的过程比最初预计的要长得多。起初,我发现分布式查询分配了CPU,但内存和磁盘IO值在递增。事实上,这么慢的内存和磁盘IO看上去象不动一样。我的第一反应是缺少或者不正确的索引以及(或者)表扫描的结果。可是使用事件探查器显示问题不是索引引起的。这个反应缓慢的分布式查询主要是利用了一套游标去返回本地服务器的结果。

在下面的文章里,我将通过对下几个方面提供一个概述来设法揭示API服务器游标:API服务器游标是什么;怎样确定分布式查询什么时候使用API服务器游标;提供一个消除使用API服务器游标的简单分布式查询的例子。

API服务器游标是什么

SQLServer OLEDBSQLOLEDB)提供者使用两种方法从远程服务器取得分布式查询的结果。一种方法是使用默认结果集。另一种方法是使用游标,就是刚才提到的API服务器游标。

SQLOLEDB怎样使用API服务器游标

SQLOLEDB使用SQLServer系统存储过程去管理API服务器游标。详情请见这里

使用事件探查器揭示API服务器游标

SQLServer事件探查器跟踪发生的事件。它的使用详见这里。正如前所述,API服务器游标使用系统存储过程来管理,所以在分辨一个连接是使用API服务器游标还是默认结果集时是很简单的。对于这个例子,你要确认SQLServer事件探查器连接到了分布式查询运行的远程服务器(链接服务器)。否则,你不会看见远程服务器上执行的工作量。在我的跟踪里使用了下面的事件来跟踪:

·          Stored Procedures - SP:Completed
·          Stored Procedures - SP:StmtCompleted
·          Stored Procedures - RPC:Completed
·          TSQL - SQL:StmtCompleted

Stored Procedures - RPC:Completed事件跟踪API服务器游标使用的系统存储过程。


使用API服务器游标的分布式查询的例子

下面的例子在SQLServer20002005的服务器上测试通过。最初的分布式查询语句使用一个四部分名称连接到远程服务器的数据库(链接服务器)。分布式查询连接两个表,两个表都在远程服务器上,每一个表大约有6000条记录。我预计应用查询条件Account = “XYZ”共需要60秒来返回结果。

use LocalDatabase;
select  *
from    RemoteServer.RemoteDatabase.dbo.RemoteTable0 as t0
inner join       RemoteServer.RemoteDatabase.dbo.RemoteTable1 as t1
          on      t0.Account = t1.Account
where  t0.Account = 'XYZ' 

事件探查器跟踪的结果表明当从SQLServer查询分析器里执行分布式查询时,远程服务器对sp_cursorfetch系统存储过程有大量的调用。跟踪的结果如下图显示:

 

RPC:Completed事件的列TextData显示执行了系统存储过程sp_cursorfetch

 
每执行一次该系统存储过程,在两个服务器之间都有一个往返。前面的语句和SQLServer事件探查器跟踪明显表明为什么使用API服务器游标会潜在地快速成为一个性能问题。对于这一点详细说明如下:系统存储过程sp_cursorfetch执行一连串的批语句直到所有的数据从远程服务器返回。系统存储过程sp_cursorfetch的最后一个参数是每次执行处理的记录数。

 

在这个例子里,系统存储过程sp_cursorfetch每次执行处理100条记录。因此,处理共计6000条记录,要一连串执行60次,每次执行都要在服务器之间往返一次。

OPENQUERY

SQLServer有一个内置功能用来确保对远程服务器执行分布式查询。请查看SQLServer的帮助文档里关于OPENQUERY的主题以获得关于这个功能的更多信息。

可以通过利用OPENQUERY 函数简单的修正查询来消除API服务器游标的使用。

use LocalDatabase;
select  *
from    OPENQUERY(
          RemoteServer
          , 'select *
                   from RemoteDatabase.dbo.RemoteTable0 as t0
                   inner join       RemoteDatabase.dbo.RemoteTable1 as t1
                             on      t0.Account = t1.Account
                   where  t0.Account = ''XYZ'''
); 


另一个跟踪结果表明用OPENQUERY 函数的SELECT语句仅在远程服务器上执行。这显著的减少了支持这个查询执行的的资源。

结论

SQLServer提供了消除对分布式查询使用API服务器游标的所有工具。

 

SQLServer里统计维护功能(autostats)

SQLServer里统计维护功能(autostats)
--王成辉翻译整理,转贴请注明出处

摘要

最新引进的统计维护功能AutoStat可能在生产系统上通过执行下面的行为产生不必要的开销:
  • 在繁重的生产期间开始统计更新

或者

  • 在特定点开始大量的更新统计进程
这篇文章详细描述你期望看到的产生autostats和在数据库的表上运行UPDATE STATISTICS的条件。

关于SQLServer2000里autostats更多的信息,请查看
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsql2k/html/statquery.asp

如果你使用的是SQLServer2005看下面微软白皮书里关于查询优化器怎样使用统计的信息:

更多信息

常规信息

SQLServer使用基于成本的优化(CBO),所以对表和索引提供的统计信息非常敏感。如果没有正确和最新的统计信息,那么SQLServer在为特定的查询产生最好的执行计划时会面临挑战。

SQLServer每个表上的统计维护使用下面的信息帮助优化器做出基于成本的决定:
表的行数
表使用的页数 
自上次统计更新以来表的键被更改的数量
为索引(每一个索引)存储的另外信息包括:
第一列上等高的柱状图
所有列前缀的密度
平均键长
当一个新索引创建时索引上的统计就自动创建了。另外,现在也可以在其他列上创建和维护统计了。

为了尽可能的维护统计信息使其最新,SQLServer引进了AutoStat功能,通过追踪表的更改,当达到某一个更改阈值时,它能为表自动更新统计。SQLServer还引进了auto-create-statistics功能,它能为正确优化特定的查询而使服务器自动的产生所有需要的统计。

AutoStat触发的时机

正如上面所述,当达到一个更改阈值("change threshold")时,AutoStat会为特定的表自动更新统计。系统表sysindexes的列rowmodctr用来记录自上次更新表的统计后插入、删除、更新的行的总数,随着时间的推移,它可以影响查询处理器的决策过程。这个计数器每当下面的事件发生时被更新:
插入一行 
删除一行 
更新了索引列
注:TRUNCATE TABLE不会更新rowmodctr。

表统计更新后,rowmodctr的值重新设置为0,并且更新表的统计架构版本。

更进一步,在存储过程从缓存中采用执行计划并且计划对统计比较敏感的情形下,统计架构版本将和目前的版本比较。如果有新的版本可用,存储过程的计划将被重新编译。

自动更新统计的基本法则:
如果表的集的势小于6并在tempdb数据库里,对表的每6个更改就会自动更新统计。
如果表的集的势大于6且小于等于500,则每500个更改就自动更新统计。 
如果表的集的势大于500,则当有500加上表的20%个更改时自动更新统计。
对于表变量,更改集的势不会触发自动更新统计。
注:在此严谨的场合,SQLServer用表的行数计算集的势。
注:除了集的势之外,断言的选择也会影响AutoStats的触发时机。这意味着如果集的势小于500且每500个更改之后或者如果集的势大于500且每20%的更改之后统计不会被更新。一个按比例增加的因素(取值范围从1到4,包括1和4)的产生依赖于选择性、该因素的积、根据法则得到的更改行数(该行数是对于触发AutoStats要求更改的实际行数)。

上面的法则可用下表的形式来展现:
表类型 清空条件 清空阈值 不清空阈值
永久表 <500行 更改数>=500 更改数>=500+20%*集的势
临时表 <6行 更改数>=6 更改数>=500+20%*集的势
表变量 更改集的势不会触发AutoStats
 
下面有两个例子来帮助示范这个概念:

示例1

考虑pubs数据库里的authors表,它有23行,2个索引。在一列au_id上有一个唯一聚集索引UPKCL_auidind,在列au_lname和au_fname上有一个非聚集的复合索引aunmind。因为这个表小于500行,AutoStat将在表数据的500个更改之后开始。更改可以是针对索引的列如au_lname或它的任何联合的500或更多的insert、delete中的一个。

因此,你能通过追踪随着每次更改增加的sysindexes.rowmodctr的值来预计UPDATE STATISTICS将在什么时候开始。当它达到或超过500时,你可以预计UPDATE STATISTICS要启动了。

示例2

考虑另一个集的势为1000的表t2。对于大于500行的表来说,SQLServer将在有(500+20%*集的势)个更改时启动UPDATE STATISTICS。按照规则,1000的20%是200,所以你可以预计在对表做出大约700个更改之后会触发AutoStat。

统计更新自动化

为了在AutoStat将运行时自动更新统计,你可以检测sysindexes表,确定表更改次数何时达到触发点。下面是基本的算法:
   if (sysindexes.rows > 500)
                                                                  if (sysindexes.rows * 0.20 >= sysindexes.rowmodctr && productionhours) //500 change leeway
                                                                      begin
                                                                 disable autostats
                                                            log autostats disable
                                                            end
                                                            else
                                                            begin
                                                            stats ok
                                                            end
                                                            else
                                                            if (sysindexes.rowmodctr >= 425) //75 change leeway
                                                            begin
                                                            disable autostats
                                                            log autostats disable
                                                            end
                                                                

你可以稍后按照下面调度作业:
  • 在你强迫禁用autostat期间的所有表上运行UPDATE STATISTICS。

并且

  • 重新启用AutoStat,因为当UPDATE STATISTICS运行时每一个表的更改计数器会重置为0。

控制UPDATE STATISTICS是否在表上运行

当AutoStat被证明有问题的时候,最明显的解决方法是禁用自动统计,从而让DBA在数据库不忙的时候自由调度UPDATE STATISTICS。你可以通过使用UPDATE STATISTICS或sp_autostats存储过程来完成,UPDATE STATISTICS的语法如下:
   UPDATE STATISTICS <table>...with NORECOMPUTE  

sp_autostats存储过程的语法如下:
 
sp_autostats <table_name>, <stats_flag>, <index_name>
stats_flag的取值为"on"或"off"。
你也可以使用sp_dboption在每个数据库级禁用UPDATE STATISTICS或CREATE STATISTICS:
sp_dboption <dbname>,'auto update statistics', <on | off>

或者

sp_dboption <dbname>,'auto create statistics', <on | off>

控制UPDATE STATISTICS进程的并发数

通常,只要没有为特定表禁用AutoStat,就不可能同时自动产生大量的UPDATE STATISTICS语句(DCR 51539已对此存档)。不管怎样,服务器限制UPDATE STATISTICS进程的并发数为每处理器4个。

何时运行Autostats

当依赖于统计的存储过程重新编译时,你可以使用跟踪标记205来报告AutoStat的结果。这个标记将写下面的信息到错误日志:
1998-10-15 11:10:51.98 spid9 Recompile issued : ProcName: sp_helpindex
LineNo: 75 StmtNo: 29
 
当跟踪标记205打开时,更新统计的来自跟踪8721的AutoStat信息也包括下面的信息。开始信息会在sysindexes的列RowModCnt中以大于0的值存储。在运行UPDATE STATISTICS后,结束信息会在列RowModCnt中以0存储:
1998-10-15 11:38:43.68 spid8 Schema Change: Tbl Dbid: 7 Objid:
133575514 RowModCnt: 60500 RowModLimit: 60499
 
对于该信息,"RowModCnt"是对表的更改总数。"RowModLimit"是阈值,当超过这个阈值时,UPDATE STATISTICS语句将为表而执行。

打开跟踪标记8721也可能在AutoStat运行时将信息存入错误日志。下面是你希望看到的信息类型的例子:
1998-10-14 16:22:13.21 spid13 AUTOSTATS: UPDATED Tbl: [authors]
Rows: 23 Mods: 501 Bound: 500 Duration: 47ms UpdCount: 2
 
对于该信息,"Mods"是对表的更改总数,"Bound"是更改阈值,"Duration"是语句UPDATE STATISTICS执行完成需要的时间,"UpdCount"更新统计的计数。

你也可以使用事件探查器来分辨UPDATE STATISTICS语句是否运行,操作步骤详见文章《SQLServer UPDATE STATISTICS提示》。
 
注:如果很多统计是用AutoStat来更新的,那么大量的信息会写到错误日志里。所以在生产服务器或其他关键的服务器上使用它们之前要对这些跟踪标记进行彻底的试验。

架构锁

SQLServer使用两种类型的架构锁,当对表进行统计更新时都要用到:
   Sch-S: 架构稳定锁
                                                            ----------------------------
                                                            该锁确定任何会话在架构元素如表或索引上有架构稳定锁时架构元素不被删除。
                                                            Sch-M-UPD-STATS: 架构更改锁
                                                            -----------------------------------------
                                                            这是一个非阻塞锁,系统用来确定在给定时刻表上只有一个自动更新统计进程在运行。
Sp_lock存储过程将使用类型为TAB,资源为UPD-STATS、模式为SCH-M来报告该锁。
你可以通过运行sp_lock或从syslockinfo表里选择来查看这些锁。
 
原文地址:https://www.cnblogs.com/piaoqingsong/p/666607.html